线程的生命周期和状态控制

本文详细介绍了Java线程的生命周期阶段,包括新建、就绪、运行、阻塞(等待、同步和其他)、死亡状态,以及线程的睡眠、优先级调整、让步(yield)和线程合并(join)方法。还讨论了守护线程的概念和如何设置。
摘要由CSDN通过智能技术生成

1、线程的生命周期

线程的生命周期就是一个线程从创建到消亡的过程,如图

新建状态(new)

用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新建状态。处于新建状态的线程有自己的内存空间,通过调用start方法进入就绪状态。

就绪状态(Runnable)

处于就绪状态的线程已经具备了运行条件(也就是具备了在CPU上运行的资格),但还没有分配到CPU的执行权,处于“线程就绪队列”,等待系统为其分配CPU。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。

运行状态(Running)

处于就绪状态的线程,如果获得了CPU的调度,就会从就绪状态变为运行状态,执行run()方法中的任务。如果该线程失去了CPU资源,就会又从运行状态变为就绪状态,重新等待系统分配资源。也可以对在运行状态的线程调用yield()方法,它就会让出CPU资源,再次变为就绪状态。

阻塞状态(Blocked)

在某种特殊的情况下,被人挂起或执行输入输出操作时,让出CPU执行权并临时中断自己的执行,从而进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。

阻塞产生的原因不同,阻塞状态又分为三种:

1、等待阻塞:

运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态。当调用notify()或notifyAll()等方法,则该线程就会重新转入就绪状态。

2、同步阻塞:

线程在获取同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。当获取同步锁成功,则该线程就会重新转入就绪状态。

3、其他阻塞:

通过调用线程sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,则该线程就会重新转入就绪状态。

死亡状态(Dead)

线程在run()方法执行完了或者因异常退出了run()方法,该线程结束生命周期。此外,如果线程执行了interrupt()或stop()方法,那么它也会以异常退出的方式进入死亡状态。

2、线程状态控制

1、线程睡眠(sleep)

如果我们需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,指定时间之后,解除阻塞状态,进入就绪状态,则可以通过调用Thread的sleep方法。

注意:

        1、sleep是静态方法,最好不要用Thread的实例对象调用它,因为它睡眠的始终是当前正在运行的线程,而不是调用它的线程对象,它只对正在运行状态的线程对象有效。

        2、使用sleep后,线程进入阻塞状态,睡眠时间过后才会重新进入就绪状态,但从就绪状态到运行状态是由系统决定的,我们不能精准控制,所以调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒。

public static void main(String[] args) {

		for(int i = 1; i < 10; i++) {
			System.out.println(i);
			try {
				Thread.sleep(2000); // 对主线程休眠2秒钟
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

2、线程的优先级

每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。

Thread类提供了setPriority(int newPriority)和getPriority()方法来设置和返回一个指定线程的优先级,其中setPriority方法的参数是一个整数,能够设置[1-10]之间的整数,数值越大,那么优先级越高,也可以使用Thread类提供的三个静态常量:

public final static int MIN_PRIORITY = 1;

public final static int NORM_PRIORITY = 5;

public final static int MAX_PRIORITY = 10;

 

class TestThread extends Thread {
    public TestThread() {}
    public TestThread(String name, int pro) {
        super(name); // 设置线程名字
        this.setPriority(pro); // 设置线程的优先级
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.getName() + "线程--第" + i + "次执行!");
        }
    }
}

public class TestDemo{
public static void main(String[] args) {
        new TestThread("高级",10).start();
        new TestThread("低级",1).start();
    }
}

运行代码,可以看到一般情况下设置比重较高的会先执行,比重低的后执行

 3、线程让步

yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态的方法,它也可以让当前正在执行的线程暂停,让出CPU资源给其它的线程。但是和sleep()方法不同的是,它不会进入到阻塞状态,而是进入到就绪状态。

class TestThread extends Thread {
    public TestThread() {}
    public TestThread(String name, int pro) {
        super(name); // 设置线程名字
        this.setPriority(pro); // 设置线程的优先级
    }
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(this.getName() + "线程--第" + i + "次执行!");
            //线程让步
            Thread.yield();
        }
    }
}

public class TestDemo{
    public static void main(String[] args) {
        new TestThread("高级",10).start();
        new TestThread("低级",1).start();
    }
}

运行结果,可以明显看到比重低也会出现在前面执行

4、线程合并

线程的合并就是:线程A在运行期间,可以调用线程B的join()方法,这样线程A就必须等待线程B执行完毕后,才能继续执行。

应用场景:当一个线程必须等待另一个线程执行完毕才能执行时,Thread类提供了join方法来完成这个功能。

class TestThread extends Thread {
    public TestThread() {}

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.getName() + "线程--第" + i + "次执行!");
        }
    }
}

public class TestDemo{
    public static void main(String[] args) {
        TestThread th = new TestThread();
        th.start();
        try{
            th.join();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

        //主线程任务
        for (int i = 0; i < 10; i++) {
            String name = Thread.currentThread().getName();
            System.out.println(name + "线程第" + i + "次执行!");
        }
    }
}

运行结果,主线程会等待其他线程执行完才会执行

5、守护线程

守护线程通常用于执行一些后台作业,守护线的好处就是你不需要关心它的结束问题。当普通线程(前台线程)都全部执行完毕,也就是当前在运行的线程都是守护线程时,Java虚拟机JVM将退出。

守护线程与普通线程写法上基本没啥区别,调用线程对象的方法setDaemon(true),就可以把该线程标记为守护线程。setDaemon(true)方法必须在启动线程前调用,否则抛出IllegalThreadStateException异常。

//前台线程
class CommonThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("前台线程" + i + "次执行!");
        }
    }
}
//后台线程
class DaemonThread extends Thread {
    int i = 0;
    @Override
    public void run() {
        while(true) {
            System.out.println("后台线程第" + i++ + "次执行!");
        }
    }
}

public class Test{
    public static void main(String[] args) {
        CommonThread ct = new CommonThread();
        DaemonThread dt = new DaemonThread();
        ct.start();
        dt.setDaemon(true); // 将dt设置为守护线程
        dt.start();
    }
}

 运行结果,可以看到,前台线程是保证执行完毕了,只剩下后台线程(守护线程)时,jvm会将其退出。

  • 54
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值