简体   繁体   中英

Access variable from another class from a thread

At the moment a key event is generated in the GUI which is then passed through several classes and given to a class which is running in a separate thread. The thread is waiting for a key event and when one is received a variable from further up the class chain is altered (see diagram). However during debug the variable is not changing.

The class that the thread is accessing is of course in its own thread as it is being called from the GUI which has led me to think that this is a problem with concurrency.

Is there a way to solve this perhaps using atomic integers or locks? I've seen a few examples using the synchronized functions however I can't get them to work as they don't explain the requirements of the classes. (by which I mean they give you the code to do the synchronization but they do not explain how to make a class "synchronisable").

类结构图 Here is the code from the Thread in class E, as you can the the thread's object reference is set from the class above which receives a reference of Class A from the class above etc.

    private Processor processor;

    public void run() {
        while (true) {
            if (keyevent != null) {


                keyevent = null;
                processor.I = 4;
            }
        }
    }

    public void SetProcessor(Processor processor) {
        this.processor = processor;
    }

An expansion on the debugging comment. During debug if I only debug the thread in class E and step through it the code functions fine and the processor.I receives the value of four. However when i am not debugging that thread nothing happens in the processor which is why I thought it might be a concurrency problem.

Made the varaible that I am accessing in Class B and Atomic Integer, also made some of the functions used synchronized as well. Still dosent function outside of debug environment :(

Code in Class B called from Class E

    public void SetI(int value){//also tried using synchronized as well
        I.set(value);
    }

The KeyEvent is generated in the GUI class by a keyListener (which fires whenver a key is pressed). The KeyEvent object is then passed to class E via several "trickle down" functions that simply pass it onto the next class, so the GUI calls processor.setKeyevent(e), the processor then call bus.setKeyevent(e) and so on a so forth until the KeyEvent property is set in Class E.

Upon initialization of the system the thread in class E is started, is constantly checks the value of the Keyevent property, once the KeyEvent is not null ie it has been passed one from the GUI (via everything else) Class E then sets the value of an integer property in class B.

What is happening is that when a key is pressed nothing happens what should be happening is that the integer is class B should be changing because of Class E however it is not. As net beans does not allow me to debug two threads at once its makes it a bit awkward, when I put breakpoints in the code outside of the thread in class E, it does not work, its as if the thread is not running or as if its not receiving the keyevent, if i put breakpoints in the thread and not outside it works, the value of I in class B is changed. If it is run outside of debug it dose not work :/

Class E should not be directly manipulating data members in class B. That is all kinds of bad. But that isn't really the point of your problem.

In order for the GUI thread in B to see the changes made by the thread in E, you need to use some kind of synchronization control. Normally I would suggest using an AtomicInteger , however you mentioned some swing stuff, so I'm assuming class B is actually a Swing component. In that case, I find it cleaner to keep the swing stuff on the EDT and make it E's responsibility to call B on the EDT.

Here's what I mean. I've eliminated class C and D since they are just pass-through anyway. I'm also ignoring the construction/setup and starting of the thread. I've removed your busy-loop in E and replaced it with a CountDownLatch .

/**
 * Some GUI class, should only be accessed from the EDT
 */
public class B extends JPanel {

    private int value = 0;

    private E thatThreadObject;

    public void setE( E e ) {
        thatThreadObject = e;
    }    
    public void setValue( int newValue ) {
        value = newValue;
        System.out.println( "Got a new int value: " + value );
    }

    public void triggerKeyEvent() {
        thatThreadObject.keyEvent();
    }
}

/**
 * Must be thread-safe, as accessed from multiple threads
 */
public class E implements Runnable{
    private B thatGuiObject;
    // Note, latch is only good for one-time use.
    private final CountDownLatch latch = new CountDownLatch( 1 );

    public void setB( B b ) {
        thatGuiObject = b;
    }

    public void keyEvent() {
        // Wake up the waiting thread
        latch.countDown();            
    }

    @Override
    public void run() {
        try {
            // Wait for key event forever, better than busy looping
            latch.await();
            // Update B, but it's a Swing component so use EDT
            EventQueue.invokeLater( new Runnable() {
                @Override
                public void run() {
                    thatGuiObject.setValue( 4 );
                }
            } );
        }
        catch ( InterruptedException e ) {
            e.printStackTrace();
        }            
    }
}

Have a look at the Java Concurrency Tutorial

It's hard to tell without some code, but you may want to take a look at java.util.concurrent. A safe way to send messages among threads is to use BlockingQueues.

http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/BlockingQueue.html

The Java API page in that link provides a good code example.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM