【2】线程安全-可见性

版权声明:送人玫瑰,手有余香,一分也是爱 https://blog.csdn.net/m0_37156901/article/details/85863545

可见性

是一个线程对内存的修改可以及时的被其他线程看到。

不可见的原因(Java内存模型原理)

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

对于可见性-JVM提供synchronize 和 volatile

    1. synchronize Atomic中讲到的四种修饰,类,块,静态方法。。。
      在这里插入图片描述
    1. volatile原理通俗理解
      volatile变量值在每次被线程访问时,都强迫从主内存中读取该变量的值。
      而当该变量发生变化的时候,又强迫线程将最新的值刷新到主内存。这样任何时候,线程总能看到该变量的最新值。
      在这里插入图片描述

下面解释内存屏障和禁止重排序的实现

所有这些都是在CPU指令级别进行操作的
在这里插入图片描述

在这里插入图片描述
那么,如果使用volatile修饰计数器的值,是否是线程安全的?看代码。

package com.mmall.concurrency.atomicDemo.count;
import com.mmall.concurrency.annotations.ThreadSafe;
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;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@ThreadSafe
public class VolatileCount {
    // 请求总数
    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);
    }
    private static void add() {
        count++;
        // 1、count
        // 2、+1
        // 3、count
    }
}

结论,volatile不是线程安全的

不适合计数,不是原子安全的,

  • 因为在执行 count++ 这个操作的时候是进行了三步,
  1. 先从主存取出值
  2. 加 1 操作
  3. 再写回主存
    单线程没问题,多线程中可能两个线程同时取最新的值,加 1 再放回,同时操作可能漏掉一些,所以每次执行都比预期的值小。

volatile的使用的两个条件

    1. 对变量的写操作不依赖于当前值
    1. 该变量没有包含在具有其他变量的不必要的式子中,因此volatile适合做状态标记量,

volatile使用的场景1 :状态标记

volatile  boolean inited = false;
    
    // thread 1
    context = loadContext();
    inited = true;
    
    //thread 2,  判断状态,线程1初始化完成后,再在初始化基础之上做其他事
    while( !inited){
        sleep();
    }
    do
        SomethingWithConfig(context);

volatile使用场景2:doubleCheck检查两次

这个之后做解释,更新快,持续关注哦

一分也是爱,么么哒

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_37156901/article/details/85863545