各位看官,大家早安午安晚安呀~~~
如果您觉得这篇文章对您有帮助的话
欢迎您一键三连,小编尽全力做到更好
欢迎您分享给更多人哦
今天我们来学习:wait和notify : 避免线程饿死(以及votile内存可见性和指令重排序问题)
目录
4.volatile
4.1.保证内存可见性
4.2.:指令重排序问题(我们放到下一篇博客来讲解)
5.wait,notify
5.1.wait方法的使用
5.2.notify方法的使用
6.wait和notify可以避免线程饿死
4.volatile
volatile的两个作用(其实都是不让编译器做出多余的优化,对我们的程序产生bug)
1.保证内存可见性
2.解决指令重排序问题
4.1.保证内存可见性
序言:
计算机运行的程序往往要访问数据,这些依赖的数据一般都被读取到了内存里面(譬如我们定义的一个变量就是在内存里面)
就譬如count++;cpu先把count读到cpu寄存器里面再进行++;
但是! cpu其他的操作都很快,但是读内存就比较慢(读硬盘更慢,快慢都是相对的)
因此,为了提高效率编译器就会做出优化,把一些要读内存的操作优化成读寄存器。减少读内存的次数,就提高了整体程序的效率~~
public class Demo1 {private static int isQuit = 0;public static void main(String[] args) {Thread t1 = new Thread(() ->{while(isQuit == 0){// 一直循环啥也不不干}System.out.println("t1 退出");});Thread t2 = new Thread(()->{System.out.println(" 请输入isQuit的值");Scanner scanner = new Scanner(System.in);isQuit = scanner.nextInt(); // 输入不为0的值,使t1线程结束});t1.start();t2.start();}
}
我们发现我们明明输入非0值但是t1线程就是没结束为什么呢?
所以说volatile就是一个优化方法,我们给isQuit前面用volatile修饰就可以避免这样的问题了
在多线程环境下,编译器对于是否要进行这样的优化,判定不一定准.这个时候就需要程序猿通过 volatile 关键字,告诉编译器,这个情况不用优化!!!(优化,是算的快了,但是算的不准了)
总之:编译器,也不是万能的.也会有一些自己短板的地方.此时就需要程序猿进行补充了.
只需要给 isQuit加上 volatile 关键字修饰,此时编译器自然就会禁止上述优化过程。
还有另一种方式,也可以避免这样的问题(再加上一个时间更长的操作,让读内存不那么慢了(相对来说)哈哈)
public class Demo1 {private static int isQuit = 0;public static void main(String[] args) {Thread t1 = new Thread(() ->{while(isQuit == 0){// 一直循环啥也不不干try { //就多加了一个sleepThread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t1 退出");});Thread t2 = new Thread(()->{System.out.println(" 请输入isQuit的值");Scanner scanner = new Scanner(System.in);isQuit = scanner.nextInt(); // 输入不为0的值,使t1线程结束});t1.start();t2.start();}
}
此时没加 volatile, 但是给循环里加了个 slee
线程是可以退出了
加了 sleep 之后, while 循环执行速度就慢了.由于次数少了,load (读内存)操作的开销,就不大了.因此,优化也就没必要进行了.
没有触发 load的优化,也就没有触发内存可见性问题了.
到底啥时候代码有优化,啥时候没有?也说不清楚,但是使用 volatile 还是更靠谱的选择!!
4.2.:指令重排序问题(我们放到下一篇博客来讲解)
5.wait,notify
每个线程都是独立的执行流
想法:
由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知.(join是决定了线程结束的先后顺序)
但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序.
完成这个协调工作, 主要涉及到三个方法
wait() / wait(long timeout): 让当前线程进入等待状态.
notify() / notifyAll(): 唤醒在当前对象上等待的线程.
注意: wait, notify, notifyAll 都是 Object 类的方法.
5.1.wait方法的使用
wait()方法要做的事情:
- 释放当前的锁(前提是你得有锁,不然怎么释放)
- 线程进入阻塞
- 线程被唤醒, 重新尝试获取这个锁.
wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常.
public static void main(String[] args) throws InterruptedException {Object locker = new Object();System.out.println("wait 之前");locker.wait(); //没加锁,会抛出异常System.out.println("wait 之后");}
wait 结束等待的条件:
1.其他线程调用该对象的 notify 方法.
2.wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
3.其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.
接下来我们看一个代码
public static void main(String[] args) throws InterruptedException {Object locker = new Object();System.out.println("wait 之前");locker.wait(); // 只要一般涉及阻塞的方法都会抛出异常//locker这个对象只要不notify,这个线程就一直处于waiting状态(加了等待时间就不会了)System.out.println("wait 之后");}
运行结果以及jconsole观察到的结果(这个程序就一直等待,死等(除非有等待时间))
5.2.notify方法的使用
我们以代码举例:创建三个线程都去wait,主线程notify唤醒(随机的)
public static void main(String[] args) throws InterruptedException {Object locker = new Object(); // 公用一个锁对象Thread t1 = new Thread(() ->{synchronized (locker){try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t1 醒了");});Thread t2 = new Thread(()-> {synchronized (locker){try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t2 醒了");});Thread t3 = new Thread(()->{synchronized(locker){try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t2 醒了");});t1.start();t2.start();t3.start();System.out.println("唤醒其中一个线程");Thread.sleep(1000); // 确保其他线程已经处于waiting状态synchronized(locker){locker.notify();// 也要记得加锁呀,不然咋释放锁}}
结果:可以看到随机一个线程被唤醒,其他线程在waiting状态
如果是notifyAll方法呢?(三个线程都被唤醒了,但是第一个拿到锁的线程如果执行的任务比较多,另外两个线程会处于BLOCKED状态(由于锁竞争导致的阻塞))
6.wait和notify可以避免线程饿死
先介绍synchronized是可重入锁,什么是可重入锁呢?一个线程对一个对象连续加锁两次不会出现死锁。就譬如这个按理说会出现死锁但是我是可重入的所以不会形成死锁
说到死锁:举一个例子()(这样一个线程想要多把锁,进而形成环,直接卡死就会导致死锁)
说到环就想到哲学家问题
总结:四个成因(破坏一个就解除)
class Singleton {private static Singleton instance = new Singleton();public static Singleton getInstance(){ // 一定要是静态方法,不然一开始别人都拿不到这个对象,又没办法调用这个方法,岂不是贻笑大方return instance;}private Singleton(){} // 啥也不用干,也干了呀,把构造方法给藏起来了
}public class SingletonDemo{public static void main(String[] args) {Singleton singleton = new Singleton();}
}
上述就是wait和notify : 避免线程饿死(以及votile内存可见性和指令重排序问题)的全部内容啦
能看到这里相信您一定对小编的文章有了一定的认可。
有什么问题欢迎各位大佬指出
欢迎各位大佬评论区留言修正~~