为什么 synchronized 不能正常工作?

Why is synchronized not working properly?

这是我的代码:

private int count = 0;

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

 public void doWork() throws InterruptedException {

    Thread t1 = new Thread(new Runnable() {
        public void run() {
            for (int i = 0; i < 5; i++) {
                increment();
                System.out.println(count+"  "+Thread.currentThread().getName());
            }}});

    Thread t2 = new Thread(new Runnable() {
        public void run() {
            for (int i = 0; i < 5; i++) {
                increment();
                System.out.println(count+"  "+Thread.currentThread().getName());
            }}});

    t1.start();
    t2.start();
}

这是我的输出:

2  Thread-1
2  Thread-0
3  Thread-1
5  Thread-1
6  Thread-1
4  Thread-0
8  Thread-0
9  Thread-0
7  Thread-1
10  Thread-0

我的理解是incrementsynchronized。所以,它应该先increment一个数然后释放lock然后把lock给线程t1t2。所以,它应该 increment 一次一个数字,对吧?

但为什么我的代码 incrementing 一次有两个或三个数字?我做错了什么吗(我是新手)?

虽然 count++; 确实是同步的 System.out.println(count+" "+Thread.currentThread().getName()); 不是,但它访问 count 变量。

即使您同步访问,也无济于事,因为下一种情况仍有可能发生:

  • 线程 1 增量
  • 线程 2 增量
  • 线程 1 打印值 2
  • 线程 2 打印值 2

要解决此问题,您需要在同一同步部分中递增和打印。例如,您可以将 System.out.println(count+" "+Thread.currentThread().getName()); 放入 increment 方法中。

实际发生的是您的线程正在快照(也许这里换个词更好)变量 count 当前 值并显示它。你可以把它想象成有一个数字为零的蓝色桶,并且 Threads 都得到相同颜色和数字的同一个桶。他们现在单独处理这些桶。

如果你想让它们在同一个桶上工作,你必须让它们成为原子,例如使用 AtomicIntegervolatile 或 java 并发包中的任何其他工具。

increment 方法可以在 increment 方法 returns 之后的另一个线程上 运行,但在 count 检索到连接之前

count+"  "+Thread.currentThread().getName()

你可以,例如通过在一个同步块中修改和检索 count 来解决此问题:

public synchronized int incrementAndGet() {
    count++;
    return count; // read access synchronized
}
for (int i = 0; i < 5; i++) {
    System.out.println(incrementAndGet()+"  "+Thread.currentThread().getName());
}

或使用the class in the standard library specifically designed for this purpose:

private final AtomicInteger counter = new AtomicInteger(0);

public void doWork() throws InterruptedException {

    Thread t1 = new Thread(new Runnable() {
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(counter.incrementAndGet() + "  " + Thread.currentThread().getName());
            }
        }
    });

    Thread t2 = new Thread(new Runnable() {
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(counter.incrementAndGet() + "  " + Thread.currentThread().getName());
            }
        }
    });

    t1.start();
    t2.start();
}

当然这并不一定会导致数字1到10按顺序打印,只是没有一个数字被检索到超过一次。可能会发生以下输出:

2  Thread-0
3  Thread-0
4  Thread-0
1  Thread-1
5  Thread-0
6  Thread-1
7  Thread-0
8  Thread-1
9  Thread-1
10  Thread-1

解决方案 1: fabian 给出。给单函数incrementAndGet().

解决方案2:一个synchronized块而不是synchronized方法(如果可能的话):

完整代码如下:

private int count = 0;
private Object dummyObject = new Object();

public void increment() {
    count++;
}

public int getCount() {
    return count;
}

public void doWork() throws InterruptedException {

    Thread t1 = new Thread(new Runnable() {
        public void run() {
            for (int i = 0; i < 5; i++) {
                synchronized (dummyObject) {
                    increment();
                    System.out.println(count + "  " + Thread.currentThread().getName());
                }
            }
        }
    });

    Thread t2 = new Thread(new Runnable() {
        public void run() {
            for (int i = 0; i < 5; i++) {
                synchronized (dummyObject) {
                    increment();
                    System.out.println(count + "  " + Thread.currentThread().getName());
                }
            }
        }
    });

    t1.start();
    t2.start();
}

不使用 synchronized 的替代解决方案。

由于您的用例很简单(只需增加计数器并打印值,AtomicInteger 是更好的选择。

import java.util.concurrent.atomic.AtomicInteger;

public class TestCounter{
    private AtomicInteger count = new AtomicInteger(0);

    public void doWork() throws InterruptedException {

        Thread t1 = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(""+Thread.currentThread().getName()+":"+count.incrementAndGet());
                }}});

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(""+Thread.currentThread().getName()+":"+count.incrementAndGet());
                }}});

        t1.start();
        t2.start();
    }

    public static void main(String args[]) throws Exception{
        TestCounter tc = new TestCounter();
        tc.doWork();
    }
}

输出:

Thread-0:1
Thread-0:3
Thread-0:4
Thread-0:5
Thread-0:6
Thread-1:2
Thread-1:7
Thread-1:8
Thread-1:9
Thread-1:10

请参考@fabian 的回答,了解为什么这些数字不按顺序打印。

如果您希望从 1-10 升序排列的一系列数字序列,则不需要线程。