我正在阅读“实践中的Java并发性”一书,以详细了解Java并发性。然后我遇到了一个叫做“陈旧数据”的术语。


  书中说:程序同步不足可能会引起意外
  结果->过时的数据。


书中提供了一个示例,该示例使用'synchronized'关键字保护mutator方法,并在其字段上使用'@GuardedBy'注释。我想测试一下。

import net.jcip.annotations.*;
public class ThreadTest4 extends Thread{

    private MyWork4 myWork= new MyWork4();

    public static void main(String[] args){
        ThreadTest4 thread1 = new ThreadTest4();
        ThreadTest4 thread2 = new ThreadTest4();
        ThreadTest4 thread3 = new ThreadTest4();
        ThreadTest4 thread4 = new ThreadTest4();

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();

    }

    public void run(){
            myWork.setA();
            System.out.println(myWork.getA());
    }
}

class MyWork4{

    @GuardedBy("this")  private static int a;

    public synchronized int getA(){
        return a;
    }

    public synchronized void setA(){
        a++;
    }

}


但是它的结果仍然让我感到惊讶!可能是什么原因?

java - 如何在Java并发中处理过时的数据?-LMLPHP

最佳答案

你有两个问题。

首先,您要锁定的myWork对象是每个线程的私有对象,因此线程仅在调用setA()试图修改静态int时才相互锁定,这需要锁定FIRST OBJECT in为了允许值更改。因此,除了Thread1之外,没有getA()调用依赖于等待锁。

由此产生的第二个问题是您的set和get调用重叠。 @GuardedBy防止该项目被不带锁的项目修改,并且同步方法只能由拥有该锁的调用方调用。所有线程都注册其setA()调用,但必须等待锁修改值。 Thread1从锁开始,然后修改该值,然后释放该锁,然后通过其getA()调用再次请求它。

然后,当Thread1释放锁时,将执行Thread2的setA()调用。线程2在增加值后释放锁,然后使用自己的getA()调用注册其对锁的请求。

Thread1现在重新获得锁以执行其等待的getA()调用,并打印出2,因为此时Thread1和Thread2已经修改了该值。

Thread3接下来获取锁并执行其等待的setA()调用,然后再次增大该值并释放该锁,然后注册其getA()调用。

然后,线程2将锁取回以等待其等待的getA()调用,并打印出3并释放该锁。

线程3的等待getA()调用接下来发生,并再次打印出3,因为还没有发生其他setter调用。

最后,最先启动的Thread4开始运行,并注册递增值的setA()调用,然后打印出新递增的getA()调用,因为它不再等待锁。

您的run方法是不同步的,并且您的各个线程没有什么要订购的,除非首先请求锁定,这取决于足够多的不同因素,基本上是随机的。

这是一个修改,可以使您的订单更加可预测:

public class ThreadTest4 extends Thread {
    static Object lock = new Object[0];
    private MyWork4 myWork = new MyWork4();

    public static void main(String[] args) {
        ThreadTest4 thread1 = new ThreadTest4();
        ThreadTest4 thread2 = new ThreadTest4();
        ThreadTest4 thread3 = new ThreadTest4();
        ThreadTest4 thread4 = new ThreadTest4();

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }

    public void run() {
        synchronized(lock){
            myWork.setA();
            System.out.println(myWork.getA());
        }
    }
}

class MyWork4 {

    @GuardedBy("lock")
    private static int a;

    public synchronized int getA() {
        return a;
    }

    public synchronized void setA() {
        a++;
    }

}


这是可行的,因为锁是外部的,并且在线程之间显式共享。所有线程都使用相同的锁,因此它们在下一个线程被赋予锁之前按顺序执行setA()调用和getA()调用,这使它们发挥得更好。

10-04 18:23