Философия Java

         

Синхронизация счетчиков


Вооружившись новым ключевым словом решим нашу задачу: мы просто добавляем ключевое слово synchronized для методов в TwoCounter. Следующий пример такой же как и предыдущим, но добавлено одно ключевое слово:

//: c14:Sharing2.java

// Using the synchronized keyword to prevent

// multiple access to a particular resource.

// <applet code=Sharing2 width=350 height=500>

// <param name=size value="12">

// <param name=watchers value="15">

// </applet>

import javax.swing.*; import java.awt.*; import java.awt.event.*; import com.bruceeckel.swing.*;

public class Sharing2 extends JApplet { TwoCounter[] s; private static int accessCount = 0; private static JTextField aCount = new JTextField("0", 7); public static void incrementAccess() { accessCount++; aCount.setText(Integer.toString(accessCount)); } private JButton start = new JButton("Start"), watcher = new JButton("Watch"); private boolean isApplet = true; private int numCounters = 12; private int numWatchers = 15;

class TwoCounter extends Thread { private boolean started = false; private JTextField t1 = new JTextField(5), t2 = new JTextField(5); private JLabel l = new JLabel("count1 == count2"); private int count1 = 0, count2 = 0; public TwoCounter() { JPanel p = new JPanel(); p.add(t1); p.add(t2); p.add(l); getContentPane().add(p); } public void start() { if(!started) { started = true; super.start(); } } public synchronized void run() { while (true) { t1.setText(Integer.toString(count1++)); t2.setText(Integer.toString(count2++)); try { sleep(500); } catch(InterruptedException e) { System.err.println("Interrupted"); } } } public synchronized void synchTest() { Sharing2.incrementAccess(); if(count1 != count2) l.setText("Unsynched"); } }

class Watcher extends Thread { public Watcher() { start(); } public void run() { while(true) { for(int i = 0; i < s.length; i++) s[i].synchTest(); try { sleep(500); } catch(InterruptedException e) { System.err.println("Interrupted"); } } } } class StartL implements ActionListener { public void actionPerformed(ActionEvent e) { for(int i = 0; i < s.length; i++) s[i].start(); } } class WatcherL implements ActionListener { public void actionPerformed(ActionEvent e) { for(int i = 0; i < numWatchers; i++) new Watcher(); } } public void init() { if(isApplet) { String counters = getParameter("size"); if(counters != null) numCounters = Integer.parseInt(counters); String watchers = getParameter("watchers"); if(watchers != null) numWatchers = Integer.parseInt(watchers); } s = new TwoCounter[numCounters]; Container cp = getContentPane(); cp.setLayout(new FlowLayout()); for(int i = 0; i < s.length; i++) s[i] = new TwoCounter(); JPanel p = new JPanel(); start.addActionListener(new StartL()); p.add(start); watcher.addActionListener(new WatcherL()); p.add(watcher); p.add(new Label("Access Count")); p.add(aCount); cp.add(p); } public static void main(String[] args) { Sharing2 applet = new Sharing2(); // This isn't an applet, so set the flag and


// produce the parameter values from args:

applet.isApplet = false; applet.numCounters = (args.length == 0 ? 12 : Integer.parseInt(args[0])); applet.numWatchers = (args.length < 2 ? 15 : Integer.parseInt(args[1])); Console.run(applet, 350, applet.numCounters * 50); } } ///:~

Можно заметить, что оба run() и synchTest() теперь synchronized.  Если синхронизировать только один из методов, то другой свободен в игнорировании блокировки объекта и может быть безнаказанно вызван. Это очень важное замечание: Каждый метод, который имеет доступ к критическим общим ресурсам должен быть synchronized, иначе он не будет правильно работать.

Теперь у программы появилось новое поведение. Watcher никогда не прочитает что происходит потому, что оба метода run() стали synchronized и, так как run() всегда запущен для каждого объекта, блокировка всегда установлена и synchTest() никогда не вызовется.  Это видно, так как accessCount никогда не меняется.

Что нам нравится в этом примере, так это возможность изолировать только часть кода внутри run(). Та часть кода, которую необходимо изолировать данным способ, называется  критическим участком (critical section) и используется ключевое слово synchronized, чтобы различными способами установить критические участки. Java поддерживает критические участки с помощью  синхронизированных блоков; в данном случае synchronized используется для определения объекта, блокировка которого будет использована для синхронизации прилагаемого кода:

synchronized(syncObject) {   // This code can be accessed    // by only one thread at a time }

До того как синхронизированный блок будет доступен, блокировка должна быть установлена в syncObject. Если какой-либо процесс уже имеет данную блокировку, то блок ( часть кода) не может быть доступен, пока не будет снята блокировка.

Пример Sharing2 может быть изменен если убрать ключевое слово synchronized у обоих методов run() и, вместо этого, установить блок synnchronized вокруг двух критических строк кода. Но что объект должен использовать как блокировку? То что уже используется synchTest(), т.е. ткущий объект (this)! Таким образом измененный run() выглядит следующим образом:

  public void run() {     while (true) {       synchronized(this) {         t1.setText(Integer.toString(count1++));         t2.setText(Integer.toString(count2++));       }       try {         sleep(500);       } catch(InterruptedException e) {         System.err.println("Interrupted");       }     }   }

Это единственные исправления которые необходимо сделать в Sharing2.java и, как видите, поскольку оба счетчика синхронизированы (согласно тому, что Watcher теперь может следить за ними), то Watcher получает соответствующий доступ во время выполнения run().

Конечно, вся синхронизация зависит от внимания программиста: каждый кусок кода который должен получать доступ к разделяемым ресурсам должен быть оформлен соответствующим синхронизированным блоком.


Содержание раздела