为什么同步无法正常工作?

时间:2016-09-28 13:28:26

标签: java multithreading synchronized

这是我的代码:

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一次有两个或三个数字?我做错了什么(我是新手)?

5 个答案:

答案 0 :(得分:5)

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

即使您同步访问权限,它也无济于事,因为下一个方案仍然可能:

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

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

答案 1 :(得分:1)

increment方法可以在increment方法返回后在另一个线程上运行,但在为{{p>}检索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

答案 2 :(得分:0)

实际发生的是你的线程正在快照(可能是另一个词在这里更好)变量count当前值并显示它。您可以将其视为有一个数字为零的蓝色桶,并且Threads都以相同的颜色和数字获得相同的桶。他们现在可以在这些桶上单独工作。

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

答案 3 :(得分:0)

解决方案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();
}

答案 4 :(得分:0)

一种不使用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开始按升序排列,则不需要线程。