线程等待:因为线程与线程之间,调度顺序是完全不确定,它取决于操作系统本身调度器的一个实现,但是有时候我们希望这个顺序是可控的,此时的线程等待,就是一种方法,用来控制线程结束的先后顺序;
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()方法的线程有可能在进入到可执行状态后马上又被执行。
3)sleep()方法声明抛出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(等待时间),在调用的时候,都会进入到阻塞状态;
注释:3,4,5状态都属于阻塞状态,只是阻塞的条件不同,当前的线程暂时停了下来,不会到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)Blocked,Waitting,TimeWaitting都是处于阻塞状态
2)在我们进行分析卡死原因的时候,我们就可以来进行分析一下当前程序中各种关键线程所处的状态,TimeWaitting,你这里面是不是Sleep,Blocked状态,是不是在处于等待锁的状态,是不是死锁,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)线程休眠