Prev: Exception Handling
Next: First class developer: who ?
From: Peter Duniho on 11 Mar 2010 01:26 Knute Johnson wrote: > [...] > I have two classes that happen to be extensions of JPanels. Both of > them have methods to input data that is then displayed (in various > forms) on the other. The Observable/Observer appeared to be perfectly > suited for this purpose. Then I got to thinking about which thread was > being used and the fact that different threads could be writing/reading > the data. [...] Why is that? Where is the data coming from? Both JPanels should be executing on the EDT, assuming you haven't explicitly introduced a new thread to them (and IMHO that would not generally be a good idea…GUI logic belongs in the EDT, and non-GUI logic doesn't belong in a GUI object). Pete
From: Knute Johnson on 11 Mar 2010 12:04 On 3/10/2010 10:26 PM, Peter Duniho wrote: > Knute Johnson wrote: >> [...] >> I have two classes that happen to be extensions of JPanels. Both of >> them have methods to input data that is then displayed (in various >> forms) on the other. The Observable/Observer appeared to be perfectly >> suited for this purpose. Then I got to thinking about which thread was >> being used and the fact that different threads could be >> writing/reading the data. [...] > > Why is that? Where is the data coming from? Both JPanels should be > executing on the EDT, assuming you haven't explicitly introduced a new > thread to them (and IMHO that would not generally be a good idea…GUI > logic belongs in the EDT, and non-GUI logic doesn't belong in a GUI > object). > > Pete The point is that you can't according to the docs, guarantee that Observable's call to update() is done on a particular thread. -- Knute Johnson email s/nospam/knute2010/
From: Knute Johnson on 11 Mar 2010 12:08 On 3/10/2010 10:14 PM, Peter Duniho wrote: > Knute Johnson wrote: >> On 3/10/2010 8:48 PM, Peter Duniho wrote: >>> If the mutable Object was thread-safe, then it would handle that. >>> Otherwise, you'd have to use some mechanism for ensuring >>> synchronization. As far as I know, Java doesn't have an explicit memory >>> barrier API, so "synchronized" is probably the best approach for simple >>> mutation observability issues. >>> >>> Pete >> >> And there's the rub. If you have as in my case, two separate classes, >> where you don't want to share data other than that which is shared via >> the Observable/Observer, how do you get the Observable instance in the >> Observer to then use to synchronize with? You have to assume that a >> different thread could read the data after it was stored via the >> Observer.update() method. > > Without a code example, it's hard for me to know for sure what you're > talking about. However, if you are asking how to get the reference to > the Observable instance so that you can use that reference in a > "synchronized" statement, then the answer is "you don't, nor should you". > > Unless you specifically want to write some special thread-safe > Observable sub-class that handles this, the synchronization isn't > something the Observable needs to know about at all, nor would it be > anything that needs the Observable instance itself. The synchronization > is between the threads modifying the data and reading the data, while > the Observable is just a by-stander. > > If you can provide a SSCCE that illustrates the relationship between > your Observable, an Observer, and whatever other code exits that is > modifying a mutable class instance, as well as examining that mutable > class instance in a different thread, then surely a solution can be > described that provides the necessary synchronization, but without > necessarily making the Observable a central figure in that. > > Note that, depending on how strict you want to be about this whole > "don't want to share data other than that which is shared via the > Observable/Observer", you may require a wrapper class to impose the > synchronization needed. > > But with an Observable that is not itself imposing a multi-threaded > implementation, it's not at all clear how you managed to get two classes > that know nothing of each other except for the Observable, and yet which > are going to access the same data on two different threads. Either each > class is associated with its own thread, and one knows about the other, > or only one class includes code executing on two different threads (e.g. > an Observer in one thread, and some other stuff in another), in which > case it only needs to synchronize within itself. > > Basically, until it's clear how it is you've got an Observable that > involves itself in cross-thread communication, in which two different > threads involve two completely mutually-isolated classes except for an > Observable mediating, and yet in which the Observable itself isn't > involved in the threading, it's very hard to suggest a design that will > work for you. > > After all, since the default Observable implementation simply calls > update() in the same thread where notifyObservers() was called, how is > it that the writer thread is different from the reader thread in the > first place? > > I suspect that once you've focused on the answer to that question, > you'll see the division between the two threads where the > synchronization has to take place, and it probably will be simple to > implement that synchronization independently of the Observable behavior. > > If you post a SSCCE, I'm sure you'll get good advice, if not from me > then from someone else. :) > > Pete Pete: Good points and I think I have figured out how to do this. I have to go to work this morning but I will post an SSCCE this evening. Thanks very much to you and markspace for all the comments, sometimes you need other perspectives to see clearly. -- Knute Johnson email s/nospam/knute2010/
From: markspace on 11 Mar 2010 12:40 Knute Johnson wrote: > I have two classes that happen to be extensions of JPanels. Both of > them have methods to input data that is then displayed (in various > forms) on the other. The Observable/Observer appeared to be perfectly > suited for this purpose. Then I got to thinking about which thread was > being used and the fact that different threads could be writing/reading > the data. OK, and sorry if I appear to be dense, but I really don't understand. You have to JPanels which need to "observe" each other (or some other object). So you make each JPanel with an extra "observable" interface: public class MyPanel extends JPanel { ... I can't actually use observable here because I can't extend both JPanel and Observable, but let's not dwell on that much, I don't think it matters. So what you end up with is something a bit like the ActionListener mechanism, which is just methods in the class public class MyPanel extends JPanel { ... public void addObserver( Observer o ) ... public void removeObserver( Observer o ) ... public void notifyObservers( Object message ) ... } Now you have two choices. You could add big Javadoc instructions to only use these methods on the EDT. JPanels aren't thread safe to begin with so maybe that wouldn't be so bad. However, as a design feature you decide to allow these messages to be called from any thread. There are a few Swing methods like that (setText() on the JTextComponent and subtypes) which makes that method very convenient to use, so this may be a good idea. I think that's where I'm at. Did I follow correctly so far? I can see two or three ways of implementing this, but it's complicated enough that I don't want to make a large example if I haven't got the right specification. I'm thinking that it largely depends on what kind of guarantees YOU want to make. That I think is the critical point. There's some kind of guarantee or design consideration that you want to make, and everything flows from there. Thread safety, and program correctness, just don't happen, you have to figure out how to make them happen. Anyway, I don't mean to rant. If I've understood it, I think I or anyone else could give some advice on how to do this, given a correct specification. Let me know if I've got it and I'll proceed from there.
From: Knute Johnson on 11 Mar 2010 14:52
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/ |