• 多线程涉及的其它知识(死锁(等待唤醒机制),内存可见性问题以及定时器)


    目录

    一.死锁

    1.死锁现象

    2.线程间的等待唤醒机制

    什么是等待唤醒机制?

    等待与唤醒方法:

    等待唤醒机制的案例演示:

    补:Wait和Sleep的区别:

    二.内存可见性问题(volatile)

    1.volatile 关键字

    2.如何保证可见性?

    3.volatile与锁的不同:      

    三.定时器的概述和使用

    1.Timer:

    2.TimerTask:定时任务


    一.死锁

    1.死锁现象

            如果出现了同步嵌套,就会很容易产生死锁问题.是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象.简单来说死锁是  两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于等待状态.

    1. public class MyThread extends Thread {
    2. private boolean flag;
    3. public MyThread(boolean flag) {
    4. this.flag = flag;
    5. }
    6. @Override
    7. public void run() {
    8. if (flag) {
    9. synchronized (LockUtils.objA) {
    10. System.out.println("true线程进来了持有ObjA锁");
    11. synchronized (LockUtils.objB) {
    12. System.out.println("true线程进来了持有ObjB锁");
    13. }
    14. }
    15. } else {
    16. synchronized (LockUtils.objB) {
    17. System.out.println("false线程进来了持有ObjB锁");
    18. synchronized (LockUtils.objA) {
    19. System.out.println("false线程进来了持有ObjA锁");
    20. }
    21. }
    22. }
    23. }
    24. }
    25. public interface LockUtils {
    26. //定义了两把锁对象
    27. Object objA = new Object();
    28. Object objB = new Object();
    29. }
    30. public class MyTest {
    31. public static void main(String[] args) throws InterruptedException {
    32. //创建了两个线程
    33. MyThread th1 = new MyThread(true);
    34. MyThread th2 = new MyThread(false);
    35. //开启线程
    36. th1.start();
    37. th2.start();
    38. }
    39. }

    运行结果便是如此!你等我,我等你.害!孽缘啊!所以一定要注意切勿造成如此"孽缘"!!!!

    为了避免死锁,就出现了一个机制:线程间的等待唤醒机制

    2.线程间的等待唤醒机制

    什么是等待唤醒机制?

            这个机制就相当于,生产线程生产出了资源.  也就是有资源了,于是就等着,那不可能一直干等着吧,不通知消费线程,他又怎么知道生产出来了资源!通知后,消费线程便拿到资源,然后消费资源,直到资源没有了,就等着,也不可能干等着吖,得通知生产线程这里没有资源了,需要生产线程去生产.这个  生产线程与消费线程之间的等待与通知对方就比较形象的展示了等待唤醒机制!

    生产线程生产出了资源,就会等待消费线程来使用,如果没有通知消费线程,那么消费线程也就会一直等待,因为消费线程并不知道已经有资源了!这样的相互等待就会形成死锁现象!

    等待与唤醒方法:

    • wait ()  在其他线程调用此对象的 notify () 方法或 notifyAll () 方法前,导致当前线程等待。   
    • wait (long timeout) 在其他线程调用此对象的 notify () 方法或 notifyAll () 方法,或者超过指定的时间量前,导致当前线程等待。
    • notify () 唤醒在此对象监视器上等待的单个线程,随机唤醒。
    • notifyAll ()  唤醒在此对象监视器上等待的所有线程。    

    注意: notify()需要在同步方法或同步块中调用,即在调用前,线程必须获得该对象的对象级别锁.  在调用wait()方法之前,线程必须获得该对象的对象级别锁.

    等待唤醒机制的案例演示:

    1. //共享资源
    2. public class Buns {
    3. public String name;
    4. public int price;
    5. //定义一个标记
    6. public boolean flag = false; //false 表示没有资源,true表示有资源
    7. }
    8. //消费者线程
    9. public class GetThread extends Thread {
    10. private Buns buns;
    11. public GetThread(Buns buns) {
    12. this.buns = buns;
    13. }
    14. @Override
    15. public void run() {
    16. while (true) {
    17. synchronized (buns) {
    18. if (!buns.flag) {
    19. //如果进来了,说明没有资源,没有资源便会等待
    20. try {
    21. buns.wait();
    22. //线程一旦等待,就会释放锁,下次被唤醒,就从这里醒来,也就是从哪里等待,从哪里醒来
    23. } catch (InterruptedException e) {
    24. e.printStackTrace();
    25. }
    26. }
    27. //没有进入if语句,就会消费资源
    28. System.out.println(buns.name + "====" + buns.price);
    29. //消费资源后,通知生产线程去生产
    30. buns.flag = false;
    31. buns.notify();
    32. }
    33. }
    34. }
    35. }
    36. //生产者线程
    37. public class SetThread extends Thread {
    38. private Buns buns;
    39. int i = 0;
    40. public SetThread(Buns buns) {
    41. this.buns = buns;
    42. }
    43. @Override
    44. public void run() {
    45. while (true) {
    46. synchronized (buns) {
    47. //生产线程,生产出了资源,通知消费线程,消费资源
    48. if (buns.flag) {
    49. //进来了,表示有资源
    50. try {
    51. //有资源便会开始等待
    52. buns.wait();
    53. } catch (InterruptedException e) {
    54. e.printStackTrace();
    55. }
    56. }
    57. //生产了资源
    58. if (i % 2 == 0) {
    59. buns.name = "肉包子";
    60. buns.price = 3;
    61. } else {
    62. buns.name = "素包子";
    63. buns.price = 2;
    64. }
    65. //通知消费者线程
    66. buns.flag = true; //修改标记
    67. buns.notify(); //唤醒等待的线程
    68. }
    69. i++;
    70. }
    71. }
    72. }
    73. //测试
    74. public class MyTest {
    75. public static void main(String[] args) {
    76. //生成者线程 SetThread
    77. //消费者线程 GetThread
    78. //资源 Bun 两个线程共享
    79. Buns buns = new Buns();
    80. SetThread th1 = new SetThread(buns);
    81. GetThread th2 = new GetThread(buns);
    82. th2.start();
    83. th1.start();
    84. }
    85. }

    补:Wait和Sleep的区别:

    • 它们最大本质的区别是,Sleep()不释放同步锁,Wait()释放同步锁。
    • 还有用法的上的不同是:Sleep(milliseconds)可以用时间指定来使他自动醒过来,如果时间不到,就只能调用Interreput()来强行打断;Wait()可以用Notify()直接唤起。
    • 这两个方法来自不同的类分别是Thread和Object
    • 最主要是Sleep方法没有释放锁,而Wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

    二.内存可见性问题(volatile)

    1.volatile 关键字

            咱们直接开门见山了,   volatile可以解决内存可见性问题.   volatile 关键字:当多个线程进行操作共享数据时,可以保证内存中的数据可见。相较于 synchronized 是一种较为轻量级的同步策略。

    想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的.关于java内存模型,在小编的这篇文章里有浅浅的介绍.

    2.如何保证可见性?

            对于可见性,Java提供了  volatile关键字来保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。另外,通过  synchronized也能够保证可见性, synchronized能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

    3.volatile与锁的不同:      

            volatile 变量,用来确保将变量的更新操作通知到其他线程。可以将 volatile 看做一个轻量级的锁,但是又与锁有些不同!对于多线程, 不是一种互斥关系.   并且  不能保证变量状态的“原子性操作”!


    三.定时器的概述和使用

    定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以  通过Timer和TimerTask类来实现  定义调度的功能。

    1.Timer:

    • public Timer()                                                                         //创建定时器对象
    • public void schedule(TimerTask task, long delay)             //延迟delay秒后执行任务
    • public void schedule(TimerTask task,long delay,long period)  //重复执行任务,第一次等待delay秒执行,以后每隔period秒重复执行.
    • public void schedule(TimerTask task,  Date time)                      //指定特定时间开始执行任务
    • public void schedule(TimerTask task,  Date firstTime, long period)        //指定特定时间开始执行任务,以后每隔period秒重复执行

    2.TimerTask:定时任务

    • public abstract void run()                                      //制定任务
    • public boolean cancel()                                        //取消定时器
    1. public class MyTest {
    2. public static void main(String[] args) {
    3. //定时器 Timer
    4. Timer timer = new Timer();
    5. MyTimerTask myTimerTask = new MyTimerTask(timer);
    6. //定时器,2秒后只执行一次该任务。
    7. timer.schedule(myTimerTask, 2000);
    8. //定时器,重复执行任务。第一次等待2秒执行 ,以后每隔1秒重复执行。
    9. timer.schedule(myTimerTask, 2000, 1000);
    10. }
    11. }
    12. //定时任务
    13. class MyTimerTask extends TimerTask {
    14. private Timer timer;
    15. public MyTimerTask(Timer timer) {
    16. this.timer = timer;
    17. }
    18. @Override
    19. public void run() {
    20. System.out.println("砰~~~~爆炸了");
    21. timer.cancel(); //取消定时器
    22. }
    23. }

    (小编也在努力学习更多哟!以后再慢慢分享的啦!)

    希望对友友们有所帮助!!!!

     

     

  • 相关阅读:
    React 中的延迟加载Lazy loading
    hbuilderx创建、运行uni-app
    git协同操作
    2 Zookeeper 单击安装
    光谱编辑和修复工具:Steinberg SpectraLayers Pro mac
    java设计模式---代理模式详解
    python 从二进制文件中读取浮点数据
    我在国企当合同工的那段日子
    MindSpore自定义算子中的张量维度问题
    Redis基本数据类型:List
  • 原文地址:https://blog.csdn.net/naoguoteng/article/details/126136799