2.3 启动一个线程–>start()
之前我们已经看到了如何通过重写run()方法来创建一个线程对象,但是线程对象被创建出来并不意味着线程就开始运行了.
- 覆写run方法是给线程提供了所要做的事情的指令清单
- 创建线程对象就是把干活的人叫了过来.
- 而调用start方法,就是喊一声"行动!!",线程会拿着事先准备好的指令清单去完成所要执行的事情.
- 调用start方法,才是真正在操作系统中创建了PCB.
下面我们用大家所感兴趣的领取任务类游戏来说明:
原神,启动!!!
- 首先,比如说我们要去任务列表那里领取任务,任务列表在你领取任务之前,就已经把你任务要做的事情规划好了,也就是该线程要做的任务,都规划在了run()方法中,这里的每一个任务就对应着一个线程,每一个任务中都有我们所要做的事情,也就是run()方法中所写的东西.
- 比如说我们要做一个每日委托的任务,在你靠近任务区域之前会触发委托,我们已经准备就绪做任务了.此时便相当于创建了线程对象.
- 我们在完成每日委托任务的时候,总是要进入战斗状态,打败怪物,来完成委托,当我们在怪物的身上打出了第一点伤害,我们就开始正式执行每日委托任务了,也就是线程通过start()方法正式启动.
2.4 中断一个线程
⽬前常⻅的有以下两种⽅式:
- 通过共享的标记来进⾏沟通
- 调⽤interrupt()⽅法来通知
实例一:通过使用自定义变量来作为标识位
public class Demo1 {public static boolean isRunning = true;//把此变量先设置为truepublic static void main(String[] args) {Thread thread = new Thread(()->{while (isRunning){//isRunning为真,执行,为假,不执行System.out.println("hello tread");try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();try {Thread.sleep(1500);//执行1.5s后修改标识位} catch (InterruptedException e) {throw new RuntimeException(e);}isRunning = false;}
}
中断线程常用的方法如下:
方法 | 说明 |
---|---|
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,那么强制唤醒线程 |
public static boolean interrupted() | 判断当前线程是否中断 |
public boolean isinterrupted() | 判断对象关联的线程是否中断 |
补充:用Tread.currentTread()
来获取当前线程的引用对象.所以判断当前线程是否中断就有了两个方法,第一个方法就是Tread.interrupted()
,另一种就是Tread.currentTread().isinterrupted()
.
实例二:通过使用interrupt()方法来中断线程
public class Demo2 {public static void main(String[] args) {Thread thread = new Thread(()->{while (!Thread.currentThread().isInterrupted()){System.out.println("hello tread");try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}thread.interrupt();//中断线程}
}
这里使用interrupt()方法来中断一个线程与使用标志位的最大区别就是是否在线程休眠的时候强制唤醒线程.如果使用标志位的方法来终止线程,那么线程休眠的时候,就不会唤醒线程,线程依然处于休眠的状态,而使用interrupt()方法可以唤醒线程,使得线程触发sleep唤醒异常,最后在catch方法中来处理唤醒异常,也就是唤醒之后干什么,是可以由程序员自己决定的.
显然,我们在实际开发中,最多使用的是第二种方法,第一种方法不可唤醒线程,线程会继续阻塞,会使得线程的效率大大降低.
下面我们来展示几种线程被唤醒常见的异常处理方法:
- 直接终止线程
public class Demo2 {public static void main(String[] args) {Thread thread = new Thread(()->{while (!Thread.currentThread().isInterrupted()){System.out.println("hello tread");try {Thread.sleep(5000);} catch (InterruptedException e) {break;//直接使用break终止线程}}});thread.start();try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}thread.interrupt();}
}
- 抛出异常,并终止线程
public class Demo2 {public static void main(String[] args) {Thread thread = new Thread(()->{while (!Thread.currentThread().isInterrupted()){System.out.println("hello tread");try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);//抛出异常}}});thread.start();try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}thread.interrupt();}
}
- 打印栈错误信息
public class Demo2 {public static void main(String[] args) {Thread thread = new Thread(()->{while (Thread.currentThread().isInterrupted()){System.out.println("hello tread");try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}});thread.start();try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}thread.interrupt();}
}
但是这种方法我们会发现,打印栈错误信息后并不可以真正终止线程:
这是为什么呢?是因为我们在sleep被唤醒之后,会清除刚才的标志位,所以才会继续执行,所以线程是否终止,是由线程自己说了算,interrupt方法只是给出建议.
我们下面举例来区分它们几个:
下面有请我们的助教:灰太狼,红太狼登场.
- 红太狼让灰太狼出门抓羊,
- 灰太狼现在正在打游戏,灰太狼现在有两种选择,第一就是立刻停下手中的游戏去抓羊,就是使得线程立刻终止,直接break掉或者抛出异常
- 第二种选择就是先答应红太狼一声,等打完这把游戏之后,再去抓羊.就是打印栈错误信息之后并没有直接终止.
- 最后一中当然就是直接无视红太狼,继续打游戏,就是catch中什么都不写,把标志位改回来之后继续执行,下场就是…
平底锅伺候!!!
2.5 等待线程–>join()
有时,我们需要等待⼀个线程完成它的⼯作后,才能进行自己的下⼀步⼯作.所以我们便引入了join方法:重点一句话,谁调用join方法,谁就要进入阻塞状态去等待那个被调用join方法的线程结束
方法 | 说明 |
---|---|
public void join() | 等待线程结束(死等) |
public void join(long millis) | 等待现场结束,最多等x毫秒 |
public void join(long millis,int nanos) | 比上一个方法更精确,可以精确到纳秒 |
我们下面拿一个具体的例子来说明:
public class Demo4 {public static void main(String[] args) {Thread thread = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("tread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();try {thread.join();//在这里,main线程会进入阻塞状态,等待tread线程结束} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("main tread is terminate");}
}
运行结果:
在main线程中调用join方法,有以下两种可能:
- 若t线程已经结束,此时join就会立即返回.
- 若t线程此时还没有结束,join就会继续阻塞等待,一直到t线程结束.
下面我们举一个例子来说明上述问题:
有请助教:达达利亚,钟离登场
钟离邀请达达利亚去万名堂去吃饭,钟离此时是main线程,是他主动发起的邀请,情况一就是钟离先到的早,此时钟离并没有打电话叫达达利亚,就一直等(main,join之后,进入阻塞状态,死等达达利亚)达达利亚到了之后,再去一起吃饭.
情况二就是达达利亚到的早,之后达达利亚直接打电话叫了钟离,钟离直接停下手中的活(join直接返回)下楼去和达达利亚一起去吃饭.
情况三,钟离也是一个有底线的人,由于达达利亚迟迟不肯到来,所以钟离不可以等了(join(限制时间)),再等肚子都要饿扁了,直接离开了
[注意]
在一般情况下,我们不使用死等的的方式去等待线程响应,我们一般是有时间的等.
2.6 休眠当前线程
也是我们比较熟悉⼀组方法,有⼀点要记得,因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是⼤于等于参数设定休眠时间.因为线程从回复就绪状态到CPU上执行还需要一定的时间.
public class Demo10 {public static void main(String[] args) throws InterruptedException {System.out.println(System.currentTimeMillis());Thread.sleep(3 * 1000);System.out.println(System.currentTimeMillis());}
}
我们看到执行的结果相减并不是准确的3000ms,有一定的时间差异.
3. 线程的状态
3.1 线程的状态分类
线程的状态,可以通过==getState()==方法来获取到.
• NEW:Tread对象已经创建,但是还没有start
• RUNNABLE:处于就绪状态
• BLOCKED:后续介绍
• WAITING:死等进入阻塞
• TIMED_WAITING:带有超时时间的等进入阻塞
• TERMINATED:线程已经终止,但是Tread对象还在,未被gc回收.
public class Demo8 {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{for (int i = 0; i < 5; i++) {System.out.println("tread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread thread1 = new Thread(()->{for (int i = 0; i < 5; i++) {System.out.println("tread1");if (i == 1){try {thread.join(3000);//等待时间3s} catch (InterruptedException e) {throw new RuntimeException(e);}}try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});System.out.println(thread.getState());thread1.start();thread.start();System.out.println(thread.getState());Thread.sleep(2000);System.out.println(thread1.getState());thread.join();thread1.join();System.out.println(thread.getState());System.out.println(thread.getState());}
}
运行结果:
把注释处的时间去掉:
thread.join();
可见tread1的状态在等待tread的过程中变为了WAITING.
3.2 线程状态的转移
一条主线三条支线: