线程的状态及影响线程状态的一些方法

一、线程的状态

图片来源:牛客网 https://www.nowcoder.com/ta/review-java/review?tpId=31&tqId=21081&query=&asc=true&order=&page=13

1. 新建( new ):新创建了一个线程对象。

2. 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。

3. 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。

4. 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu timeslice 转到运行( running )状态。阻塞的情况分三种:

(一). 等待阻塞:运行( running )的线程执行 o . wait ()方法, JVM 会把该线程放 入等待队列( waitting queue )中。

(二). 同步阻塞:运行( running )的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。

(三). 其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t . join ()方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。            当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。

5. 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。

(查看Thread类的源码,可以看到在State枚举类中,有6种状态,NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED,但感觉这两种说法都可以讲得通,并不影响对并发编程的理解。)

二、影响线程状态的一些方法

sleep,wait,yield,notify,notifyAll,join,stop(已废弃),suspend(已废弃),resume(已废弃),interrupt等,大家学习下面的一些方法时,可以对照上面的几种状态图及其变化来学习。juc包中仍然有一些方法可以改变线程状态,将在juc包部分总结。

1.终止线程

终止线程有很多方法:(1)当 run 方法正常完成后线程终止。(2)设置一个标志位,改变标志位,使run方法结束(3)通过中断方法终止(4)使用 stop 方法强行终止线程(过期)

终止线程可以利用stop方法,但stop方法已经废弃,原因是stop方法会使得一个线程在执行过程中突然停止,然后释放锁,这就可能导致操作之后得操作读取到错误得数据,例如两个线程,读线程1和写线程2,2获得锁时,1不能读取,要等待2释放锁。而假如2在执行过程中,要为两个变量a和b赋值,如果在某个时间点,a已经被赋值,而b还未赋新值,这时调用stop方法,可能会使a为新值,b为旧值,而1获得锁,就会发生读取错误得情况。而要想安全得终止线程,可以设置一个volatile修饰得布尔变量作为标志位,这样在run方法中会读取这个变量,需要停止线程时,改变该标志位为指定停止的值,当run方法中读取到这个值时,就会停止了,这样可以保证线程中操作的原子性不被破坏。

设置标志位:https://blog.csdn.net/lyabc123456/article/details/81010995

2.线程中断

线程中断是使得线程终止的一种方式,但是线程中断不会使得线程立即退出,而是给线程一个通知,告知目标线程,希望它终止,而目标线程收到通知后,如何处理,则由目标线程自己决定(也可以不理睬),这就避免了stop方法可能导致的问题。

下面是《实战java高并发程序设计》中关于中断的方法说明:

实例代码,新建线程,并在主方法中利用interrupt方法中断,利用isInterrupted方法判断是否中断,如果中断,退出循环,即终止线程运行。如果想使用静态方法interrupted,可以将

if(Thread.currentThread().isInterrupted())

换为

if(Thread.interrupted()) //不需要传入参数,默认中断当前线程 
public class InterruptTest {
    public static void main(String[] args) throws InterruptedException{
        Thread t1 = new Thread(){
            @Override
            public void run(){
                while(true){
                    if(Thread.currentThread().isInterrupted()){
                        System.out.println("Interrupted!");
                        break;
                    }
                    try{
                        Thread.sleep(2000);
                    } catch (InterruptedException e){
                        System.out.println("Interrupted When Sleep");
                        //防止异常被捕获后中断标志被清除
                        Thread.currentThread().interrupt();
                    }
                }
            }
        };
        t1.start();
        Thread.sleep(2000);
        t1.interrupt();
    }
}

3.等待(wait)和通知(notify)及睡眠(sleep)

wait和notify是为了支持多线程之间的协作,这两个方法不是Thread类中的,而是Object类中的,所以理论上任何对象都可以调用这个方法,当一个线程中调用obj.wait()方法,那么这个线程就会停止执行,并释放锁,转为等待阻塞状态,进入这个对象的等待队列(一个等待队列中,可能会有多个线程等待这个对象),直到其他线程调用了obj.notify()方法,这个线程可能会被唤醒(从等待队列中随机选择一个线程),而notifyAll会唤醒所有等待的线程,当线程被唤醒后,要做的第一件事不是执行后续代码,而是尝试重新获得obj的监视器,如果暂时无法获得,这个线程将进入锁池,等待获得锁,获得锁后,才可以继续运行。

还有一点,这几个方法不是随便调用的,必须获得这个对象的监视器。

如下是一个例子,t1线程等待t2线程唤醒,而且wait方法会释放锁,t2线程唤醒t1线程,同时线程自然终止释放锁,t1被唤醒,且获得锁,继续运行,直到结束。

假如t1.start()和t2.start()换一下顺序,t1会因为无法被唤醒,而一直处于等待阻塞状态。

public class SimpleWN {
    final static Object object = new Object();

    public static class T1 extends Thread {


        public void run() {
            synchronized (object) {
                System.out.println(System.currentTimeMillis() + "T1");
                try {
                    System.out.println(System.currentTimeMillis() + "T1 wait for object");
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + "T1 end");
            }
        }
    }

    public static class T2 extends Thread {


        public void run() {
            synchronized (object) {
                System.out.println(System.currentTimeMillis() + "T2");
                object.notify();
                System.out.println(System.currentTimeMillis() + "T2 end");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        Thread t1 = new T1();
        Thread t2 = new T2();
        t1.start();
        t2.start();
    }
}

sleep 是线程类(Thread)的静态方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用 sleep 不会释放对象锁。 sleep()使当前线程进入阻塞状态,在指定时间内不会执行。wait,notify 和notifyAll 只能在同步控制方法或者同步控制块里面使用,而 sleep 可以在任何地方使用。sleep 必须捕获异常,而 wait,notify 和 notifyAll 不需要捕获异常。

注意:1.sleep 方法属于 Thread 类中方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态,因为线程调度机制恢复线程的运行也需要时间,一个线程对象调用了 sleep 方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响其他进程对象的运行。但在 sleep 的过程中过程中有可能被其他对象调用它的 interrupt(),产生 InterruptedException 异常,如果你的程序不捕获这个异常,线程就会异常终止,进入 TERMINATED 状态,如果你的程序捕获了这个异常,那么程序就会继续执行 catch 语句块(可能还有 finally 语句块)以及以后的代码。(见线程中断的例子)

2.sleep()方法是一个静态方法,也就是说他只对当前对象有效,通过 t.sleep()让 t对象进入 sleep,这样的做法是错误的,它只会是使当前线程被 sleep 而不是 t 线程。(很重要,不了解的话会发生费解的错误。)

4.挂起(suspend)和继续执行(resume)方法

suspend(已废弃)和resume(已废弃)是Thread类中的实例方法,执行了suspend的线程必须等到执行resume方法后才能继续执行,wait和notify是Object类的方法,且只能在同步方法或同步代码块中使用,而suspend和resume在任何地方都可以使用,所以相比而言要方便一些,但是之所以废弃,是因为suspend操作是不释放锁的,一个线程挂起后,其他线程想要访问它所占有的资源,都必须等待。如果这时,resume意外地在suspend()方法之前执行了,那么被挂起的线程就很可能无法再运行了,就会发生错误。如下的代码运行时,resume会先于suspend执行,t1线程将一直被挂起。

public class SuspendTest {
    public static void main(String[] args){
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("hello thread");
                    Thread.currentThread().suspend();
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t1.start();
        t1.resume();
    }
}

而想要完成这个功能,可以设置一个标志位,配个wait和notify方法,可以达到suspend和resume的效果。

5.等待线程结束(join)和谦让(yield)

join方法是一个Thread类的实例方法,可以让一个线程等待另一个线程执行完毕后再继续执行(前者进入等待阻塞状态),比如一个线程的输入可能非常依赖于另外一个或者多个线程的输出,所以需要等待另一个线程执行完毕。这个方法比较好理解,如下例子中,主线程要等待at线程执行完毕后,才能使i的值为1000000,否则将是一个很小的值。

public class JoinMain {
    public static volatile int i = 0;
    public static class AddThread extends Thread{
    public void run() {
            for(i=0;i<10000000;i++){
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        AddThread at = new AddThread();
        at.start();
        at.join();
        System.out.println(i);
    }
}

yield是Thread类的静态方法,它会使当前线程让出CPU,从“运行中”状态变为“可运行”状态,当然,让出CPU后,该线程还会进行CPU资源的争夺,如果争夺到CPU,还会继续运行。

参考:

1.https://www.nowcoder.com/ta/review-java/review?tpId=31&tqId=21081&query=&asc=true&order=&page=13

2.《实战java高并发程序设计》

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值