访问同步方法时,为什么我的线程会向我提供此输出?

时间:2018-08-02 20:58:05

标签: java multithreading concurrency synchronization synchronized

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

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(),获得方法的锁,并且他在打印计数值后立即释放该锁,第二个线程获取该锁以调用{{1} },并从最后一个线程开始

  2. 当所有3个线程都完成调用getCount()时,它们中的每个都正在调用getCount(),并且由于该方法已同步,因此每个线程在增加计数之前都会看到更新的值,这说明为什么我们在输出中看到+3的跳动

  3. 一旦线程结束,它就会自动调用incrementCount(),但是由于调用速度如此之快,因此似乎三个线程同时启动和停止进入睡眠状态

但是,当我删除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 在他之前看到计数等于 2 并递增计数,怎么能看到计数等于 1

请多加解释以帮助我更好地理解多线程同步环境中的Java。谢谢您的宝贵时间。

3 个答案:

答案 0 :(得分:4)

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

  

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

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

  1. 线程3调用getCount()并返回1。
  2. 线程1调用crementCount()
  3. 线程2调用getCount()并返回2。
  4. 线程2调用System.out.println打印:“ pool-1-thread-2 count:2”
  5. 线程3调用System.out.println进行打印:“ pool-1-thread-3 count:1”

答案 1 :(得分:4)

仅仅因为一个线程在另一个值之前读取一个值并不意味着它将在另一个值之前打印它。为了保证这一点,您需要在同步块内自动完成读取和打印。

因此,您可以拥有的是

  • 线程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 count:2
  • 线程3打印之前已读取的值: 线程:pool-1-thread-3计数:1

答案 2 :(得分:3)

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

在没有sleep()的情况下,一个线程可以多次调用getCount(),而另一个线程甚至可以一次调用incrementCount()从技术上讲即使睡觉也没有禁止。同样,一个线程在另一个线程获取和打印时获取,递增和打印获取线程是可能的。这些排序方式说明了为什么没有sleep的输出不是连续的。

如果您希望看到连续输出而不跳过,那么您需要更广泛的同步。例如,在您的任务实现中,您可以使用synchronized块:

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

                sharedObject.incrementCount();
            }

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