实现线程安全的三种基本手段
摘要
摘要
本文旨在举例说明Java中多线程的不安全性,已经使用三种不同的方法去解决例子中的问题。
目录
(点击可直接跳转)
目录
(点击可直接跳转)*1.举例说明
*2.避免在多线程下,出现意外结果的方法
1.前言
在这个例子中,我们开启100个线程,对一个Integer类型,值为0的数,进行加减同一个数的操作。按道理,结果应该是0,那么现实中的情况真的是不是这样呢?
代码如下:
public class Counter {
private Integer value = 0;
public int getValue() {
return value;
}
public int addAndGet(int i) {
value += i;
return value;
}
public int minusAndGet(int i) {
value -= i;
return value;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
for (int i = 0; i < 10; i++) {
if (test() == 0) {
System.out.print(true+" ");
} else {
System.out.print(false+" ");
}
}
}
public static Integer test() throws ExecutionException, InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(100);
Counter counter = new Counter();
List<Future<Void>> futures = new ArrayList<>();
for (int i = 0; i < 100; ++i) {
futures.add(
threadPool.submit(
() -> {
safeSleep();
counter.addAndGet(2);
counter.minusAndGet(2);
return null;
}));
}
threadPool.shutdown();
for (Future future : futures) {
future.get();
}
return counter.value;
}
private static void safeSleep() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
代码很简单,使用线程池开辟100个线程,每一个线程执行一次加减2操作。100次操作之后,对产生的结果进行与0比较,比较10次。从结果我们可以看到,就算是如此简单的操作,在多线程情况下都会出现问题。
结果如下:

2.避免在多线程下,出现意外结果的方法
1.使用synchornized同步块
相当于给代码上锁,也就是同一时刻,只能有一个线程执行该段内容。可以在方法的签名中声明synchornized,其实相当于把这个class对象当成锁。例如:
public synchronized int minusAndGet(int i) {
value -= i;
return value;
}
或者使用一个可变的对象:
private final Object lock = new Object();
public int minusAndGet(int i) {
synchronized (lock) {
value -= i;
return value;
}
}
注意:synchornized()中锁住的内容(例子中是lock),必须保证在后面的代码中不会被修改。否则锁住的不是一个对象,结果还是会出现问题。
2.使用原子类型
使用AtomicInteger/AtomicBoolean/AtomicLong类代替对应的类型,也可以实现同样的效果。
不过需要使用对应的get方法获取相应类型的值,例如:
private AtomicInteger value = new AtomicInteger(0);
public int getValue() {
return value.get();
}
// 加上一个整数i,并返回加之后的结果
public int addAndGet(int i) {
return value.addAndGet(i);
}
// 减去一个整数i,并返回减之后的结果
public int minusAndGet(int i) {
return value.addAndGet(-i);
}
注意:这里的addAndGet方法是AtomicInteger中提供的加一个整数的方法,与类中的方法无关。
3.使用ReentrentLock
使用ReentrentLock(可重入锁)的lock/unlock。
ReentrantLock lock = new ReentrantLock();
public int addAndGet(int i) {
try {
lock.lock();
value += i;
return value;
}finally {
lock.unlock();
}
}