Java线程共享对象同步问题

3

我遇到了与Synchronized不符合我的预期的问题,我尝试使用volatile关键字:

共享对象:


public class ThreadValue {
private String caller;
private String value;
public ThreadValue( String caller, String value ) {
    this.value = value;
    this.caller = caller;
}

public synchronized String getValue() {
    return this.caller + "     "  + this.value;
}
public synchronized void setValue( String caller, String value ) {
    this.caller = caller;
    this.value = value;
}
}

线程 1:


class CongoThread implements Runnable {
    private ThreadValue v;
    public CongoThread(ThreadValue v) {
    this.v = v;

    }
    public void run() {
    for (int i = 0; i  10; i++) {
    v.setValue( "congo", "cool" );
    v.getValue();
    }
    }
}

线程 2:


class LibyaThread implements Runnable {
    private ThreadValue v;
    public LibyaThread(ThreadValue v) {
    this.v = v;

    }
    public void run() {
    for (int i = 0; i  10; i++) {
       v.setValue( "libya", "awesome" );
       System.out.println("In Libya Thread " + v.getValue() );

    }
    }
}

调用类:


class TwoThreadsTest {
    public static void main (String args[]) {

    ThreadValue v = new ThreadValue("", "");
        Thread congo = new Thread( new CongoThread( v ) );
        Thread libya = new Thread( new LibyaThread( v ) );

    libya.start();
        congo.start();

    }
}

偶尔会出现"In Libya Thread congo cool"这样的错误,这是不应该出现的。我期望的是: "In Libya Thread libya awesome" "In Congo Thread congo cool"。 我不希望它们混合在一起。

已经有多次回答了,这是重复的问题。请参考以下链接:http://stackoverflow.com/questions/1135502/java-synchronized-and-threads 和 https://dev59.com/sEjSa4cB1Zd3GeqPFXTT - Bombe
@Bombe 第二个“重复”的是这个问题本身。 - Zoey Hewll
3个回答

4

为什么它们不能混合使用?虽然每个单独的调用都是同步的,但没有任何方法可以阻止一个线程调用v.setValue,然后另一个线程调用setValue,然后第一个线程调用getValue()。我认为这就是发生的情况。您可以通过使用以下方式来避免这种情况:

public void run() {
    for (int i = 0; i  10; i++) {
       synchronized (v) {
           v.setValue( "libya", "awesome" );
           System.out.println("In Libya Thread " + v.getValue() );
       }
    }
}

因此,在每次迭代中,它确保调用setValue getValue,而不会有另一个线程在此期间调用setValue

诚然,这不是一种理想的设计 - 但我猜测这个演示更多地是为了理解同步,而不是其他方面 :)


1
通常锁定“真实”对象是不好的做法。使用单独的对象进行锁定,该对象仅用于锁定一个特定功能。 - Thirler
是的,我同意...但我希望这只是一个教学示例。 - Jon Skeet

2
发生的情况如下:
  1. 线程1设置值
  2. 线程2设置值
  3. 线程1读取由线程2设置的值。
为了解决这个问题,您需要有一个锁对象来保护两个线程的get/set函数调用。最好的方法是创建一个额外的同步方法,既可以进行设置又可以进行获取。但有时这并不理想。在这种情况下,给两个线程一个锁对象。这只是一个普通的对象。然后它们在同步块中使用。
每个线程的实现如下所示,请注意它们需要精确相同的对象!
Object lockObject = new Object();
Thread t1 = new CongroThread(v, lockObject);
Thread t2 = new LibyaThread(v, lockObject);

...

class CongoThread implements Runnable {
    private ThreadValue v;
    private Object lockObject;

    public CongoThread(ThreadValue v, Object lockObject) {
    this.v = v;
    this.lockObject = lockObject,
    }
    public void run() {
        for (int i = 0; i < 10; i++) {
            synchronized(lockObject)
            {
                v.setValue( "libya", "awesome" );
                System.out.println("In Libya Thread " + v.getValue() );
            }
        }
    }
}

0
问题是,可能会在刚刚刚果线程调用v.setValue()后立即调用利比亚线程的v.getValue(),从而导致混淆。
解决方案是让一个线程阻塞获取和设置值,否则你将继续遇到这个问题。你必须在setter内部调用getter或使用wait/notify让另一个线程等待,直到一个线程已经设置并获取了该值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接