为什么我的线程在访问同步方法时给我这个输出?

Why are my threads giving me this output when accessing a synchronized method?

我正在尝试多线程和同步,所以我创建了这个在所有线程之间共享的简单对象:

public class SharedObject {

    private int count = 0;

    public synchronized int getCount(){
        return count;
    }

    public synchronized void incrementCount(){
        count++;
    }
}

并且它被 3 个线程以这种方式访问​​:

public static void main(String[] args) throws Exception {


    SharedObject sharedObject = new SharedObject();
    ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(3);

    Runnable task = () -> {
        for(int i = 0; i < 10; i++){

            System.out.println("Thread : " + Thread.currentThread().getName() 
            + " count : " + sharedObject.getCount());

            sharedObject.incrementCount();

            try{
                Thread.currentThread().sleep(2000);
            }
            catch (Exception e){}
        }
    };

    executor.submit(task);
    executor.submit(task);
    executor.submit(task);

    executor.shutdown();
    executor.awaitTermination(1, TimeUnit.HOURS);

    System.out.println("Final : " + sharedObject.getCount());
}

我的输出如下:

Thread : pool-1-thread-2 count : 0
Thread : pool-1-thread-1 count : 0
Thread : pool-1-thread-3 count : 0
Thread : pool-1-thread-3 count : 3
Thread : pool-1-thread-2 count : 3
Thread : pool-1-thread-1 count : 3
Thread : pool-1-thread-2 count : 6
Thread : pool-1-thread-1 count : 6
Thread : pool-1-thread-3 count : 6
...

如果我的理解是正确的(如果我错了请纠正我),这是因为:

  1. 第一个线程调用getCount(),得到方法上的锁,他一打印计数值,就释放锁,然后第二个线程获取到调用getCount(),以及最后一个线程

  2. 当所有 3 个线程都完成调用 getCount() 时,每个线程现在都在调用 incrementCount(),并且由于该方法是同步的,每个线程在递增计数,这解释了为什么我们在输出中看到 +3 的跳跃

  3. 一个线程一结束,它就自己调用sleep(2000),但是由于调用速度太快,好像三个线程同时开始和停止休眠

但是,当我删除sleep(2000)时,我得到以下输出:

Thread : pool-1-thread-3 count : 0
Thread : pool-1-thread-2 count : 0
Thread : pool-1-thread-1 count : 0
Thread : pool-1-thread-2 count : 2
Thread : pool-1-thread-3 count : 1
Thread : pool-1-thread-2 count : 4
Thread : pool-1-thread-1 count : 3
Thread : pool-1-thread-2 count : 6
Thread : pool-1-thread-3 count : 5
Thread : pool-1-thread-2 count : 8
Thread : pool-1-thread-1 count : 7
Thread : pool-1-thread-2 count : 10
Thread : pool-1-thread-3 count : 9
Thread : pool-1-thread-2 count : 12
Thread : pool-1-thread-1 count : 11
Thread : pool-1-thread-2 count : 14

而且我不明白这是怎么发生的。例如,如果 thread-2 在他之前看到它等于 2,那么 thread-3 如何看到计数等于 1并增加它?

任何解释都将不胜感激,以帮助我更好地理解多线程同步环境中的 Java。谢谢你的时间。

请记住,屏幕上的输出可能与 getCount()/incrementCount() 的执行顺序没有任何关系,打印输出的代码没有锁定。

For example, how can thread-3 see the count being equal to 1 if thread-2 saw it equal to 2 before him and incremented it ?

如果您有以下执行顺序,则会出现此输出:

  1. Thread-3 调用 getCount() 并且它 returns 1.
  2. 线程 1 调用 incrementCount()
  3. Thread-2 调用 getCount() 并且它 returns 2.
  4. Thread-2 调用 System.out.println 打印:"pool-1-thread-2 count : 2"
  5. Thread-3 调用 System.out.println 打印:"pool-1-thread-3 count : 1"

仅仅因为一个线程在另一个线程之前读取一个值并不意味着它会在另一个线程之前打印它。 Yo 需要在同步块内以原子方式完成读取和打印才能获得该保证。

那么你能拥有的就是这样

  • 线程3读取并打印值(0): 线程:pool-1-thread-3 计数:0

  • 线程2读取并打印值(0): 线程:pool-1-thread-2 计数:0

  • 线程1读取并打印值(0): 线程:pool-1-thread-1 计数:0

  • 线程3递增值(1)

  • 线程3读取值(1)
  • 线程2递增值(2)
  • 线程 2 读取并打印值 (2): 线程:pool-1-thread-2 计数:2
  • 线程 3 打印它之前读取的值: 线程:pool-1-thread-3 计数:1

在您的 SharedObject 中,单独的 getCountincrementCount 方法是同步的,但没有什么能阻止所有三个线程在之前调用 getCount(一次一个)他们中的任何一个打电话给 incrementCount。然后在每次睡眠后再次。这就是您的第一个输出显示的内容。

如果没有 sleep(),一个线程甚至可能在一个或多个其他线程调用 incrementCount() 之前多次调用 getCount()从技术上讲即使在睡眠中也没有被禁止。同样,一个线程可以在另一个线程获取和打印时获取、递增和打印 getween。这些排序解释了为什么没有 sleep 的输出不是顺序的。

如果你想看到没有跳过的顺序输出,那么你需要范围更广的同步。例如,在您的任务实现中,您可以使用 synchronized 块:

            synchronized (sharedObject) {
                System.out.println("Thread : " + Thread.currentThread().getName() 
                        + " count : " + sharedObject.getCount());

                sharedObject.incrementCount();
            }

每次线程进入 synchronized 块时,它都会获取计数、打印它并递增它,而其他线程不会在其间执行任何这些操作。