理解线程的上下文切换

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

rxjava切换到主线程执行 java线程切换原理_java

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

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

什么是线程死锁

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

rxjava切换到主线程执行 java线程切换原理_rxjava切换到主线程执行_02

  •  如上图所示死锁状态,线程 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 占用的资源。

rxjava切换到主线程执行 java线程切换原理_rxjava切换到主线程执行_03

 

死锁的实现

场景设计

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

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

rxjava切换到主线程执行 java线程切换原理_rxjava切换到主线程执行_04

 代码讲解: 

  • 从代码中来看,我们首先创建了两个资源 resourceA 和 resourceB;
  • 然后创建了两条线程 threadA 和 threadB。threadA 首先获取了 resourceA ,获取的方式是代码 synchronized (resourceA) ,然后沉睡 1000 毫秒;
  • 在 threadA 沉睡过程中, threadB 获取了 resourceB,然后使自己沉睡 1000 毫秒;
  • 当两个线程都苏醒时,此时可以确定 threadA 获取了 resourceA,threadB 获取了 resourceB,这就达到了我们做的第一步,线程分别持有自己的资源;
  • 那么第二步就是开始申请资源,threadA 申请资源 resourceB,threadB 申请资源 resourceA 无奈 resourceA 和 resourceB 都被各自线程持有,两个线程均无法申请成功,最终达成死锁状态。
package jvm.juc;

public class DeadLock {
    private static Object resoureceA = new Object();
    private static Object resoureceB = new Object();
    public static void main(String[] args) {
        Thread threadA = new Thread(()->{
            System.out.println("线程A-准备获得A资源....");
            synchronized (resoureceA) {
                System.out.println("线程A-已经获得A资源");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程A-准备获得B资源");
                synchronized (resoureceB) {
                    System.out.println("线程A-已经获得B资源");
                }
            }
        });
        threadA.setName("Thread-A");
        Thread threadB = new Thread(()->{
            System.out.println("线程B-准备获得B资源....");
            synchronized (resoureceB) {
                System.out.println("线程B-已经获得B资源");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程B-准备获得A资源");
                synchronized (resoureceA) {
                    System.out.println("线程B-已经获得A资源");
                }
            }
        });
        threadA.setName("Thread-B");

        threadA.start();
        threadB.start();

    }
}

如何避免线程死锁

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

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

package jvm.juc;

public class DeadLock {
    private static Object resoureceA = new Object();
    private static Object resoureceB = new Object();
    public static void main(String[] args) {
        Thread threadA = new Thread(()->{
            synchronized (resoureceA) {
                System.out.println("线程A-已经获得A资源");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resoureceB) {
                    System.out.println("线程A-已经获得B资源");
                }
            }
        });
        threadA.setName("Thread-A");
        Thread threadB = new Thread(()->{
            synchronized (resoureceA) {
                System.out.println("线程B-已经获得A资源");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resoureceB) {
                    System.out.println("线程B-已经获得B资源");
                }
            }
        });
        threadA.setName("Thread-B");

        threadA.start();
        threadB.start();

    }
}