Java基础(31)——多线程相关知识详解及示例分析三(join方法及线程生命周期详解)
版权声明
- 本文原创作者:清风不渡
- 博客地址:https://blog.csdn.net/WXKKang
一、join方法
1、引入
默认情况下,两个线程A、B相互之间是独立运行的,那么如果我们希望某一个线程执行完毕之后再执行另外一个线程时该怎么办呢?
这时候我们就需要使用到Thread的join方法协调线程之间的运行顺序,在A线程中执行B线程的join方法,A线程就会等待B线程,只有B线程执行完毕后,A线程才会继续执行
2、API
3、示例
现在我们通过一个示例来理解此方法的意思,我们先在main(主线程)中启动一个MyRunnable线程,然后main线程开始执行for循环,当计数为20的时候让MyRunnable线程通过join方法加入到主线程中,现在只有JoinRunnable线程执行完毕后,主线程才会执行, 接口实现类代码如下:
package qfbd.com;
/*
原创作者:清风不渡
博客地址:https://blog.csdn.net/WXKKang
*/
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("MyRunnable:"+i);
}
}
}
测试类代码如下:
package qfbd.com;
/*
原创作者:清风不渡
博客地址:https://blog.csdn.net/WXKKang
*/
public class Demo {
public static void main(String[] args) throws Exception {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
for(int i=0;i<100;i++){
System.out.println("main:"+i);
if(i==50){
thread.join();
}
}
System.out.println("main is over");
}
}
执行结果如下:
由上面的执行结果我们可以看出join();方法的具体作用,细细理解即可
二、线程生命周期
1、线程状态
线程共有五种状态,如下图所示:
以上就是线程在生命周期中所处的五种状态,下面我们就来逐一学习
2、API与线程状态的关系
在一个线程的生命周期中,线程总是处于某一种状态。线程状态表示了线程正在进行的活动,以及在这段时间内线程可以完成的任务
下面有两个类,他们分别为Thread的实体类及Runnable接口的实现类,代码如下:
package qfbd.com;
public class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<50;i++){
System.out.println("MyThread:"+i);
}
}
}
package qfbd.com;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("MyRunnable:"+i);
}
}
}
(1)创建线程对象(New Thread)
首先创建一个线程对象,它是一个普通的Java对象,这时并没有启动线程,创建代码如下:
//创建Thread实体类线程对象
Thread thread1 = new MyThread();
//创建Runnable接口实现类对象并创建其对应的线程对象
MyRunnable myRunnable = new MyRunnable();
Thread thread2 = new Thread(myRunnable);
(2)使用start()方法启动线程( Runnable)
使用start()方法启动一个新线程,它将进入“就绪/可运行状态( Runnable)”,等待操作系统为线程分配CPU时间,启动代码如下
thread1.start();
thread2.start();
(3)调度线程并执行run() (Running)
操作系统为线程分配CPU时间后,它将转变为“运行状态(Running)”, 现在系统将会自动调用run()方法开始运行。
注意: run()方法是自动被调用的,不是由程序本身所调用
(4)线程阻塞(Not Runnable)
在线程执行时,线程可以调用一些方法阻塞自身的运行,线程交出执行时间(CPU时间)后进入阻塞状态(NotRunnable)。操作系统不会调度被阻塞线程,线程只有退出阻塞状态(NotRunnable)并进入就绪状态(Runnable)后,才可能会继续被操作系统调度
常见的阻塞方式如下所示:
(5)线程终止(Dead)
线程结束后就变为终止状态。最常见的情形就是run()方法执行完成时,线程将自动退出。早期还提供了stop()方法,它已被禁用
线程对象和线程执行是两个概念。线程对象只是一个线程执行体的封装类,本质上还是一个Java对象,它被分配在Java堆中。线程执行是系统运行run()方法, 此方法结束后线程结束,但是线程对象还是依然存在。例如:
Thread thread1 = new MyThread();
thread1.start();
//线程执行结束后还是可以访问线程对象
System.out.println(thread1);
3、如何友好的结束线程?
(1)自然退出与强制退出
任何事物都有生命周期,线程也是一样。通常结束线程有二种方式:
1、自然退出: run()执行完成后线程自动退出,这种行为是可预测的。这是最好的方式。由于run方法中的代码结构通常都是循环结构,因此只需要使用标识控制住循环,就可以让run方法结束,从而线程就结束
2、强制退出: 传统方式使用suspend()和resume()来暂停或恢复线程,使用stop()结束线程。在实际编程中不会使用这三个方法,因为它们操作线程时,根本不知道线程的内部状态,这样可能导致不可预知的情况发生
(2)友好的结束线程
1、首先在run()中定义一个大的循环体,每次循环代表一次处理过程
2、由于run()声明中不能抛出异常,因此在循环体中使用try…catch…finally结构捕获和处理所有的异常
3、在线程中定义一个线程退出标识,使用这个标识控制循环体的退出。这个标识可以由线程本身或者是线程外部的程序进行修改(外部控制、内部控制)
4、在run()中只是提供一个执行入口,所有功能的实现代码应该使用函数或类定义,使得程序结构更加清晰,结构如下:
package qfbd.com;
/*
原创作者:清风不渡
博客地址:https://blog.csdn.net/WXKKang
*/
public class MyThread extends Thread{
private boolean flag = false;
public void setFlag(boolean flag){
this.flag = flag;
}
public boolean isFlag(){
return flag;
}
@Override
public void run() {
/*
* run()方法的主体是一个受控制的循环结构
* 这个循环默认是一个死循环,直到满足特定条件才会退出
*
* 这样就可以保证:
* 1、run()方法可以反复提供服务
* 2、外部可以控制线程什么时候退出
* 3、当run()方法退出时,可以保证服务是完整执行的
*/
try {
while(!flag){
//线程的执行体可以任意,实际就是普通的Java代码,可以调用本类或其他类
//调用本类方法
doservice();
//调用其他类方法
//OtherClass other = new OtherClass();
//other.method();
}
} catch(Throwable th){
//做异常处理,如记日志等
th.printStackTrace();
}finally {
// 释放资源
}
}
//线程要处理的内容的真正代码从这里开始写
public void doservice(){
//执行业务逻辑
}
}
(3)示例
针对上面我们友好结束线程的方式现在以一个示例的方式来加深理解,示例如下:
首先定义一个计数器i,用来控制main方法(主线程)中打印的循环次数,在0~19这段时间内,两个线程交替执行。当计数器变20,将退出标记改为false,也就是终止了MyThread线程的while循环,在run方法结束后,MyThread线程也随之结束
注意:当计数器i变为20的,将标记改为false的时候,CPU不一定马上回到MyThread线程,所以MyThread线程并不会马上终止
MyThread类代码如下:
package qfbd.com;
/*
原创作者:清风不渡
博客地址:https://blog.csdn.net/WXKKang
*/
public class MyThread extends Thread{
private boolean flag = false;
public void setFlag(boolean flag){
this.flag = flag;
}
public boolean isFlag(){
return flag;
}
@Override
public void run() {
try {
while(!flag){
doservice();
}
} catch(Throwable th){
th.printStackTrace();
}finally {
System.out.println("释放资源");
}
}
//线程要处理的内容的真正代码从这里开始写
public void doservice(){
System.out.println("MyThread is run!!!");
}
}
测试类代码如下:
package qfbd.com;
/*
原创作者:清风不渡
博客地址:https://blog.csdn.net/WXKKang
*/
public class Demo {
public static void main(String[] args) throws Exception {
//创建Thread实体类线程对象
MyThread thread1 = new MyThread();
thread1.start();
for(int i = 0;i<30;i++){
System.out.println("main is run:"+i);
if(i==20){
thread1.setFlag(true);
}
}
System.out.println("main is over");
}
}
执行结果如下:
上面这种执行结果就是比较理想的结果,有时会有出入是因为像上面讲的:当标记改为false的时候,CPU不一定马上回到MyThread线程,所以MyThread线程并不会马上终止