线程等待,线程休眠,线程状态

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

线程等待:因为线程与线程之间调度顺序是完全不确定它取决于操作系统本身调度器的一个实现但是有时候我们希望这个顺序是可控的此时的线程等待就是一种方法用来控制线程结束的先后顺序

1)我们的线程之间调度顺序是不确定的线程之间的执行是按照调度器来进行安排执行的,这个过程是无序随机的有些时候但是这样不太好我们要控制线程之间的执行顺序先进行执行线程1再来执行线程2再来执行线程3

2)线程等待就是其中一种控制线程执行的先后顺序的一种手段此处我们所说的线程等待就是我们说的控制线程结束的先后顺序

3)当我们进行调用join的时候哪个线程调用的join那个线程就会阻塞等待直到对应的线程执行完毕也就是对应线程run方法执行完之后

4)我们在调用join之后对应线程就会进入阻塞状态暂时这个线程无法在CPU上面执行就暂时停下了不会再继续向下执行了就是让main线程暂时放弃CPU暂时不往CPU上面调度往往会等待到时机成熟

5)Sleep等待的时机是时间结束而我们的join等待的时机是当我们的(t.join)t线程中的run方法执行完了main方法才会继续执行

public class Main{
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
          for(int i=0;i<5;i++){
              System.out.println("hello Thread");
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
        });
        t1.start();
//我们在主线程中就可以使用一个等待操作来进行等待t线程执行结束
        try{
            t1.join();
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }
}

当我们的join执行到这一行就暂时停下了不会继续向下执行当前的join方法是main方法调用的针对这个t线程对象进行调用的此时就是让main等待t我们这是阻塞状态调用join之后main方法就会暂时进入阻塞状态(暂时无法在CPU上执行)

6)join默认情况下是死等只要对应的线程不结束那么我们就进行死等里面可以传入一个参数指定时间最长可以等待多久等不到咱们就撤

Thread.currentThread()表示获取当前对象的实例相当于在Thread实例中run方法中直接使用this

操作系统管理线程
1描述
2组织:双向链表
就绪队列:队列中的PCB有可能随时被操作系统调度上CPU执行

1)我们要注意当前这几个状态都是Thread类的状态和操作系统中的内部的PCB的状态并不是一致的

2)然后我们从当前join位置啥时候才可以向下执行呢也就是说恢复成就绪状态呢就是我们需要等待到当前t线程执行完毕也就是说t的run方法执行完成了通过线程等待我们就可以让t先结束main后结束一定程度上干预了这两个线程的执行顺序

3)我们此时还是需要注意优先级是咱们系统内部进行线程调度使用的参考量咱们在用户代码层面上是控制不了的这是属于操作系统内核的内部行为但是我们的join是控制线程代码结束之后的先后顺序

4)就是说我们带有参数的join方法的时候就会产生阻塞但是这个阻塞不会一直执行下去如果说10s之内t线程结束了此时的join会直接进行返回但是假设我们此时的10S之后t线程仍然不结束那么join也直接返回这就是超时时间

sleep和yield有什么区别

sleep和yield都是Thread类中的静态方法yield是暂停当前执行的线程对象就是放弃当前拥有的CPU资源并执行其他线程就是让当前运行的线程回到就绪状态也就是可执行状态就是以保证相同优先级的状态具有执行机会

1)sleep()方法给其他线程运行机会时不考虑线程的优先级因此会给优先级低的线程以运行的机会而yield()方法只会给相同优先级或者更高优先级的线程以运行机会yield()虽然不会经常用到让线程主动让出CPU但是不会改变线程的状态
2线程执行sleep()方法后会转入阻塞状态所以执行sleep()方法的线程在指定的时间内肯定不会被执行而yield()方法只是使当前线程重新回到可执行状态所以执行yield()方法的线程有可能在进入到可执行状态后马上又被执行。
3sleep()方法声明抛出InterruptedException而yield()方法没有声明任何异常;

获取线程的名字 

1)Thread.currentThread()的意思就是说这个方法就可以获取到当前线程的引用也就是说Thread实例的引用哪一个线程调用的currentThread就获取到的是那一个线程的实例

2)Thread.currentThread的操作等同于this但是我们无法在Runnable和lmada表达式里面进行使用this连代码都编译不了

 

3)如果说我们在main线程里面调用Thread.currentThread()这个时候调用的就是main线程的实例

 public static void main(String[] args) {
          Thread t=new Thread(()->{});
          t.start();
          System.out.println(Thread.currentThread().getName());
//当前我们调用Thread.currentThread()方法的是main线程,我们先通过这个方法获得该实例对象的引用,通过引用就可以访问到该方法
    }
打印结果:main

 

线程休眠:sleep

操作系统是如何管理进程的是通过一个类来进行描述的通过一个双向链表来进行组织的这个说法可以是说针对只有一个线程的进程是如此的但是如果说一个进程里面有多个线程每一个线程里面都有一个PCB一个进程对应的就是一组PCB

1)咱们的上面的这个双向链表属于就绪队列但是在我们的操作系统内核里面这样的队列却不是有多个如果说某一个线程调用了sleep方法这个PCB就会进入到阻塞队列 

2)咱们的操作系统进行调度线程的时候就是从就绪队列里面挑选合适的PCB到CPU上面运行那么我们阻塞队列里面的PCB就只能干等着啥时候这个PCB可以回到就绪队列里面呢那么只有说睡眠时间到了咱们的系统才会把刚才的这个PCB从阻塞队列挪回到就绪队列里面

3)只有我们就绪队列里面的PCB才有被调度上CPU执行的权力阻塞队列里面的PCB没有资格进入到CPU上面执行

1)我们的一个进程对应的就是一组PCB每一个PCB上面就有一个字段叫做tgroupID这个ID其实就是进程的tgroupID同一个进程中的若干个线程的tgroupID其实是相同linux的系统内核是不区分进程和线程的linux内核只认PCB进程和线程其实是咱们程序员写有关于应用程序的代码才搞出来的词实际上linux内核只认PCB在linux系统内核里面我们把线程称之为轻量级进程只不过是有些PCB共用同一个内存有些PCB共用同一块虚拟地址空间有些PCB有不同的虚拟地址空间我们前面所说的进程线程概念是站在一个更加抽象的角度站在用户写代码的角度来进行看待的但在操作系统内核实现的角度他们是一视同仁使用同样的方式来进行表述的

2)咱们的上面的这个链表就是就绪队列的双向链表如果我们的某个线程调用了sleep方法这个PCB就会进入到阻塞队列实际上咱们的操作系统在进行调度线程的时候就是从我们的就绪队列中查找合适的PCB到我们的CPU上面执行当我们的睡眠时间到了系统就会把刚才这个PCB从阻塞队列挪回到就绪队列join也会导致线程进入到阻塞队列里面

线程状态(通过线程的引用的GetState()方法来获取到线程状态)

1)在我们的开发中会经常遇到线程卡死的情况有一些关键的线程阻塞了线程卡死了在我们分析原因的时候我们就可以看看当前的各种线程所处的状态

2)this.getState()Thread.currentThread().getState();

3)进程的状态就是系统按照什么样子的态度来进行决定调度这个进程就绪态和阻塞态相对于那种一个进程只有一个线程的情况进程中单线程的状态就绑定在线程的身上更常见的情况下一个进程中包含了多个线程所谓的状态就是绑定在了线程上面

4)在linux中咱们的PCB其实是和线程相对应的一个进程对应着一组PCB我们通过进程的状态来进行区分决定当前进程是否要被调度上CPU进行执行

5)我们上面所说的就绪状态和阻塞状态都是针对系统上面的线程的状态也就是PCB的状态

6)在我们的JAVA里面在Thread类里面我们针对线程的状态又再次进行了划分

7)我们的线程的状态本质上来说是一种枚举类型

public class Solution {
    public static void main(String[] args) {
        for(Thread.State state:Thread.State.values()){
            System.out.println(state);
        }
    }
}
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED

使用JAVA来进行打印线程的所有状态: 

public class Teacher {
    public static void main(String[] args) {
        for(Thread.State value:Thread.State.values()){
            System.out.println(value);
        }
    }
}

也就是说这些状态是JAVA自己搞出来的就和操作系统中的PCB的状态没有啥关系

1)NEW:Thread对象有了内核中的PCB还没有创建相当于任务布置了但是还没有开始执行工作没有调用start方法

2)RUNNABLE:调用了run方法之后就绪状态就是在就绪队列里面当前的PCB已经创建出来了当前线程正在CPU上执行或者已经准备好上CPU上执行了如果我们的代码中没有进行sleep也没有出现其他的可能导致阻塞的操作代码大概率是出于Runnable状态的

3)BLOCKED:排队等待synchronized状态导致阻塞等待锁状态阻塞状态线程中尝试进行加锁发现锁已经被被其他线程调用了那么此时该线程也会进行阻塞等待这个等待就会等到其他线程释放锁之后(synchronized)(获取到锁没有获取成功)

4)WAITTING线程中调用wait()方法没有等待时间死等除非有其他线程来唤醒

5)TIME-WAITTING表示当前PCB在阻塞队列中等待呢意思就是当前线程在一定时间之内是处于阻塞的状态一定时间到了之后阻塞状态解除当我们的代码执行了sleep(时间)就会进入到TIMED_WAITING,还有join(等待时间),在调用的时候都会进入到阻塞状态;

注释345状态都属于阻塞状态只是阻塞的条件不同当前的线程暂时停了下来不会到CPU上继续执行等到时机成熟才回到CPU上执行

6)TERMINATED状态:此时内核中的线程已经完成了任务PCB没了线程中被销毁但是代码中的Thread对象还在这时就得等这个对象被GC回收这里我们就知道了Thread对象与内核中的PCB的生命不一样

islive判断内核中的线程是否存在除了1和6之外结果都是true判断内核中的PCB是否存在)

1)当我们进行调用wait()方法之后就会从runnable变成waitting状态

2)当我们的线程获取到synchronized锁之后就会从Blocked状态变成runnable状态

 public static void main(String[] args) throws InterruptedException {
       Thread thread=new Thread(){
           int count=0;
         public void run(){
             while(true) {
                 count++;
                 System.out.println(Thread.currentThread().getState());//RUNNABLE状态
                 if (count == 10) {
                     break;
                 }
             }
         }
       };
       thread.start();
       Thread.sleep(1000);
       System.out.println(thread.getState());//Terminated状态
    }

1)BlockedWaittingTimeWaitting都是处于阻塞状态

2)在我们进行分析卡死原因的时候我们就可以来进行分析一下当前程序中各种关键线程所处的状态TimeWaitting你这里面是不是SleepBlocked状态是不是在处于等待锁的状态是不是死锁Waitting是不是说代码中哪里调用了wait或者是join没人来进行唤醒远离就解释了代码为什么这个写这个BUG背后的原因可以解释清楚

常用方法

public static void main(String[] args)throws InterruptedException{
        Thread t1=new Thread(){
            public void run() {
                for(int i=0;i<10;i++)
                {
                   // System.out.println(Thread.currentThread().getName());
                }
                try{
                    sleep(100);
                }catch(InterruptedException e)
                {
                    e.printStackTrace();;
                }
    System.out.println("线程结束");
            }};
        System.out.println(t1.getName());//获取线程名字
        System.out.println(t1.getPriority());//获线程优先级
        System.out.println(t1.isDaemon());//该线程是否为守护线程
        System.out.println(t1.getId());
        System.out.println(t1.isAlive());
        System.out.println(t1.isInterrupted());
        System.out.println(t1.getState());//获取到指定线程的状态
        t1.start();
        while(t1.isAlive()){
            System.out.println(t1.getState());
            System.out.println(t1.isInterrupted());
        }
        System.out.println(t1.getState());
    }}

我们竖着下来的这一连串属于主线任务剩下两边的都是属于支线任务我们创建出一些线程就是让他执行一些任务的可能我们在Runnable过程中执行一些特殊代码导致线程切换到了其它的一些阻塞状态不同的代码就会进入到不同的一个阻塞情况

1)从NEW状态到Runnable状态:当我们创建一个线程的时候new一个Thread对象的时候就会进入到NEW状态当我们调用Start方法的时候就会从NEW状态到Runnable状态

2)从Runnable状态到BLOCKED状态:当我们线程中的代码排队执行synchronized的时候线程就会从RUNNABLE状态变成BLOCKED

3)当我们的线程进行获取到synchronized锁的时候就会从BLOCKED状态变成RUNNABLE状态

4)从Runnable状态到waitting状态:当我们的程序中调用wait()方法之后就会从RUNNABLE状态变成WAITTING变成无限等待状态但是当我们调用notify/notifyAll方法就会从waitting状态变成runnable状态

5)从Runnable状态到TIMED_WAITTING:当我们调用超时时间的等待方法的时候比如说sleep方法会从RUNNABLE状态变成TimeWaitting状态当过了超时时间之后线程就会从timewaitting状态变成runnable状态

6)当我们从RUNNABLE状态变成Terminated状态就是PCB已经被销毁

 总结:

1)线程是一个独立的执行流主要他是为了解决并发编程的问题虽然多进程也可以但是不如线程更轻量

2)创建进程比创建线程开销大很多

销毁进程比销毁线程开销大很多

调度进程比调度线程开销大很多

3)Thread类就是JAVA中系统对于多线程API提供的封装

3.1)构造方法创建线程继承于Thread(匿名内部类lamda表达式)实现Runnable接口(匿名内部类lamda表达式)在构造方法里面还可以指定线程的名字区分当前线程是哪一个

3.2)创建线程在系统内里面创建通过start方法进行创建只有调用start方法才可以在系统中真正创建出一个线程run方法只是描述了一个线程在干什么只有咱们的start方法才是在系统中真正的进行创建出了一个PCB

4)中断线程:核心思路就是说让我们的当前的run方法执行完成本质上来说就是从while循环里面指定一个条件条件不满足循环结束此时的run方法也就完了同时我们也是可以借助Thread类中内置的提供的标志位

4.1)我们通过调用线程的Interrupt方法来去出发终端操作这个方法又有两种行为要么是设置标志位当我们的线程处于就绪状态的时候也就是我们的PCB处于就绪队列的时候要么是抛出一个异常当前成处于阻塞状态的时候直接就抛出异常

5)等待线程:join让一个线程等待另一个线程结束让一个线程先结束然后自己后结束会让调用join的线程产生阻塞效果等到我们的t.join()直到t执行完成之后才会返回

6)线程休眠

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6