上一章节我们讲了 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 :5000000volatile iCounter: 2737312 atomicInteger :5000000volatile iCounter: 3613868 atomicInteger :5000000volatile iCounter: 2081604 atomicInteger :5000000volatile iCounter: 2875711 atomicInteger :5000000volatile iCounter: 3037079 atomicInteger :5000000volatile iCounter: 2806466 atomicInteger :5000000volatile iCounter: 3218029 atomicInteger :5000000volatile iCounter: 2608899 atomicInteger :5000000volatile iCounter: 2513628 atomicInteger :5000000AtomicInteger 是原子性操作,线程安全的,volatile 并不能保证线程安全。
这是因为虽然 **volatile 保证了内存可见性,每个线程拿到的值都是最新值,但 count++** 这个操作并不是原子的,这里面涉及到获取值、自增、赋值的操作并不能同时完成。
- 所以想到达到线程安全可以使这三个线程串行执行(其实就是单线程,没有发挥多线程的优势)。
- 也可以使用**
synchronize**或者是锁的方式来保证原子性。 - 还可以用 **
Atomic包中AtomicInteger来替换int,它利用了CAS** 算法来保证了原子性。
