Re: Speaking of thread safety?
This has been driving me for two days now and I had to get this out
before I left for the day.
Panel1 has a JSpinner that is used to acquire input. It uses a
ThreadSafeObservable to send that data to Panel2 where it is displayed.
I have deliberately used a new thread other than the EDT to do the
sending. This was easier than writing a Panel2 that used other than the
EDT to read the data but the point is the same, you can't guarantee that
the thread writing the data will be the same thread reading the data.
In the ThreadSafeObservable I synchronize on the ThreadSafeObserver when
calling update(). Then in Panel2's paintComponent() when I access the
stored value, I again synchronize on the ThreadSafeObserver using
'this'. This enforces the requirement that the same lock be used to
read and write the data. In this instance using a volatile for the
stored value would have worked even better but if I were sending some
arbitrary mutable object it wouldn't do.
If you look at Sun's source for Observable, you will see that the list
of Observers are stored in a private Vector. Because no method exist to
get at the Observers, overriding notifyObservers() becomes rather
difficult. I'm not sure if there is some underlying reason for this or
if it was just an oversight.
My SSCCE source code is below but I have put all of the files, including
Sun's source for the Observable and Observer on my web site here;
http://rabbitbrush.frazmtn.com/observable/
I would appreciate any comments or any possible risks that you see with
this approach.
Thanks,
package com.knutejohnson.test;
import java.awt.*;
import javax.swing.*;
public class Demo {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
JFrame f = new JFrame("Thread Safe Observable Demo");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new FlowLayout());
Panel1 p1 = new Panel1();
Panel2 p2 = new Panel2();
p1.addObserver(p2);
f.add(p1);
f.add(p2);
f.pack();
f.setVisible(true);
}
});
}
}
package com.knutejohnson.test;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
public class Panel1 extends JPanel {
private final ThreadSafeObservable observable;
private final JSpinner spin;
public Panel1() {
super(new GridBagLayout());
setPreferredSize(new Dimension(100,100));
observable = new ThreadSafeObservable();
spin = new JSpinner(new SpinnerNumberModel(0,0,100,1));
spin.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent ce) {
JSpinner s = (JSpinner)ce.getSource();
final int value = ((Integer)s.getValue()).intValue();
// start a different thread just to be sure
new Thread(new Runnable() {
public void run() {
observable.setChanged();
observable.notifyObservers(value);
}
}).start();
}
});
add(spin);
}
public void addObserver(ThreadSafeObserver o) {
observable.addObserver(o);
}
}
package com.knutejohnson.test;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
public class Panel2 extends JPanel implements ThreadSafeObserver {
private int value;
public Panel2() {
setPreferredSize(new Dimension(100,100));
}
public void update(ThreadSafeObservable o, Object arg) {
// store value here synchronized on this the observer
synchronized (this) {
value = ((Integer)arg).intValue();
}
repaint();
System.out.println(
"update() called from: " + Thread.currentThread().getName());
}
public void paintComponent(Graphics g) {
g.setColor(Color.WHITE);
g.fillRect(0,0,getWidth(),getHeight());
g.setColor(Color.BLUE);
// read value here synchronized on this
synchronized (this) {
g.fillRect(getWidth()/2-10,getHeight()-value,10,value);
}
System.out.println(
"paintComponent() called from: " +
Thread.currentThread().getName());
}
}
package com.knutejohnson.test;
import java.util.*;
public class ThreadSafeObservable {
private boolean changed = false;
private Vector obs;
public ThreadSafeObservable() {
obs = new Vector();
}
public synchronized void addObserver(ThreadSafeObserver o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
public synchronized void deleteObserver(ThreadSafeObserver o) {
obs.removeElement(o);
}
public void notifyObservers() {
notifyObservers(null);
}
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
for (int i=arrLocal.length-1; i>=0; i--)
// synchronize on the observer
synchronized (arrLocal[i]) {
((ThreadSafeObserver)arrLocal[i]).update(this,arg);
}
}
}
public synchronized void deleteObservers() {
obs.removeAllElements();
}
protected synchronized void setChanged() {
changed = true;
}
protected synchronized void clearChanged() {
changed = false;
}
public synchronized boolean hasChanged() {
return changed;
}
public synchronized int countObservers() {
return obs.size();
}
}
package com.knutejohnson.test;
public interface ThreadSafeObserver {
void update(ThreadSafeObservable o, Object arg);
}
--
Knute Johnson
email s/nospam/knute2010/