目录
本章概述
通过上一章的学习,我们已经对线程及其创建有了深入的了解;本章将继续学习线程的相关知识,深入学习线程的生命周期,多线程协调对CPU的使用。
一. 线程的生命周期
创建线程对象时,线程的生命周期就已经开始了,直到线程对象被撤销为止。在这整个生命周期中,线程并不是一个创建就进入可运行状态,线程启动之后,也不是一直处于可运行状态。在这个生命周期中,线程含有多种状态,这些状态之间可以相互转化。
Java的线程的生命周期可以分为6种状态:
(1)创建(New)状态
如果创建一个线程而没有启动它,此线程就处于创建状态。
Thread myThread = new MyThreadClass();
其中,MyThreadClass()是Thread的子类。刚创建的线程不能执行,此时,它和其他的Java对象一样,仅仅由JVM为其分配了内存,并初始化了其他成员变量的值,必须向系统注册并分配必要的资源后,才能进入可运行状态。
(2)可运行(Runnable)状态
如果对于一个处于创建状态的线程调用start()方法,则此线程便进入可运行状态。
myThread.start();
myThread进入可运行状态。JVM会为其创建调用栈和程序计数器。
(3)阻塞(Blocked)状态
若一个线程试图获取一个内部的对象锁,而该锁被其他线程持有,则该线程进入阻塞状态。或者它已经进入了某个同步块或同步方法,在运行的过程中它调用了某个对象继承自java.lang.Object的wait()方法,正在等待返回这个同步块或同步方法,此时同样为阻塞状态。
(4)等待(Waiting)状态
当线程调用wait()方法来带另一个线程的通知,或者调用join()方法等待另一个线程执行结束的时候,线程进入等待状态。
(5)计时等待(Timed Waiting)状态
如果线程调用sleep(),wait(),join()等方法的时候,传递一个超时参数,这些方法执行的时候会使线程进入计时等待状态。
(6)终止(Terminated)状态
线程一旦进入终止状态,它将不再具有运行的资格,所以也不可能再转到其他状态。线程会以一下三种方式进行终止状态:
- run()方法执行完成,线程正常结束
- 线程抛出一个未捕获的Exception或Error
- 直接调用该线程的stop()方法来结束线程,该方法已经过时,不推荐使用
二. 线程的调度
线程在生命周期之内,其状态会经常发生变化,由于在多线程编程中同时存在多个处于活动状态的线程,哪一个线程获得CPU的使用权呢?我们往往通过控制线程的状态变化,来协调多个线程对CPU的使用。
2.1 线程睡眠 —— sleep
如果我们需要让当前正在执行的线程暂停一段时间,则通过使用Thread类的静态方法sleep(),使其进入计时状态,让其他线程有机会执行。
sleep()方法是Thread的静态方法,它有两个重载方法:
- public static void sleep(long millis) throws InterruptedException:在指定的毫秒数内让当前正在执行的线程休眠。
- public static void sleep(long millis,nanos) throws InterruptedException:在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠。
线程在睡眠的过程中如果被中断,则方法抛出InterruptedException异常,所以调用时要捕获异常。
public class Thread1 {
public static void main(String[] args) {
Runner1 r1 = new Runner1();
Thread t = new Thread(r1);
t.start();
for (int i = 0; i < 3; i++) {
System.out.println("main thread :"+i);
}
}
}
class Runner implements Runnable{
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 3; i++) {
System.out.println("Runner : " + i);
}
}
}
—————————————————————————————————————————————————————————————————————————————————————————
结果:
main thread :0
main thread :1
main thread :2
----------------- 此处睡眠5秒,5秒后出现以下:
Runner : 0
Runner : 1
Runner : 2
线程睡眠是使线程让出CPU资源的最简单的做法之一,线程睡眠的时候,会将CPU资源交给其他线程,以便轮换执行,当睡眠一定时间后,线程会苏醒,进入可运行状态等待执行。
2.2 线程让步 —— yield
线程让步可以通过yield()方法来实现,该方法和sleep()方法有点相似,都可以让当前正在运行的线程暂停,区别在于yield()方法不会阻塞线程,它只是将线程转换成就绪状态,让系统的调度器重新调度一次。yield()方法结束后,只有与当前线程优先级相同或者更高的线程才能获得执行机会。
在主线程中创建两个子线程对象,然后启动它们,使其并发执行,在子线程的run()方法中每个线程循环9次,每循环3次输出一次。通过调用yield()方法,实现两个子线程交替输出消息。
public class Threadyield implements Runnable{
String str = "";
public void run(){
for(int i = 1;i <= 9;i++){
//获取当前线程名和输出编号
str += Thread.currentThread().getName() + "------" + i + " ";
//当满3条消息时,输出信息内容,并让出CPU
if(i % 3 == 0){
System.out.println(str);
str = "";
Thread.yield();
}
}
}
public static void main(String[] args) {
Threadyield ty1 = new Threadyield();
Threadyield ty2 = new Threadyield();
Thread threada = new Thread(ty1,"线程A");
Thread threadb = new Thread(ty2,"线程B");
threada.start();
threadb.start();
}
}
—————————————————————————————————————————————————————————————————————————————————————————
结果:
线程B------1 线程B------2 线程B------3
线程A------1 线程A------2 线程A------3
线程B------4 线程B------5 线程B------6
线程A------4 线程A------5 线程A------6
线程B------7 线程B------8 线程B------9
线程A------7 线程A------8 线程A------9
sleep()方法与yield()方法的区别
- sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给优先级低的线程以运行的机会,而yield()方法只会给相同优先级或者更高优先级的线程以运行机会。
- 线程执行sleep()方法后会转入阻塞状态,所以,执行sleep()方法的线程在指定的时间内肯定不会被执行,而yield()方法只是使当前线程重新回到可执行状态,所以执行yield()方法的线程有可能在进入到可执行状态后马上又被执行。
- sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常。
- sleep()方法比yield()方法(跟操作系统)具有更好的可移植性。
2.3 线程协作 —— join
若需要一个线程运行到某一个点时,等待另一个线程运行结束后才能继续运行,这种情况可以通过调用另一个线程的join()方法来实现。
在主线程的main()方法中创建一个子线程,一开始两个线程并发执行,每个线程输出6条线程信息,在主线程输出前3条信息之后,等待子线程运行结束,然后,主线程再输出后3条信息。
public class ThreadJoin {
public static void main(String[] args) {
Thread t = new SubThread();
t.start();
for(int i = 1;i <= 6;i++){
System.out.println("我是主线程");
if(i == 3){
try{
t.join(); //等待子线程结束
} catch (InterruptedException e1){
e1.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class SubThread extends Thread{
public void run(){
for(int i = 1;i <= 6;i++){
System.out.println("我是子线程¥¥¥");
try{
sleep(1000);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
}
—————————————————————————————————————————————————————————————————————————————————————————
结果:
我是主线程
我是子线程¥¥¥
我是子线程¥¥¥
我是主线程
我是子线程¥¥¥
我是主线程
我是子线程¥¥¥
我是子线程¥¥¥
我是子线程¥¥¥
我是主线程
我是主线程
我是主线程