上一章节我们讲了 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
算法来保证了原子性。