Повторное обращение к JavaBeans
Теперь, после того как вы познакомились с синхронизацией, можете иначе взглянуть на JavaBeans. Когда бы вы не создавали Bean, вы должны предполагать, что он будет использован в среде с множеством процессов. Это значит, что:
Везде, где это возможно, все public методы Bean должны быть synchronized. Конечно, это приведет к увеличению времени выполнения synchronized методов. Если это будет основной загвоздкой, то методы, не вызывающие подобных проблем в критических секциях должны быть оставлены без synchronized, но учитывайте, что обычно это не разрешается. synchronized должны быть методы, которые могут быть оценены как относительно небольшие (например getCircleSize()
в следующем примере) и/или "атомарные", то есть те, которые вызывают такие небольшие куски кода, что объект не может быть изменен во время выполнения. Установка подобных методов как не-synchronized может не иметь какого-либо особенного эффекта на скорости выполнения программы. Вы можете также определить все public методы Bean как synchronized и опустить ключевое слово synchronized только тогда, когда вы твердо убеждены, что это необходимо и что это не приведет к изменениям.
При выполнении множественных событий для нескольких слушателей заинтересованных в этом событии, необходимо предположить, что слушатели могут быть добавлены или удалены при перемещении через список.
Первый пункт совершенно прост для рассмотрения, но следующий требует некоторого обдумывания. Рассмотрим пример BangBean.java, приведенный в последней главе. Тогда мы ушли от ответа на вопрос о множестве процессов игнорированием ключевого слова synchronized (который не был еще объяснен) и сделав события одноадресные (unicast). А вот тот же пример, измененный для работы в среде с множеством процессов и использованием многоадресных событий:
//: c14:BangBean2.java
// You should write your Beans this way so they
// can run in a multithreaded environment.
import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.*; import java.io.*; import com.bruceeckel.swing.*;
public class BangBean2 extends JPanel implements Serializable { private int xm, ym; private int cSize = 20; // Circle size
private String text = "Bang!"; private int fontSize = 48; private Color tColor = Color.red; private ArrayList actionListeners = new ArrayList(); public BangBean2() { addMouseListener(new ML()); addMouseMotionListener(new MM()); } public synchronized int getCircleSize() { return cSize; } public synchronized void setCircleSize(int newSize) { cSize = newSize; } public synchronized String getBangText() { return text; } public synchronized void setBangText(String newText) { text = newText; } public synchronized int getFontSize() { return fontSize; } public synchronized void setFontSize(int newSize) { fontSize = newSize; } public synchronized Color getTextColor() { return tColor; } public synchronized void setTextColor(Color newColor) { tColor = newColor; } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.black); g.drawOval(xm - cSize/2, ym - cSize/2, cSize, cSize); } // This is a multicast listener, which is
// more typically used than the unicast
// approach taken in BangBean.java:
public synchronized void addActionListener(ActionListener l) { actionListeners.add(l); } public synchronized void removeActionListener(ActionListener l) { actionListeners.remove(l); } // Notice this isn't synchronized:
public void notifyListeners() { ActionEvent a = new ActionEvent(BangBean2.this, ActionEvent.ACTION_PERFORMED, null); ArrayList lv = null; // Make a shallow copy of the List in case
// someone adds a listener while we're
// calling listeners:
synchronized(this) { lv = (ArrayList)actionListeners.clone(); } // Call all the listener methods:
for(int i = 0; i < lv.size(); i++) ((ActionListener)lv.get(i)) .actionPerformed(a); } class ML extends MouseAdapter { public void mousePressed(MouseEvent e) { Graphics g = getGraphics(); g.setColor(tColor); g.setFont( new Font( "TimesRoman", Font.BOLD, fontSize)); int width = g.getFontMetrics().stringWidth(text); g.drawString(text, (getSize().width - width) /2, getSize().height/2); g.dispose(); notifyListeners(); } } class MM extends MouseMotionAdapter { public void mouseMoved(MouseEvent e) { xm = e.getX(); ym = e.getY(); repaint(); } } public static void main(String[] args) { BangBean2 bb = new BangBean2(); bb.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e){ System.out.println("ActionEvent" + e); } }); bb.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e){ System.out.println("BangBean2 action"); } }); bb.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e){ System.out.println("More action"); } }); Console.run(bb, 300, 300); } } ///:~
Добавление synchronized для методов есть простейшее изменение. Однако помня, что addActionListener( ) иremoveActionListener( ), которые относятся к ActionListener теперь добавлены в и удалены из ArrayList, так, что можно создать необходимое количество.
Можно видеть, что метод notifyListeners( ) не synchronized. Он может быть вызван из более чем одного процесса за раз. Также возможно для addActionListener( ) или removeActionListener( ) быть вызванными из самого вызова notifyListeners( ), что является проблемой поскольку он пересекается (traverse) в ArrayList actionListeners. Чтобы избежать этой проблемы ArrayList клонирован вне секции synchronized и клон пересечен (traversed) (в Приложении A объясняются детали клонирования). Таким образом оригинальный ArrayList может быть использован без воздействия на notifyListeners( ).
Метод paintComponent( ) также не synchronized. Решение, стоит ли синхронизировать переопределенный (overridden) метод не такое же простое как в случае когда добавляется собственный метод. В данном примере кажется, что paint() выполняется успешно, независимо от того синхронизирован он или нет. Но дополнительно необходимо рассмотреть:
Изменяет ли метод значения "критических" переменных внутри объекта? Чтобы определить, является ли переменные "критическими", необходимо определить будут ли значения прочитаны или установлены другими процессами в программе. (В этом случае чтение и установка значения фактически всегда происходит через synchronized методы, так что можно их просто проверить.) В случае с paint() ни каких изменений нет.
Зависит ли метод от значения этих "критических" переменных? Если synchronized метод изменяет значение той переменной, которую использует ваш метод, то вам просто необходимо также объявить ваш метод как synchronized. В связи с этим, можно видеть, что значение переменной cSize изменяется synchronized
методами и, следовательно, paint() также должен быть synchronized. Однако в данном случае можно спросить, "А что ужасного произойдет в том случае, если cSize измениться во время paint()?" Когда видно, что ничего плохого, к тому же присутствует эффект самовосстановления (transient effect), можно решить оставить paint() не synchronized во избежании излишних накладных расходов при вызове synchronized метода.
И в третьих, необходимо убедиться, является ли базовый класс для paint() synchronized или нет. Это не просто высказывание для сотрясания воздуха, а просто подсказка. В нашем случае например, поля, изменяемые
через synchronized методы (такие как cSize), были перемешены в paint() формуле и могли изменить ситуацию. Однако обратите внимание, что synchronized не наследуется, так например, если метод является synchronized в базовом классе, то он не будет автоматически synchronized в переопределенном методе наследующего класса.
Тестовая программа TestBangBean2 была изменена по сравнению с версией из предыдущей главы так, чтобы показать способность множественного приведение типов в BangBean2 через добавление дополнительных слушателей.