• Java并发-线程上下文切换与死锁


     理解线程的上下文切换

    概述:在多线程编程中,线程个数一般都大于 CPU 个数,而每个 CPU 同一时-刻只能被一个线程使用,为了让用户感觉多个线程是在同时执行的, CPU 资源的分配采用了时间片轮转的策略,也就是给每个线程分配一个时间片,线程在时间片内占用 CPU 执行任务

     定义:当前线程使用完时间片后,就会处于就绪状态并让出 CPU,让其他线程占用,这就是上下文切换,从当前线程的切换到了其他线程

    线程上下文切换时机: 当前线程的 CPU 时间片使用完或者是当前线程被其他线程中断时,当前线程就会释放执行权。那么此时执行权就会被切换给其他的线程进行任务的执行,一个线程释放,另外一个线程获取,就是我们所说的上下文切换时机。

    什么是线程死锁

    死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的互相等待的现象,在无外力作用的情况下,这些线程会一直相互等待而无法继续运行下去

    •  如上图所示死锁状态,线程 A 己经持有了资源 2,它同时还想申请资源 1,可是此时线程 B 已经持有了资源 1 ,线程 A 只能等待。
    • 反观线程 B 持有了资源 1 ,它同时还想申请资源 2,但是资源 2 已经被线程 A 持有,线程 B 只能等待。所以线程 A 和线程 B 就因为相互等待对方已经持有的资源,而进入了死锁状态。

     线程死锁的必备要素

    • 互斥条件:进程要求对所分配的资源进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待;

    两个线程拥有互斥的资源而且都没有释放。

    • 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放,如 yield 释放 CPU 执行权);

    没有外力强迫其释放,自己也不能主动释放。

    • 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放;
    • 循环等待条件:指在发生死锁时,必然存在一个线程请求资源的环形链,即线程集合 {T0,T1,T2,…Tn}中的 T0 正在等待一个 T1 占用的资源,T1 正在等待 T2 占用的资源,以此类推,Tn 正在等待己被 T0 占用的资源。

     

    死锁的实现

    场景设计

    • 创建 2 个线程,线程名分别为 threadA 和 threadB;
    • 创建两个资源, 使用 new Object () 创建即可,分别命名为 resourceA 和 resourceB;
    • threadA 持有 resourceA 并申请资源 resourceB;
    • threadB 持有 resourceB 并申请资源 resourceA ;
    • 为了确保发生死锁现象,请使用 sleep 方法创造该场景;
    • 执行代码,看是否会发生死锁。

    结果如下图所示,线程A获取A资源,线程B获取B资源。然后线程A由想要获取B资源,线程B又想获取A资源,他们谁也没有主动释放。

     代码讲解: 

    • 从代码中来看,我们首先创建了两个资源 resourceA 和 resourceB;
    • 然后创建了两条线程 threadA 和 threadB。threadA 首先获取了 resourceA ,获取的方式是代码 synchronized (resourceA) ,然后沉睡 1000 毫秒;
    • 在 threadA 沉睡过程中, threadB 获取了 resourceB,然后使自己沉睡 1000 毫秒;
    • 当两个线程都苏醒时,此时可以确定 threadA 获取了 resourceA,threadB 获取了 resourceB,这就达到了我们做的第一步,线程分别持有自己的资源;
    • 那么第二步就是开始申请资源,threadA 申请资源 resourceB,threadB 申请资源 resourceA 无奈 resourceA 和 resourceB 都被各自线程持有,两个线程均无法申请成功,最终达成死锁状态。
    1. package jvm.juc;
    2. public class DeadLock {
    3. private static Object resoureceA = new Object();
    4. private static Object resoureceB = new Object();
    5. public static void main(String[] args) {
    6. Thread threadA = new Thread(()->{
    7. System.out.println("线程A-准备获得A资源....");
    8. synchronized (resoureceA) {
    9. System.out.println("线程A-已经获得A资源");
    10. try {
    11. Thread.sleep(1000);
    12. } catch (InterruptedException e) {
    13. e.printStackTrace();
    14. }
    15. System.out.println("线程A-准备获得B资源");
    16. synchronized (resoureceB) {
    17. System.out.println("线程A-已经获得B资源");
    18. }
    19. }
    20. });
    21. threadA.setName("Thread-A");
    22. Thread threadB = new Thread(()->{
    23. System.out.println("线程B-准备获得B资源....");
    24. synchronized (resoureceB) {
    25. System.out.println("线程B-已经获得B资源");
    26. try {
    27. Thread.sleep(1000);
    28. } catch (InterruptedException e) {
    29. e.printStackTrace();
    30. }
    31. System.out.println("线程B-准备获得A资源");
    32. synchronized (resoureceA) {
    33. System.out.println("线程B-已经获得A资源");
    34. }
    35. }
    36. });
    37. threadA.setName("Thread-B");
    38. threadA.start();
    39. threadB.start();
    40. }
    41. }

    如何避免线程死锁

    要想避免死锁,只需要破坏掉至少一个构造死锁的必要条件即可,学过操作系统的读者应该都知道,目前只有请求并持有和环路等待条件是可以被破坏的。

    我们,不让他们循环持有相互的资源就行。

    1. package jvm.juc;
    2. public class DeadLock {
    3. private static Object resoureceA = new Object();
    4. private static Object resoureceB = new Object();
    5. public static void main(String[] args) {
    6. Thread threadA = new Thread(()->{
    7. synchronized (resoureceA) {
    8. System.out.println("线程A-已经获得A资源");
    9. try {
    10. Thread.sleep(1000);
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }
    14. synchronized (resoureceB) {
    15. System.out.println("线程A-已经获得B资源");
    16. }
    17. }
    18. });
    19. threadA.setName("Thread-A");
    20. Thread threadB = new Thread(()->{
    21. synchronized (resoureceA) {
    22. System.out.println("线程B-已经获得A资源");
    23. try {
    24. Thread.sleep(1000);
    25. } catch (InterruptedException e) {
    26. e.printStackTrace();
    27. }
    28. synchronized (resoureceB) {
    29. System.out.println("线程B-已经获得B资源");
    30. }
    31. }
    32. });
    33. threadA.setName("Thread-B");
    34. threadA.start();
    35. threadB.start();
    36. }
    37. }

  • 相关阅读:
    带你了解前端之CSS层叠样式表
    Hive--12---文件存储格式
    【CV】第 15 章:结合计算机视觉和 NLP 技术
    sql 注入(2), 文件读写 木马植入 远程控制
    Vite3 + Vue2.7 环境搭建(TS)
    谷粒学院16万字笔记+1600张配图(十)——课程管理
    GO语言使用之网络编程(TCP编程)
    java 使用GeoTools工具 geojson 与shp 相互转换
    荐书丨《进化心理学》:刘耕宏、王心凌背后的魔力之谜
    【信号处理】基于CNN自编码器的心电信号异常检测识别(tensorflow)
  • 原文地址:https://blog.csdn.net/abc123mma/article/details/128048989