4-4 线程安全性-可见性

一个线程对主内存的修改可以及时的被其他线程观察到

导致共享变量在线程间不可见的原因

  • 线程交叉执行
  • 重排序结合线程交叉执行
  • 共享变量更新后的值没有在工作内存与主存间及时更新

可见性之synchronized

JMM关于synchronized的规定

  • 线程解锁前,必须把共享变量的最新值刷新到主内存
  • 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(加锁与解锁是同一把锁)

可见性之volatile

通过加入内存屏障和禁止重排序优化来实现

  • 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存
  • 对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量
    在这里插入图片描述
    在这里插入图片描述

用volatile做计数操作,看是否是线程安全的

package com.mmall.concurrency.example.count;

import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
@NotThreadSafe
public class CountExample4 {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static volatile int count = 0;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
        /*
        运行结果:
         5000
        4992
        4998
        因此volatile也是线程不安全的
        原因是:假设开始时count=5,线程1和线程2同时做count++操作时,线程1和线程2都从内存中读取到了count=5,然后线程1和线程2同时对count做加一操作,然后线程1把count的结果6由工作内存存回内存,线程2也把count的结果6由工作内存存回内存,因此运行结果小于等于5000
        */
    }

    private static void add() {
        count++;
        // 1、count
        // 2、+1
        // 3、count
    }
}

运行结果:

5000
4992
4998

因此volatile也是线程不安全的。
原因是:假设开始时count=5,线程1和线程2同时做count++操作时,线程1和线程2都从内存中读取到了count=5,然后线程1和线程2同时对count做加一操作,然后线程1把count的结果6由工作内存存回内存,线程2也把count的结果6由工作内存存回内存,因此运行结果小于等于5000。

volatile使用

volatile适合使用在状态标识的场景中,如下实例:(volatile还可以用于检查2次的场景中)

volatile boolean inited = false;//全局变量

//线程1:
context = loadContext();
inited= true;

// 线程2:
while( !inited ){
    sleep();
}
doSomethingWithConfig(context)

猜你喜欢

转载自blog.csdn.net/csdnlijingran/article/details/83002639