【问题标题】:Why is synchronized not working properly?为什么同步不能正常工作?
【发布时间】:2017-02-06 12:28:24
【问题描述】:

这是我的代码:

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

【问题讨论】:

  • incrementsynchronized,但 count 不是。 increment()System.out.println() 这两个语句不是原子的。

标签: java multithreading synchronized


【解决方案1】:

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

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

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

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

【讨论】:

  • 或者使count volatile。
  • @chrylis 它会解决第一个问题,但不能解决第二个问题。
  • @chrylis 将 count 标记为 volatile 不会阻止线程 2 在线程 1 打印之前递增。
【解决方案2】:

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

如果您希望它们在同一个存储桶上工作,则必须使它们具有原子性,例如使用 AtomicIntegervolatile 或 java concurrent 包中的任何其他工具。

【讨论】:

    【解决方案3】:

    increment 方法可以在 increment 方法返回之后,但在为连接检索到 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
    

    【讨论】:

    • @Hemlata 哦,它确实有效,但是如果不明确执行此操作,您不能期望打印语句也会同步,并且您没有在问题中提到这样的限制。 (没有数字出现不止一次,没有数字丢失,数字在每个线程上按升序打印。)此外,在这种情况下使用多个线程没有意义......
    • 也许我做错了什么,这就是为什么它不起作用,因为我是新手。
    【解决方案4】:

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

    【讨论】:

    • 这无济于事,因为在调用 increment 和调用 getCount 之间仍然可以更改值,因为在这种情况下需要两次获取监视器。
    【解决方案5】:

    不使用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 的数字序列中按升序排列,则不需要线程。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-11-17
      • 2016-07-16
      • 2019-01-04
      • 2020-09-03
      • 2016-10-10
      • 2016-10-24
      • 2017-02-27
      • 2017-07-08
      相关资源
      最近更新 更多