为什么volatile和synchronized语句不能避免线程干扰?

时间:2014-09-19 02:11:29

标签: java multithreading concurrency

package simple;

public class ThreadInterference {
    public static volatile Integer count = 1000;

    public static class MyThread implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 10000; i++) {
                count++;
                count--;
                count++;
                count--;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println(count);

        Thread t1 = new Thread(new MyThread());
        Thread t2 = new Thread(new MyThread());
        Thread t3 = new Thread(new MyThread());
        Thread t4 = new Thread(new MyThread());

        t1.start();
        t2.start();
        t3.start();
        t4.start();

        t1.join();
        t2.join();
        t3.join();
        t4.join();

        System.out.println(count);

    }

}

count变量标记为volatile,但输出为:

1000
1230

如果我更改为synchronized语句,则还会发生线程干扰:

package simple;

public class ThreadInterference {
    public static Integer count = 1000;

    public static class MyThread implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 10000; i++) {
                synchronized(count) {
                    count++;
                    count--;
                    count++;
                    count--;
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println(count);

        Thread t1 = new Thread(new MyThread());
        Thread t2 = new Thread(new MyThread());
        Thread t3 = new Thread(new MyThread());
        Thread t4 = new Thread(new MyThread());

        t1.start();
        t2.start();
        t3.start();
        t4.start();

        t1.join();
        t2.join();
        t3.join();
        t4.join();

        System.out.println(count);

    }

}

这次的输出是:

1000
1008

为什么?

4 个答案:

答案 0 :(得分:3)

我相信发生的事情就是这样:

count++尝试增加Integer,这是不可变的。编译器必须创建一个新的Integer并将其称为&#34; count&#34;。然后,对前一个计数的同步锁定会出现意外情况。非常聪明的人可能会解决这个问题......

将计数更改为原始int并同步其他内容以查看发生的情况。或者使用AtomicInteger。

除非你确定,否则不要增加整数!

答案 1 :(得分:3)

正如其他人所说,你有两个不同的问题。第一个是count++(和count--)不是原子操作,即使对于原始int类型也是如此。因此,如果没有某种锁定或其他并发处理,这将无法工作。对于count++,其中count是int,编译器会生成类似以下字节代码指令的内容:

    getstatic count
    iconst_1
    iadd
    putstatic count

即使编译为本机代码,这也不可能是原子的。

第二个问题是您没有锁定一致的对象,因此操作不会被序列化。代码:

   count++; // "count" is an "Integer" object type.

创建一个新对象。从本质上讲,它具有以下特点:

   count = Integer.valueOf(count.intValue() + 1);

因此,您的count对象将被新对象替换,随后进入synchronized部分将同步另一个对象。

作为安全提示,如果您使用的是synchronized (someObject),其中someObject是一个类或实例字段,那么创建该字段final是一个好主意。这样就无法将其无意中重新分配给不同的值。

我能想到的问题有两种直接的解决方案。一个锁定用于锁定的特定对象,如下所示:

public class ThreadInterference {
  public static final Object COUNT_LOCK = new Object();
  public static int count = 1000; // Either "int" or "Integer" OK, depending on need

  public static class MyThread implements Runnable {
    @Override
    public void run() {
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      for (int i = 0; i < 10000; i++) {
        synchronized(COUNT_LOCK) {
          count++;
          count--;
          count++;
          count--;
        }
      }
    }
  }

  // And so on...
}

另一种选择是使用AtomicInteger,这可能会提供更好的并发性能,如果这很重要:

public class ThreadInterference {
  public static AtomicInteger count = new AtomicInteger(1000);

  public static class MyThread implements Runnable {
    @Override
    public void run() {
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      for (int i = 0; i < 10000; i++) {
        count.incrementAndGet();
        count.decrementAndGet();
        count.incrementAndGet();
        count.decrementAndGet();
      }
    }
  }

  // And so on...
}

答案 2 :(得分:0)

而不是同步计数,

尝试执行自己同步的增量/减量方法...

public static synchronized decrementCount()
{
    count--;
}

public static synchronized incrementCount()
{
    count++;
}

然后调用它们而不是直接递减/递增。

另外,如果你只做一个防止线程干扰的计数器而不求助于同步,你应该使用原子值。

无论如何,请查看此文档,其中显示了如何使用同步方法或原子值执行同步计数器:http://docs.oracle.com/javase/tutorial/essential/concurrency/atomicvars.html

答案 3 :(得分:0)

当你这样做时,你做错了。

        public static volatile Integer count = 1000;
        for (int i = 0; i < 10000; i++) {
            synchronized(count) {
                count++;
                count--;
                count++;
                count--;
            }
        }

整数是不可变的。这意味着,当您执行count++时,它会创建一个新的Integer对象,并为该新对象分配count。同时,同步部分仍然引用旧对象。因此每个线程都有可能与不同的对象达到同步块。 您应该使用在进程发生时不指向不同对象的变量。