1.线程不安全现象
创建测试类和库存类如下,在测试类中启动两个线程,分别增加10000个库存和减少10000个库存。
public class Test {
public static void main(String[] args) {
Inventory inventory = new Inventory(0);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
inventory.increment();
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
inventory.decrement();
}
}, "t2");
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("test result vaule:" + inventory.get());
}
}
}
/**
* thread unsafe
*
*/
class Inventory {
private int count;
public Inventory(int count) {
this.count = count;
}
public void increment() {
count++;
}
public void decrement() {
count--;
}
public int get() {
return count;
}
}
程序运行预期结果应该是0,但实际结果可能出现0,正数,负数。
2.原因
使用javap查看Inventory.class对应的字节码,可以看到count++并非原子操作,而是由多个指令构成,如下:
getfield //获得成员变量
iconst_1 //准备常量1
iadd //执行加法操作
putfield //将计算后的结果写回成员变量
count--类似,同样包含取值,准备常量1,执行减法,回写值这几个指令。
当多线程读写count值时候,可能出现指令交错,具体情况如下(引用第三方图片,读者将图中i当作count即可):
或者
这也就解释了上述代码执行结果为何会出现正数,负数。
3.解决方案
加锁(悲观锁,避免出现指令交错的情况),将increment,decrement操作上锁,当前线程未执行完释放锁,其他线程无法执行。
/**
* thread safe
*
*/
class Inventory1 {
private int count;
public Inventory1(int count) {
this.count = count;
}
public synchronized void increment() {
count++;
}
public synchronized void decrement() {
count--;
}
public int get() {
return count;
}
}
CAS(乐观锁),每次写入(putfield)之前检查count值是否被其他线程更新,如果已经更新,重新读取、计算写入新值;如果未被更新,直接写入计算结果。
/**
* thread safe
*
*/
class Inventory2 {
private AtomicInteger count;
public Inventory2(int count) {
this.count = new AtomicInteger(count);
}
public void increment() {
// CAS
while (true) {
int prev = count.get();
int next = prev + 1;
if (count.compareAndSet(prev, next)) {
break;
}
}
// 可简写如下
// count.getAndIncrement();
}
public void decrement() {
// CAS
while (true) {
int prev = count.get();
int next = prev - 1;
if (count.compareAndSet(prev, next)) {
break;
}
}
// 可简写如下
// count.getAndDecrement();
}
public int get() {
return count.get();
}
}