一、volatile 轻量级锁

        synchronized 是阻塞式同步锁,valotile 在激烈竞争的情况下,会升级为重量级锁,有两个核心 (工作内存和主内存),三大性质:可见性,原子性,安全性。

二、 volatile 用途

        多线程并发编程中,各个线程从共享变量的主内存拷贝到工作内存中,引擎会基于工作内存的数据进行处理。线程对volatile 变量的修改会立即被其他线程所感知

        被volatile修饰的变量,能够使线程获得变量的最新值,从而避免出现脏数据的现象。

三、volatile 底层原理

        1.生成了汇编代码,在对 volatile变量进行写操作的时候,JVM就会向处理器发送一条Lock前缀的指令,这些指令可以将当前处理器缓存的数据写入系统内存。使其他线程缓存的数据无效。当处理器发现这些数据无效时,会从系统内存中重新读取该数据,就可以获得最新值。

四、volatile 内存模型

Java ----- volatile

 

 

五、volatile  应用场景

        因 volatile 无法保证操作的原子性,所以使用时必须满足以下两个条件

        1.对变量的写操作不能依赖于当前值

        2.对变量没有包含在具有其他变量的不变式中,

1.状态标记量 
    volatile boolean flag = false;
     //线程1
    while(!flag){
        doSomething();
    }
      //线程2
    public void setFlag() {
        flag = true;
    }
2.单例模式 
    class Singleton {
        private volatile static Singleton instance = null;
    
        private Singleton() {
    
        }
    
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null)
                        instance = new Singleton();
                }
            }
            return instance;
        }
    }
    //new Singleton 主要做了三件事 1. 在堆内存中开辟内存  2. 通过构造函数初始化成员变量    3.将instance 对象 指向分配的内存空间。

六、volatile 深度解读

        1.volatile 保证可见性

    //线程1
    boolean stop = false;
    while(!stop){
        doSomething();
    }
     
    //线程2
    stop = true;
// 这个中标记方法不一定将线程1中断。因每个线程都有一个工作线程,线程1在运行的时候会将 stop 成员变量拷贝一份到工作内存中。
// 线程2更改stop的值还没来得及写入系统内存中,就去干其他事情了,线程1不知道线程2对 stop 变量的修改,还会一直循环下去。
//加入 volatile后 
    线程2对stop变量修改后,会强行的写入系统内存中,导致线程1中缓存的stop数据无效,所以线程1会重新从系统内存中读取新的数据。

2.volatile 保证原子性

     //成员变量虽然保持了可见性,但是没有保证原子性,自增操作不能保持原子性 自增操作(读取变量的原始值,加1操作,写入工作内存) 
    //某个时刻,线程1对inc 进行自增操作,线程1读取了原始值,然后线程1被阻塞了,线程2进行了自增操作,也去读取变量的原始值。由于线程1对白能量inc
    //进行了读取操作,却没有进行修改操作,所以线程2的缓存值不会失效,也不会时主内存中的值刷新。所以线程2会直接去主内存中读取值,进行加1操作,写入主内存。
    //这时线程因已经进行读操作,还是之前的10,进行加1,等到11,最后写入内存,两个线程都进行了自增1的操作,但inc 却只加了 1,
    //解决方案:
    // 同意通过synchronized 或者 Lock 进行枷锁,保证原子性操作。或者使用AtomicInteger 
    public class Nothing {
    
        private volatile int inc = 0;
        private volatile static int count = 10;
    
        private void increase() {
            ++inc;
        }
    
        public static void main(String[] args) {
            int loop = 10;
            Nothing nothing = new Nothing();
            while (loop-- > 0) {
                nothing.operation();
            }
        }
    
        private void operation() {
            final Nothing test = new Nothing();
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 1000000; j++) {
                        test.increase();
                    }
                    --count;
                }).start();
            }
    
            // 保证前面的线程都执行完
            while (count > 0) {
    
            }
            System.out.println("最后的数据为:" + test.inc);
        }
    
    }
    最后的数据为:1206951
    最后的数据为:43510
    最后的数据为:0
    最后的数据为:0
    最后的数据为:2168433
    最后的数据为:0
    最后的数据为:72419
    
        //CountDownLatch 使一个线程等待其他线程完成各自的工作后再执行   
        // 通过计数器实现,初始线程的任务数量,每个线程完成任务后,会自行减一,到零时,等待的线程可以恢复执行。
        CountDownLatch countDownLatch = new CountDownLatch(100);
        AtomicInteger atomicInteger = new AtomicInteger(0);//保证原子性操作。
        for (int i = 0; i < 100; i++) {
            new Thread() {
                @Override
                public void run() {
                    atomicInteger.getAndIncrement();//自增一

                    countDownLatch.countDown();//计数器减一
                }
            }.start();
        }
        //使当前线程等待,知道 计数器为 0 
        countDownLatch.await();
        System.out.println(atomicInteger.get());
    

        3.volatile 保证有序性

            volatile关键字禁止指令重排序,,能在一定程度上保持安全性

            1.1 当程序执行volatile变量的读操作或者写操作时,在其前面的操作的更改肯定已经全部执行完毕,结果对后面的线程可见。后面的操作肯定没有执行。

            1.2 当进行指令优化时,不能将对volatile的读操作或者写操作语句放在其后面执行,也不能放在前面执行。

// x、y为非volatile变量
    // flag为volatile变量
    
    x = 2;        //语句1
    y = 0;        //语句2
    flag = true;  //语句3
    x = 4;        //语句4
    y = -1;       //语句5
//在进行指令重排序的时候,不会将语句3 放在语句1,2的前面,也不会放在语句4,5的后面,volatile关键字保证执行到语句3时 语句 1,2是已经执行完毕的,
//语句1,2执行的结果时对 语句3,4,5时可见的。

 

七、乐观锁和悲观锁

        在某个资源不可用的时候,就会让出cpu的资源,把当前线程状态切换为阻塞状态,等到资源可用了,就将线程唤醒。  这就是典型的悲观锁。

        synchronized :是一种典型的独占锁 ,独占锁是一种悲观锁。认为一个线程修改共享数据的时候其他的线程也会修改数据。因此只会在其他线程不会干扰

的情况下 执行,这也会导致其他的线程挂起,等到持有锁的线程释放锁。

        但是线程的挂起和执行的恢复都是有着很大的开销,当一个线程正在等待锁时,它不会做任何事情。所以这是悲观锁的很大的缺点。所以就有了 乐观锁的概念。

        每次执行数据时不加锁,其他的线程一定不会修改,若是修改过产生冲突 就失败。重试。直到成功为止。乐观锁的效果更好。      

 

相关文章: