2.4 线程状态和生命周期
一个线程对象在它的生命周期内,需要经历5个状态。
1.新生状态(New)
用new关键字建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态。
2.就绪状态(Runnable)
处于就绪状态的线程已经具备了运行条件,但是还没有被分配到CPU,处于“线程就绪队列”,等待系统为其分配CPU。就绪状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会进入执行状态。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。有4种原因会导致线程进入就绪状态:
- 新建线程:调用start()方法,进入就绪状态;
- 阻塞线程:阻塞解除,进入就绪状态;
- 运行线程:调用yield()方法,直接进入就绪状态;
- 运行线程:JVM将CPU资源从本线程切换到其他线程。
3、运行状态(Running)
在运行状态的线程执行自己run方法中的代码,直到调用其他方法而终止或等待某资源而阻塞或完成任务而死亡。如果在给定的时间片内没有执行结束,就会被系统给换下来回到就绪状态。也可能由于某些“导致阻塞的事件”而进入阻塞状态。
4、阻塞状态(Blocked)
阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。
有4种原因会导致阻塞:
1.执行sleep(int millsecond)方法,使当前线程休眠,进入阻塞状态。当指定的时间到了后,线程进入就绪状态。
2.执行wait()方法,使当前线程进入阻塞状态。当使用nofity()方法唤醒这个线程后,它进入就绪状态。
3.线程运行时,某个操作进入阻塞状态,比如执行IO流操作(read()/write()方法本身就是阻塞的方法)。只有当引起该操作阻塞的原因消失后,线程进入就绪状态。
4.join()线程联合: 当某个线程等待另一个线程执行结束后,才能继续执行时,使用join()方法。
5、死亡状态(Terminated)
死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有两个。一个是正常运行的线程完成了它run()方法内的全部工作; 另一个是线程被强制终止,如通过执行stop()或destroy()方法来终止一个线程(注:stop()/destroy()方法已经被JDK废弃,不推荐使用)。当一个线程进入死亡状态以后,就不能再回到其它状态了。
三、线程的使用
3.1 终止线程的典型方式
终止线程我们一般不使用JDK提供的stop()/destroy()方法(它们本身也被JDK废弃了)。通常的做法是提供一个boolean型的终止变量,当这个变量置为false,则终止线程的运行。因为线程可能还有后续工作,不能直接将他们嘎了。控制子线程生死的是主线程。
package cn.it.bz.Thread; import java.io.IOException; public class KillThread implements Runnable { //生死牌,true为生,false为死 private boolean flag = true; //控制生死牌的方法 public void killThread(){ this.flag = false; } //子线程 @Override public void run() { System.out.println("子线程开始:"+Thread.currentThread().getName()); int i = 0; while (flag){ System.out.println(Thread.currentThread().getName()+"-"+i); i++; try { Thread.sleep(1000); //休眠,线程由运行状态变为阻塞状态 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("子线程结束"); } //主线程 public static void main(String[] args) throws IOException { System.out.println("主线程开始"); KillThread kill = new KillThread(); Thread thread = new Thread(kill); //启动子线程 thread.start(); //使主线程阻塞 System.in.read(); //主线程结束,结束子线程 kill.killThread(); System.out.println("主线程结束"); } }
主线程启动后,将创建的线程对象包装为Thread 对象,调用start();方法启动子线程。此时子线程执行while循环,主线程阻塞,但是子线程一直在执行,当从键盘输入数据时,主线程不再阻塞并开始向下执行,子线程杀死程序和打印输出语句,主线程是不会等待子线程死亡的。子线程被杀死时不是立即结束工作,而是先执行完线程(也就是run方法)后死亡。
3.2 线程休眠
sleep()方法:可以让正在运行的线程进入阻塞状态,直到休眠时间满了,进入就绪状态。sleep方法为Thread类的静态方法,参数为休眠的毫秒数(1秒 = 1000毫秒)。
package cn.it.bz.Thread; public class SleepThread implements Runnable { @Override public void run() { System.out.println("子线程开始:"+Thread.currentThread().getName()); for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+"---"+i); //子线程休眠 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } //主线程,主线程在哪个类是没有区别 public static void main(String[] args) { System.out.println("主线程开始"); SleepThread sleepThread = new SleepThread(); Thread thread = new Thread(sleepThread); //启动子线程 thread.start(); System.out.println("主线程结束"); } }
主线程是不会等待子线程的,两个线程分别执行各自的。
3.3 线程让步
yield()让当前正在运行的线程回到就绪状态,以允许具有相同优先级的其他线程获得运行的机会。因此,使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。
使用yield方法时要注意的几点:
- yield是一个静态的方法。
- 调用yield后,yield告诉当前线程把运行机会交给具有相同优先级的线程。
- yield不能保证,当前线程迅速从运行状态切换到就绪状态。
- yield只能是将当前线程从运行状态转换到就绪状态,而不能是等待或者阻塞状态。当让步线程遇到堵塞时先变为阻塞态,阻塞结束了再变为就绪态。
package cn.it.bz.Thread; public class TestyieldThread implements Runnable{ @Override public void run() { for (int i = 0; i < 15; i++) { //如果当前线程名字是Thread-1,就让步,而且只让步第一次 if ("Thread-1".equals(Thread.currentThread().getName())){ if (i == 0){ System.out.println("我™直接让步~"); Thread.yield(); } } System.out.println(Thread.currentThread().getName()+"---"+i); } } public static void main(String[] args) { Thread thread1 = new Thread(new TestyieldThread());//子线程1 Thread thread2 = new Thread(new TestyieldThread());//子线程2 //启动线程,线程的运行顺序取决于CPU的线程调度 thread1.start(); thread2.start(); } }
3.4 线程联合
当前线程邀请调用方法的线程优先执行,在调用方法的线程执行结束之前,当前线程不能再次执行。线程A在运行期间,可以调用线程B的join()方法,让线程B和线程A联合。这样,线程A就必须等待线程B执行完毕后,才能继续执行。和Java中方法的执行顺序差不多。
join方法的使用
join()方法就是指调用该方法的线程在执行完run()方法后,再执行join方法后面的代码,即将两个线程合并,用于实现同步控制。
package cn.it.bz.Thread; import java.util.stream.Stream; //子线程A class A implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("当前A线程:"+Thread.currentThread().getName()+"--"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } //主线程 public class TestJoinThread { public static void main(String[] args) throws InterruptedException { Thread threadA = new Thread(new A()); threadA.start(); for (int i = 0; i < 10; i++) { if (i == 2) { //主线程联合A线程,直接在主线程调用join方法 threadA.join(); } System.out.println("主线程:"+Thread.currentThread().getName()+"--"+i); Thread.sleep(1000); } } }
主线程和A线程在没有联合之前是同步执行的,但是执行到threadA.join();时,主线程会等待A线程执行完毕之后再执行。
3.4.1 线程联合案例
package cn.it.bz.Thread; //儿子买烟线程 class SonThread implements Runnable{ @Override public void run() { System.out.println("儿子得知要去买烟,买烟需要十分钟"); for (int i = 0; i < 10; i++) { System.out.println("第"+i+"分钟"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("儿子买烟回来了"); } } //爸爸抽烟线程 class FatherThread implements Runnable{ @Override public void run() { System.out.println("爸爸想抽烟发现烟抽完了,让儿子去买包华子"); //启动儿子买烟线程 Thread thread = new Thread(new SonThread()); thread.start(); //爸爸需要等着儿子买烟回来 try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("儿子买烟出异常了,爸爸出门找儿子"); System.exit(1); //结束运行在虚拟机的进程,找儿子去吧 } System.out.println("爸爸开心地接过烟,猛吸了一口,说真好!b( ̄▽ ̄)d "); } } public class TestJoinDemo { public static void main(String[] args) { System.out.println("这是个爸爸和儿子的故事~"); Thread thread = new Thread(new FatherThread()); thread.start(); } }
3.5 Thread类中的其他常用方法
3.5.1 获取当前线程名称
方式一
this.getName()获取线程名称,该方法适用于继承Thread实现多线程方式。
class GetName1 extends Thread{
@Override
public void run() {
System.out.println(this.getName());
}
}
方式二
Thread.currentThread().getName()获取线程名称,该方法适用于实现Runnable接口实现多线程方式。Thread.currentThread()获取当前线程对象
class GetName2 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
3.5.2 修改线程名称
方式一
当线程继承Thread类时通过构造方法设置线程名称。
package cn.it.bz.Thread; class SetName1 extends Thread{ //接受自己定义的线程名称 public SetName1(String name){ super(name); //调用父类的构造方法 } @Override public void run() { System.out.println("SetName1线程名称:"+this.getName()); } } //主线程 public class TestSetNameThread { public static void main(String[] args) { SetName1 setName1 = new SetName1("setName1"); setName1.start(); } }
方式二
当线程实现Runable接口时通过setName()方法设置线程名称。
package cn.it.bz.Thread; class SetName implements Runnable{ @Override public void run() { System.out.println("当前线程名字:"+Thread.currentThread().getName()); } } public class TestSetNameThread2 { public static void main(String[] args) { //创建Thread对象 Thread thread = new Thread(new SetName()); thread.setName("😄"); thread.start(); } }
3.5.3判断线程是否存活
isAlive()方法: 判断当前的线程是否处于活动状态。返回值是true表示活着,false表示死亡。
活动状态是指线程已经启动且尚未终止,线程处于正在运行或准备开始运行的状态,就认为线程是存活的。
package cn.it.bz.Thread; class Alive implements Runnable{ @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("当前线程:"+Thread.currentThread().getName()+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } //主线程 public class TestAliveThread { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new Alive()); thread.setName("🤭"); thread.start(); System.out.println("当前🤭线程是否存活:"+thread.isAlive());//true Thread.sleep(7000); //主线程休眠7秒 System.out.println("当前🤭线程是否存活:"+thread.isAlive());//false } }