i++
线程不安全原因
- 多个线程同时访问共享变量
i
,而JVM允许每个线程存储变量的副本,i++
的操作可以分为三步: 取值、自增、写回。存在一个线程在 自增 时,刚好有线程在 取值,因此最后会出现i
增加的结果总比预计的结果线程小。
- 测试例:
class TestIPlus {
private int val = 0;
public void run() {
for(int i=0; i<10; i++) {
this.val = 0;
final CountDownLatch count = new CountDownLatch(10000);
for(int j=0; j<100; j++) {
new Thread(){
@Override
public void run() {
for(int i=0; i<100; i++) {
TestIPlus.this.val++;
count.countDown();
}
}
}.start();
}
try {
count.await();
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.val);
}
}
}
使i++
变得线程安全有3种方式:
- 使用
synchronized
关键字,将i++
写成一个方法,并使用synchronized
修饰
public synchronized void incI() {
this.i++;
}
private Lock lock = new ReentrantLock();
public void incI() {
lock.lock();
try {
i++;
} finally {
lock.unlock();
}
}
class TestIPlus {
private AtomicInteger val;
public void run() {
for(int i=0; i<10; i++) {
this.val = new AtomicInteger(0);
final CountDownLatch count = new CountDownLatch(10000);
for(int j=0; j<100; j++) {
new Thread() {
@Override
public void run() {
for(int i=0; i<100; i++) {
TestIPlus.this.val.getAndIncrement();
count.countDown();
}
}
}.start();
}
try {
count.await();
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.val);
}
}
}