volatile,解决内存可见性引起的问题,wait和notify

简介: volatile,解决内存可见性引起的问题,wait和notify


补充:synchronized(务必会读(辛可肉耐子)会写),要搭配一个对象的时候,不一定非要是访问的this成员

 

synchronized(锁对象){ 代码块}

public synchronized static void func(){} 静态方法和具体对象无关,和类有关了

相当于synchronized(类.class)

在synchronized()里面没有必要去纠结里main写普通对象还是类对象之类的,synchronized不关心对象是什么,只关心两个线程是否针对同一个对象加锁

static:相当于加上了一个类属性/类方法


一、 💛

内存可见性引起的问题,如下图,你在线程1设定假如他不是等于0就终止,然后你在线程2中给他把值改变了,让他的循环结束,但是我么可以看到,当前我们输入了一个不是0的数字,然后试图改变它,却改变不了,这就是内存可见性的问题

程序在运行的时候,java编译器和jvm可能会对代码进行优化,程序猿们写代码,然后java编译器把你代码改了,保持原有逻辑不变的情况下提高代码效率——编译器优化后

并且优化的效果特别好:服务器的启动步骤非常复杂,启动一个差不多10分钟左右(但是假如我们吧优化关了,可能1个小时打底)

我们想要知道他是如何优化的,就要先清楚while循环的本质,两个指令

1.load读取内存

2.jcmp(比较,并且跳转,寄存器操作,速度极快)

此时编译器发现,代码反复的,快速的读取同一个内存值,并且这个内存值每次读出来的结果还是一样的,此时编译器决定,直接把load优化掉了,只是第一次执行load,后续并不执行load,直接拿寄存器中的数据进行比较了。

但是在另一个线程修改t2线程会不会执行,什么时候去执行,因此产生了误判,导致虽然最后t2的isQuit改动了,但是t1线程中,并未重复load也就会导致出现上述问题了。

import java.util.Scanner;
public  class  Demo {
    public  static int isQuit=0;                 //静态变量
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {           //创立一个线程
            while(isQuit==0){
                ;
            }
            System.out.println("t1 结束了");
        }); 
        Thread t2 = new Thread(() -> {         //我们去用t2来改变t1的值
            Scanner scanner=new Scanner(System.in);
            System.out.println("请输入isQuit的数字");
            isQuit=scanner.nextInt();    
        });
        t1.start();
        t2.start();
    }
}

volatile(会读会写 (wao(平🐍)里太哦)弥补上述缺口:意思易变的,修饰一个变量之后,编译器就明白,这个变量是一边倒,就不能按照上述方式处理代码(把读操作优化到寄存器中),让编译器禁止优化,于是保证t1在循环的过程中,始终都能读取内存中的数据

volatile本质是保持变量的内存可见性

见下面代码用法:

下面得到的结果就是正确的,我就暂时省略结果了。

import java.util.Scanner;
public  class  Demo {
    public volatile static int isQuit=0;          //变量static前面
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while(isQuit==0){
                ;
            }
            System.out.println("t1 结束了");
        });
        Thread t2 = new Thread(() -> {
            Scanner scanner=new Scanner(System.in);
            System.out.println("请输入isQuit的数字");
            isQuit=scanner.nextInt();
        });
        t1.start();
        t2.start();
    }
}

编译器的优化是一个“玄学问题”,就比如说,我在新代码里面,写了个sleep,同时把volatile取消了,但是这样也正确

可以理解为,加上sleep之后,sleep就会大幅度的影响到while循环的速度,速度慢了,编译器也不打算继续优化了~此时即使不加volatile,也能够及时感知到内存变化了,sleep到底间隔多久,会触发优化,只有那些码届远古大能才知道。

二、💜

另一种解释方式

Java的内存模型(JMM)

其实这个理解和上面那个编译器解释原理是一样的,从主内存读,但反复读都是一样的,所以直接就去工作内存读,但是工作内存又是什么鸟东西捏?

工作内存:不是我们平时说的内存,而是cpu的寄存器和cpu缓存统称为工作内存,有人可能会好奇那为啥不叫cpu寄存器+cpu缓存呢(猜:JAVA宣称是跨平台,但是cpu的话又是有一部分硬件知识,他们希望java程序猿可以不用掌握太多的硬件知识。

内存可见性和加锁描述了线程安全问题的典型情况和处理方式。


三、 💙

wait(等待)和notify(通知):用来协调线程顺序的重要工具,多线程调度是随机的~很多时候希望多个线程按照咱们规定的顺序来执行,完成线程之间的配合工作。

上面两个都是object提供的方法,也就是说任意对象,当wait引起线程阻塞之后,可以用interrupt方法,把线程给唤醒,打断当前线程的阻塞状态的。

wait执行程序的时候会干三件事:

1.解锁

2.阻塞等待

3.当被其他线程唤醒之后,就会尝试重新加锁,加锁成功,wait执行完毕,继续往下执行其他逻辑。

也就是说wait(需要先加锁)

核心思路:先加锁,在synchronized里面进行wait(加锁要加同一个对象上面),这里的线程会一直阻塞到其他线程notify。

其中最典型的场景就是有效的避免线程饥饿/线程饿汉

 

几个注意

1.要想notify能顺利唤醒wait,就需要确保wait和notify都是同一个对象调用的,

2.wait和notify都需要放到synchronzied之中的,虽然notify不涉及到解锁操作

3.如果进行notify的时候,另一个线程没有处于wait状态,此时notify也没有任何副作用。

t2可以理解成辅助t1的线程,使用notify线程对其他线程统筹安排作用。

import java.util.Scanner;
public  class  Demo {
    public  static Object locker=new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while(true){
               synchronized (locker) {
                   System.out.println("t1 开启");       //第一步执行的
                   try {
                       locker.wait();                  //第二步执行,t2陷入阻塞状态
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println("t1 over");      //第五步,t1被唤醒
               }
               }
        });
        Thread t2 = new Thread(() -> {
         while (true) {
             try {
                 Thread.sleep(100);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             synchronized (locker) {
                 System.out.println("t2 开启");      //第三步执行的
                 locker.notify();                   //第四部执行,唤醒t1
                 System.out.println("t2 over");
             }
         }
        });
        t1.start();
        t2.start();
    }
}

 

线程可能有多个~

几个线程wait,一个线程复制notify,notify只会唤醒一个线程,具体哪个随机,notifyAll会唤醒全部线程(但是不推荐去用),这种也是全随机,就和wait的初心违背了。

如果要唤醒某个特定的线程,就要让不同的线程,使用不同的对象来进行wait,想要唤醒谁,就可以使用对应的对象notify

wait和sleep的区别

sleep有明确的使用是假,到达时间自动被唤醒,也能提前用interrupt

wait:死等,一直等到其他线程notify(但是,是正常唤醒,可继续工作,还会进入wait 状态),wait也可以被interrupt提前唤醒(但是这个是来通知线程要结束了,线程要收尾了)

,当然他也有带时间版的和join差不多,因此协调多个线程执行顺序wait比notify更牛一些


相关文章
|
4月前
|
存储 SQL 缓存
揭秘Java并发核心:深度剖析Java内存模型(JMM)与Volatile关键字的魔法底层,让你的多线程应用无懈可击
【8月更文挑战第4天】Java内存模型(JMM)是Java并发的核心,定义了多线程环境中变量的访问规则,确保原子性、可见性和有序性。JMM区分了主内存与工作内存,以提高性能但可能引入可见性问题。Volatile关键字确保变量的可见性和有序性,其作用于读写操作中插入内存屏障,避免缓存一致性问题。例如,在DCL单例模式中使用Volatile确保实例化过程的可见性。Volatile依赖内存屏障和缓存一致性协议,但不保证原子性,需与其他同步机制配合使用以构建安全的并发程序。
73 0
|
2月前
|
缓存 Java 编译器
【多线程-从零开始-伍】volatile关键字和内存可见性问题
【多线程-从零开始-伍】volatile关键字和内存可见性问题
44 0
|
5月前
|
安全 Java 开发者
探索Java内存模型:可见性、有序性和并发
在Java的并发编程领域中,内存模型扮演了至关重要的角色。本文旨在深入探讨Java内存模型的核心概念,包括可见性、有序性和它们对并发实践的影响。我们将通过具体示例和底层原理分析,揭示这些概念如何协同工作以确保跨线程操作的正确性,并指导开发者编写高效且线程安全的代码。
|
5月前
|
缓存 安全 Java
Java面试题:解释volatile关键字的作用,以及它如何保证内存的可见性
Java面试题:解释volatile关键字的作用,以及它如何保证内存的可见性
86 4
|
5月前
|
设计模式 安全 Java
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
89 1
|
5月前
|
设计模式 缓存 安全
Java面试题:工厂模式与内存泄漏防范?线程安全与volatile关键字的适用性?并发集合与线程池管理问题
Java面试题:工厂模式与内存泄漏防范?线程安全与volatile关键字的适用性?并发集合与线程池管理问题
64 1
|
5月前
|
存储 缓存 Java
(一) 玩命死磕Java内存模型(JMM)与 Volatile关键字底层原理
文章的阐述思路为:先阐述`JVM`内存模型、硬件与`OS`(操作系统)内存区域架构、`Java`多线程原理以及`Java`内存模型`JMM`之间的关联关系后,再对`Java`内存模型进行进一步剖析,毕竟许多小伙伴很容易将`Java`内存模型(`JMM`)和`JVM`内存模型的概念相互混淆,本文的目的就是帮助各位彻底理解`JMM`内存模型。
126 0
|
5月前
|
微服务
多线程内存模型问题之在单例模式中,volatile关键字的作用是什么
多线程内存模型问题之在单例模式中,volatile关键字的作用是什么
|
5月前
|
存储 缓存 安全
Java面试题:介绍一下jvm中的内存模型?说明volatile关键字的作用,以及它如何保证可见性和有序性。
Java面试题:介绍一下jvm中的内存模型?说明volatile关键字的作用,以及它如何保证可见性和有序性。
38 0
|
5月前
|
设计模式 缓存 安全
Java面试题:详解单例模式与内存泄漏?内存模型与volatile关键字的实操?并发工具包与并发框架的应用实例
Java面试题:详解单例模式与内存泄漏?内存模型与volatile关键字的实操?并发工具包与并发框架的应用实例
38 0