【javaEE】多线程初阶(Part4 volatile、wait、notify)


前言

今天不学习,明天变垃圾

本文主要是volatile(保证内存空间性)、wait和notify的介绍。


一、volatile关键字

  1. volatile内存可见性(内存可见性也是线程安全的一个问题)
  2. CPU读写数据最快,内存次之,硬盘最慢:CPU比内存快3-4个数量级,内存又比硬盘快3-4个数量级
  3. 编译器的优化在多线程下可能存在误判,如:一个线程只读,一个线程又进行修改
  • 解决方法:volatile关键字是内存可见。 volatile是“可变的”,即:可以使用这个关键字来修饰一个变量,此时被修饰的变量 编译器就不会做出“不读内存,只读寄存器”这样的优化;加了该关键字之后,每次代码都会老老实实的读取该变量的值。
  1. 编译器啥时候优化,啥时候不优化,这个是和写的代码和运行环境密切相关的。

  2. synchronized(加锁)保证了线程的可重入性和原子性!! 而volatile是保证“内存可见性”的,但是不保证原子性。

  3. 针对一个线程读,一个线程修改 的场景,使用volatile是合适的; 针对两个线程修改的场景,volatile是无能为力的,因为没有原子性
    (volatile可以多加,但是千万不要少加)

  4. 在面试中一旦谈到了volatile,多半是脱离不了JMM(Java Memory Model:java内存模型)。
    ① volatile是会禁止编译器优化的,避免直接读取了CPU寄存器中缓存的数据,而是每次都重新读取内存。
    ② 站在JMM的角度来看待volatile:
    正常程序执行过程中会把主内存(也就是所说的内存)的数据先加载到工作内存(也就是所说的CPU寄存器,不是真的内存)中,然后再进行计算处理; 编译器优化可能会导致不是每次都真的读取主内存,而是直接取从工作内存中缓存的数据,这就可能会导致内存可见性问题; volatile起到的效果 就是保证每次读取内存都是真的从主内存中重新读取

  5. 缓存的存储空间比寄存器大,比内存小; 但是缓存的访问速度比寄存器慢,比内存快。
    有时候重复读内存,就不必真的每次都读内存了,而是可以只有第一次读内存,后续就读缓存中的数据就行,这样可以提高效率。(类似于“编译器优化”)
    简记法:寄存器 缓存 内存

(寄存器里存的是正在使用的中间结果)

  1. 【如果面试中问到volatile,最好两个方面都回答。 并且面试最好“举一反三”】

二、wait和notify

  1. wait和notify用来调配线程线程执行的顺序
  2. wait是Object方法,而Object是java所有类的祖宗,因此可以使用任意类的实例来调用wait方法; wait可能会抛出InterruptedException异常,这个异常就是被interrupt方法唤醒的。
  3. 当线程执行到wait的时候就会发生阻塞,直到另一个线程调用notify才会把这个wait唤醒,然后继续往下走。
  4. IlegalMonitorStateException非法的锁状态异常:此时的Monitor指的其实就是synchronized,在JVM中synchronized也叫做“监视器锁”。
  5. wait操作 在内部本质上是做三件事:
    ①释放当前锁;
    ②进行等待通知;
    ③满足一定条件的时候(也就是别的线程调用notify)被唤醒,然后重新获取锁
  • 所以等待通知的前提是释放锁,而释放锁的前提是先加锁
  • wait的第一步操作是释放锁,保证其他线程能够正常往下执行
  • wai和加锁操作密不可分。(也就是synchronized之后才可以wait)
    wait这里可以记住类似举例:ATM取钱
  1. notify也是要包含在synchronized里面的
    线程1没有释放锁的话,线程2也就无法调用notify(因为锁在阻塞等待);线程1调用wait,在wait中就释放锁了,这个时候虽然线程1代码阻塞在synchronized里面,但是此时的锁还是在释放状态,线程2 就可以拿到锁。
  2. 其他线程也是需要上锁才能调用notify,调用了notify就会唤醒wait,wait就会尝试重新加锁,但是wait加锁可能需要阻塞一会儿,直到notify所在的线程释放锁完成后wait才加锁成功
  3. 要保证:加锁的对象和调用wait的对象是同一个对象;另外同时还要保证:调用wait的对象和调用notify的对象也是同一对象!!
  4. 多个线程都在wait时,notify是随机唤醒一个线程; notifyAll则是全都唤醒,但是即使是唤醒了所有的wait,这些wait也是需要重新竞争锁的,而重新竞争锁的过程仍然是串行的,所以这个其实并不是很常用。
  5. wait, notify, notifyAll 都是 Object 类的方法。
  6. wait 要搭配 synchronized 来使用, 脱离 synchronized 使用 wait 会直接抛出异常。
  7. 方法notify()【即:唤醒等待】也要在同步方法或同步块【即:使用synchronized修饰】中调用。

13. 【面试题】 wait和sleep的对比

答: ① wait 需要搭配 synchronized 使用没有synchronized就会抛异常; sleep 不需要。
② wait 是 Object 的方法, sleep 是 Thread 的静态方法.

其实理论上 wait 和 sleep 完全是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻塞一段时间。
唯一的相同点就是都可以让线程放弃执行一段时间。

(群面进行分组讨论:当个掐表的!还有30s提醒同学们赶紧总结)

三、代码参考

Demo10-13
多线程实例【重要】


THINK

  1. volatile是保证了内存可见性,但是不保证原子性; synchronized(加锁)保证了原子性和可重入性。
  2. synchronized、wait、notify是要保证同一对象的!
  3. wait内部本质上是三步
  4. wait和notify都要保证在synchronized里面
  5. 【面试题】sleep和wait的区别
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

'Dream是普通小孩耶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值