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
为什么?
答案 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
。同时,同步部分仍然引用旧对象。因此每个线程都有可能与不同的对象达到同步块。
您应该使用在进程发生时不指向不同对象的变量。