上一章节我们讲了 volatile的可见性的,以及可见性的演示,这里就会给大家产生一个误区,这样的使用方式很容易给人的感觉是对volatile修饰的变量进行并发操作是线程安全的。 其实不然,用volatile修饰的变量只有两个特性就是 可见性、禁止指令重排序。并不能保证线程的安全性
我们通过以下代码进行演示。
volatile int iCounter = 0;
AtomicInteger atomicInteger = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
LatchTest latchTest = new LatchTest();
latchTest.startTaskAllInOnce(5);
}
private void m() throws InterruptedException {
for (int i = 0; i < 1000000; i++) {
iCounter++;
atomicInteger.incrementAndGet();
}
}
public void startTaskAllInOnce(int threadNums) throws InterruptedException {
final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(threadNums);
for (int i = 0; i < threadNums; i++) {
new Thread(() -> {
try {
System.out.println("wait thread");
startGate.await();
try {
m();
} finally {
endGate.countDown();
}
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}).start();
}
System.out.println("thread waited");
startGate.countDown();
endGate.await();
System.out.println("iCounter: " + iCounter + " atomicInteger :" + atomicInteger);
}
输出结果:
volatile iCounter: 2972488 atomicInteger :5000000
volatile iCounter: 2737312 atomicInteger :5000000
volatile iCounter: 3613868 atomicInteger :5000000
volatile iCounter: 2081604 atomicInteger :5000000
volatile iCounter: 2875711 atomicInteger :5000000
volatile iCounter: 3037079 atomicInteger :5000000
volatile iCounter: 2806466 atomicInteger :5000000
volatile iCounter: 3218029 atomicInteger :5000000
volatile iCounter: 2608899 atomicInteger :5000000
volatile iCounter: 2513628 atomicInteger :5000000
AtomicInteger 是原子性操作,线程安全的,volatile 并不能保证线程安全。
这是因为虽然 **volatile
保证了内存可见性,每个线程拿到的值都是最新值,但 count++
** 这个操作并不是原子的,这里面涉及到获取值、自增、赋值的操作并不能同时完成。
- 所以想到达到线程安全可以使这三个线程串行执行(其实就是单线程,没有发挥多线程的优势)。
- 也可以使用**
synchronize
**或者是锁的方式来保证原子性。 - 还可以用 **
Atomic
包中AtomicInteger
来替换int
,它利用了CAS
** 算法来保证了原子性。