首页 > 解决方案 > Java同步计数器相同的值由不同的线程打印

问题描述

我正在使用同步方法用 Java 编写同步计数器。代码如下

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class Counter implements Runnable {
    private static int counter = 0;
    private static final int limit = 1000;
    private static final int threadPoolSize = 5;

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
        for (int i = 0; i < threadPoolSize; i++) {
            executorService.submit(new Counter());
        }
        executorService.shutdown();
    }

    @Override
    public void run() {
        incrementCounter();
    }

    private synchronized void incrementCounter() {
        while (counter < limit) {
            System.out.println(Thread.currentThread().getName() + " : " + counter);
            counter++;
        }
    }
}

代码工作正常。但在某些时候,两个线程打印相同的数字,

pool-1-thread-2 : 29
pool-1-thread-2 : 30
pool-1-thread-1 : 30
pool-1-thread-1 : 32

如上面的输出,线程 2 和 1 都在打印30。我不知道为什么会这样。任何帮助表示赞赏

标签: javamultithreadingconcurrency

解决方案


当您使用一个synchronized方法时,执行该方法的线程将获得Counter该类的当前实例的锁,因此其他线程可以同时增加该counter值而不受任何抑制。

这是因为您Counter在循环中创建了五个不同的类实例:

 for (int i = 0; i < threadPoolSize; i++) {
    executorService.submit(new Counter());
 }

因此,有时五个线程可以同时执行该incrementCounter()方法,因为它们正在锁定 Counter 类的五个不同实例(当您使用一个synchonized方法时,您实际上是锁定在this当前类实例上)。

您可以创建一个必须启用的新static 对象,synchronized以便池中的所有线程都拥有并共享相同的公共锁对象:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

class Counter implements Runnable {
    private static AtomicInteger counter = new AtomicInteger(0);
    private static final Object lock = new Object();
    private static final int limit = 1000;
    private static final int threadPoolSize = 5;

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
        for (int i = 0; i < threadPoolSize; i++) {
            executorService.submit(new Counter());
        }
        executorService.shutdown();
    }

    @Override
    public void run() {
        incrementCounter();
    }

    private void incrementCounter() {
        synchronized (lock) {
            while (counter.get() < limit) {
                System.out.println(Thread.currentThread().getName() + " : " + counter.get());
                counter.incrementAndGet();
            }
        }
    }
}

此外,由于这是您正在执行读取-更新-写入操作的情况,您应该考虑使用AtomicInteger代替int原语来使操作原子化,即操作将在单个事务中发生,并且其他线程始终可以看到最新值.


推荐阅读