如何在java并发中处理过时数据?

如何在java并发中处理过时数据?,java,concurrency,Java,Concurrency,为了详细了解Java并发,我正在阅读《实践中的Java并发》。然后我遇到了一个名为“过时数据”的术语 书中说:没有充分同步的程序会导致令人惊讶的结果 结果-->过时数据 书中给出了一个例子,它使用“synchronized”关键字保护mutator方法,并在字段上使用“@GuardedBy”注释。我想测试一下 import net.jcip.annotations.*; public class ThreadTest4 extends Thread{ private MyWor

为了详细了解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++;
    }
    
}
但它的结果仍然让我惊讶!原因可能是什么

您有两个问题

首先,您要锁定的
myWork
对象对每个线程都是私有的,因此当调用试图修改静态int的
setA()
时,线程只会相互锁定,而静态int需要锁定第一个对象,以便允许值发生更改。因此,除了Thread1之外,
getA()
调用都不依赖于等待锁

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

然后,当Thread1释放锁时,执行Thread2的
setA()
调用。Thread2在增加值后释放锁,然后用自己的
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()
调用,这让它们玩得更愉快

@user2864740您的意思是调用setA()和getA()应该在同步块内吗。?我想这就是你所说的原子同步(锁)在run()中工作的意思。但是为什么我们需要一个外部锁呢。为什么当我们使用“myWork”对象的内部锁时它不工作!?。我认为它应该阻止多个线程访问“myWork”方法。他们无法看到彼此的实例,因此无法使用它们正确排序。如果它是静态的,那么不同的线程可以共享它并使用它进行有序锁定,但是由于它们都是私有的,所以它们基本上不会以它们应该的方式相互阻止。好的。。有道理。因此,如果我使用MyWork4的静态实例,它可以用作锁。让我试试。如果我错了,请纠正我。酷!!。。。成功了。我忘记了“我的工作”不是共享的,因为它对每个线程都是私有的。嘿,我想即使我从每个方法中删除'synchronized'关键字,这也会起作用。我注意到,即使我没有在MyWOrk4类上保持'synchronized'和'@guardeby',你的建议也会起作用。