【问题标题】:Why are my threads giving me this output when accessing a synchronized method?为什么我的线程在访问同步方法时会给我这个输出?
【发布时间】:2018-08-02 20:58:04
【问题描述】:

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

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()调用和println()调用之间可能会有延迟,在此期间其他线程也会调用getCount()println()。如果我编写这个代码,我将只有incrementCount(int delta),它返回count 的当前值,并且可以传递0 的增量。

标签: java multithreading concurrency synchronization synchronized


【解决方案1】:

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

例如,如果线程 2 在他之前看到计数等于 2 并增加它,线程 3 怎么能看到计数等于 1?

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

  1. Thread-3 调用 getCount() 并返回 1。
  2. Thread-1 调用 incrementCount()
  3. Thread-2 调用 getCount() 并返回 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”

【讨论】:

  • 让我更好地理解我的结果,感谢您的宝贵时间!
【解决方案2】:

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

所以你可以拥有的就是这样

  • 线程 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

【讨论】:

  • 非常感谢!确实,我没有考虑打印时间,现在很有意义!
【解决方案3】:

在您的SharedObject 中,单独的getCountincrementCount 方法是同步的,但是没有什么可以阻止所有三个线程在调用incrementCount 之前调用getCount(一次一个)。然后在每次睡觉后再次。这就是您的第一个输出显示的内容。

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

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

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

                sharedObject.incrementCount();
            }

每次线程进入synchronized 块时,它都会获取计数、打印并递增计数,而其他任何线程都不会在两者之间做任何这些事情。

【讨论】:

  • 非常感谢您的解释!尝试了您的建议并按预期工作:)!
猜你喜欢
  • 2021-04-06
  • 2015-03-19
  • 1970-01-01
  • 1970-01-01
  • 2021-11-12
  • 2011-07-10
  • 1970-01-01
  • 1970-01-01
  • 2020-08-24
相关资源
最近更新 更多