JAVA多线程系列之线程的生命周期和常用方法(篇三)

   线程在其生命周期中,可能随时处于以下五种状态之一:

  1. 创建(new)状态: 当使用new创建一个新的线程对象时,该线程处于创建状态,此时系统还没有为它分配资源。
  2. 就绪(runnable)状态: 此时线程调用了start()方法,进入可执行状态,等待CPU进行调度
  3. 运行(running)状态: 线程获得CPU资源,执行run()方法
  4. 阻塞(blocked)状态: 因sleep()或wait()或其他线程join()或等待IO等原因,暂时停止执行,让出CPU资源进入阻塞状态
  5. 终止(dead)状态: 线程执行完毕,或被杀死,此时线程销毁
    这里写图片描述
       有意思的是阻塞有三种阻塞:

1.持有锁资源式阻塞:这种阻塞方式不会释放当前线程已持有的锁资源

2.等待池:进入等待池的线程会将持有的锁资源释放

3.锁池:进入锁池的线程,只需获取到执行所需的锁资源就可以进入可执行状态了,有种“万事俱备只欠东风”的感觉,但它仍处于等待状态(Block)中

   实现Runnable接口或继承Thread可以得到一个线程类,new一个该类的实例出来,线程就进入了创建(new)状态。调用线程的start()方法,此线程进入就绪(runnable)状态。就绪状态只是说该线程处于随时可运行的状态,但具体是否运行,何时运行由cpu决定,cpu没有”临幸”就只能等着。当时间片用完、sleep()结束、join()加入的其他线程执行完毕(并成功获取到锁)、IO输入完成、线程拿到别的线程释放的自己所需的同步锁等情况出现时,线程进入就绪状态。需要注意的是,yield()方法是线程主动建议cpu结束自己的时间片,让给其他线程执行,但是否采纳建议,由CPU决定

   线程调度程序从可运行池(Runnable)中选择一个线程,为其分配cpu资源,进入运行(running)状态。这也是线程进入运行状态的唯一一种方式。

   因某种原因,线程进入不可继续执行状态,这种状态被称为阻塞(blocked)状态
   以下情况会出现线程阻塞状态:

1.线程调用sleep()方法,主动放弃占用的处理器资源,但不会释放自己所持有的锁资源,等待sleep()时间到重新进入可执行状态

2.线程等待IO输入,等待IO输入完成重新进入可执行状态,IO阻塞是不会释放锁的

3.线程试图获得一个同步锁(synchronized),但该同步锁正被其他线程所持有,等待获取锁后重新进入可执行状态。

4.线程被wait()方法阻塞同时会释放自己持有的同步锁,然后等待notify()方法唤醒或wait()时间到,此时进入同步锁阻塞状态(第3种阻塞情况),获取到锁之后才能进入可执行状态

5.线程被其他线程通过join()方法打断执行,待通过join()加入的线程执行完毕后再重新进入可执行状态,由于join()底层是wait()方法,因此join()会释放锁

   当线程的run()方法完成时,或者线程抛出一个未捕获的Exception或Error,或者通过stop()方法结束该线程(stop极度危险),我们就认为它进入终止(dead)状态。这个线程对象也许是活的。但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。在一个终止的线程上再次调用start()方法,会抛java.lang.IllegalThreadStateException异常。

   下面我们来盘点一下多线程中常用的方法

1.yeild()

   线程调用yield()方法意味着线程告诉JVM自己正在执行的任务并不紧急,”暗示”虚拟机自己可以让出CPU资源给其他线程使用,但具体是否会出让资源并不保证,由CPU决定是否切换线程。当然了,即使出让CPU,线程也是转入可执行状态,并不会释放自己所持有的锁资源,随时准备再次执行

2.wait()和notify()/notifyAll()

   这三个方法都是java.lang.Object的方法

2.1 锁池和等待池

   在讨论这三种方法之前,首先讨论两个概念,这两个概念实际上我们之前也有提过:

   锁池:假设线程A已经拥有了某个锁资源,而其它的线程想要调用这个锁资源对象的某个synchronized方法(或者synchronized块),由于这些线程在进入synchronized方法(或者synchronized块)之前必须先获得该锁的拥有权,但是该锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中,当锁资源被线程A释放后,锁池中的线程会竞争 锁资源,获取到锁的线程将进入可执行状态。参见上文导致阻塞的第3种情况

   等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中,等待池中的线程在被唤醒前不会竞争锁资源。参见上文导致阻塞的第4种情况

2.2 wait()和notify()/notifyAll()

   对某个 锁资源对象 调用wait()方法会使当前线程进入该锁对象的等待池中,并释放线程自己所持有的该 锁资源对象 的锁,直到其他线程调用此 锁资源对象 的notify()方法或 notifyAll()方法唤醒它。wait()方法当然也可以设置一个long类型的等待时间,一旦超时也会自动唤醒

   对 锁资源对象 调用notify()方法,将从该 锁资源对象 的等待池中 随机 选择一个线程进入该 锁资源对象 的锁池中,和锁池中的其他资源竞争锁

   对 锁资源对象 调用notifyAll ()方法,会将该 锁资源对象 的等待池中所有的线程都唤醒到锁池中一起来竞争锁资源

   需要注意的是,由于wait()方法会释放自己持有的锁资源,因此,该方法必须放在synchronized同步代码块中使用,否则抛出IllegalMonitorStateException异常,notify/notifyAll方法也是如此

   另外,调用notify/notifyAll方法后,被唤醒的线程也不会立即进入可执行状态,只是被从锁对象的等待池中移入了锁对象的锁池。接下来该线程会尝试和锁池中的其他线程竞争锁资源,如果获取锁成功,才能进入可执行状态

   下面是一个简单的示例:

class Waiter implements Runnable{
private String msg;

    public Waiter(String msg) {
        super();
        this.msg = msg;
    }

    @Override
    public void run() {
        synchronized (msg) {
            System.out.println(Thread.currentThread().getName()+"获取msg控制权");
            try {
                Thread.sleep(10);
                msg.wait();
                System.out.println(Thread.currentThread().getName()+"被恢复");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Notifyer implements Runnable {
    private String msg;

    public Notifyer(String msg) {
        super();
        this.msg = msg;
    }

    @Override
    public void run() {
        synchronized (msg) {
            System.out.println("notify开始工作");
            try {
                Thread.sleep(100);
                msg.notify();
                System.out.println("notify工作完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
    @Test
    public void test_2() {
        String msg=new String("------");
        new Thread(new Waiter(msg),"waiter").start();
        new Thread(new Notifyer(msg),"notify").start();
        while (true){}
    }

2.3 由notify()引发的死锁

    notify()在使用不当时可能导致死锁 ,如下例:
//生产者
class Producer implements Runnable {
    private String threadName;
    //缓冲队列,存储生产者产出的Integer
    List<Integer> cache;

    public Producer(List<Integer> cache,String threadName) {
        this.cache = cache;
        this.threadName=threadName;
    }

    @Override
    public void run() {
        //生产者线程一旦被唤醒就会不断生产
        while (true) {
            produce();
        }
    }

    private void produce() {
        synchronized (cache) {
            try {
                System.out.println("**********生产者"+this.threadName+"获取锁**********" );
                //首先判断缓冲队列中是否已存在数据,如果存在就把自己移入缓冲队列的等待池中
                while (cache.size() > 0) {
                    System.out.println("缓存不为空,生产者"+this.threadName+"进入等待池" );
                    cache.wait();
                }
                // 跳出循环,意味着cahce为空,接下来一秒生产一条消息
                Thread.sleep(1000);
                System.out.println("生产者"+this.threadName+"生产数据!");
                cache.add(new Random().nextInt());
                //从等待池中  随机  唤醒一个线程
                cache.notify();
                System.out.println("**********生产者 "+this.threadName+"从等待池中随机唤醒一个线程并释放锁**********");
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
class Consumer implements Runnable {
    private String threadName;
    //缓冲队列,消费者从中获取Integer消费
    List<Integer> cache;

    public Consumer(List<Integer> cache,String threadName) {
        this.cache = cache;
        this.threadName=threadName;
    }

    @Override
    public void run() {
        //消费者线程一旦被唤醒就会不断消费
        while (true) {
            consume();
        }
    }

    private void consume() {
        synchronized (cache) {
            try {
                System.out.println("**********消费者"+this.threadName+"获取锁**********" );
                //首先判断缓冲队列中是否为空,如果没有产品就把自己移入缓冲队列的等待池中
                while (cache.isEmpty()) {
                    System.out.println("缓存为空,消费者"+this.threadName+"进入等待池" );
                    cache.wait();
                }
                //消费产品
                System.out.println("消费者 "+this.threadName+"消费了数据"+cache.remove(0) + "!");
                cache.notify();
                System.out.println("**********消费者 "+this.threadName+"从等待池中随机唤醒一个线程并释放锁**********");
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

   如上例的消费者和生产者,如果我们只启动一个消费者和一个生产者两条线程,那么一切正常,等待池中始终只有一个线程,要么就是生产者唤醒消费者,要么消费者唤醒生产者,不会发生死锁:

@Test
public void test() {
    //一个消费者一个生产者
    List<Integer> cache=new ArrayList<>();
    new Thread(new Consumer(cache,"C")).start();
    new Thread(new Producer(cache,"P")).start();
    while (true){}
}

   如果开启了一个生产者和两个消费者,且使用notify()的方式来唤醒线程,那么则可能发生死锁:

@Test
public void test_2() {
    //两个消费者一个生产者
    List<Integer> cache=new ArrayList<>();
    new Thread(new Consumer(cache,"C1")).start();
    new Thread(new Consumer(cache,"C2")).start();
    new Thread(new Producer(cache,"P")).start();
    while (true){}
}

   输出结果如下:
这里写图片描述

   解决方法是将notify换成notifyAll

3.interrupt()

   一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。interrupt翻译过来是”终断,打断”的意思,它被用于终止线程,但 interrupt()并不直接中断线程,而是通过给线程设置一个中断标识(每个线程中都有一个与中断相关联的 boolean 属性,用来表示线程的中断状态),传达”线程应该被中断了”的信息,然后由我们自己编写的代码进行中断检查,再决定是否中断,他只是设置了一个特殊的标识位,并未起到任何实际作用。(实际上,interrupt()不能不能真正意义上地中断正在运行过程中的线程,但能通过抛出中断异常的方式真正中断处于阻塞状态中的线程)

   那么哪些场景中需要用到中断机制呢?

1.很多线程的运行模式是死循环,比如在生产者/消费者模式中,消费者主体就是一个死循环,它不停的从队列中接受任务,执行任务,在停止消费者程序时,我们需要一种”优雅”的方法以关闭该线程。

2.抢12306的火车票,我们可能开启多个线程购买火车票,只要有一个线程买到了,我们就需要中断其他线程。

3.从第三方服务器查询一个结果,我们希望在限定的时间内得到结果,如果得不到,我们会希望取消该任务。此时需要中断查询的线程
……

   在了解中断机制前,我们先来看看与中断标识相关的三个方法:

  • public boolean isInterrupted() //返回线程当前的中断状态
  • public void interrupt() //设置线程的中断状态(设为true)
  • public static boolean interrupted()//返回线程当前的中断状态,然后清除中断状态(设为false)

   对于处于不同状态的线程,调用 interrupt()的效果是不一样的:

1. 对处于运行(running)状态的线程使用 interrupt()
   interrupt()只是会设置线程的中断标志位,没有任何其它作用,不会打断线程的执行,为了能够正常终止线程,编写线程时应该在合适的位置检查中断标志位,比如:

//生产者
class Producer implements Runnable {
    private String threadName;
    //缓冲队列,存储生产者产出的Integer
    List<Integer> cache;

    public Producer(List<Integer> cache,String threadName) {
        this.cache = cache;
        this.threadName=threadName;
    }

    @Override
    public void run() {
        long beginTime=System.currentTimeMillis();
        //生产者线程会不断生产,直至被打断
        while (!Thread.currentThread().isInterrupted()) {
            //5s后打断生产者
            if(System.currentTimeMillis()-beginTime>500){
                Thread.currentThread().interrupt();
                System.out.println("打断线程"+Thread.currentThread().isInterrupted());
            }
            produce();
        }
    }

    private void produce() {
        synchronized (cache) {
            try {
                cache.add(new Random().nextInt());
                System.out.println("生产者生产了数据" );
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
@Test
public void test() {
    List<Integer> cache=new ArrayList<>();
    Thread thread=new Thread(new Producer(cache,"P"));
    thread.start();
    while (true){}
}

2. 对处于阻塞(block)状态的线程使用 interrupt()
   对于调用了sleep(), wait(), join()等方法而进入阻塞状态的线程,若此时调用线程的interrupt(),会先将线程的中断标记设为true。由于处于阻塞状态,中断标记会被清除(重置为false),同时抛出一个InterruptedException异常,从而退出阻塞(block)状态(抛出异常会导致被阻塞的线程重新开始执行,不过此时执行的是catch块中的代码)

@Override
public void run() {
    try {
        while (true) {
	    // 执行任务
        }
    } catch (InterruptedException e) {
        // 由于产生InterruptedException异常,退出while(true)循环,线程终止!
    }
}

   Thread.stop, Thread.suspend, Thread.resume等中断线程的方法极度危险,现行版本的jdk已将其标为废弃方法,应尽量避免使用

4. sleep()

   Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)静态方法强制当前正在执行的线程休眠(暂停执行),以“减慢”线程执行速度。

   和wait()方法不同,线程睡眠并不会释放锁资源。因此当线程睡眠时间到期后,线程不需要进入锁池获取锁,而是直接进入可执行状态等待cpu调度

   需要注意的是sleep()中指定的时间是线程暂停运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就立即开始执行,只能保证睡眠到期后进入可执行状态。

5. join()

   在某些情况下,主线程创建并启动了子线程,如果子线程中需要进行大量的耗时运算,主线程往往将早于子线程结束之前结束,如果主线程想等待子线程执行完毕后,获得子线程中的处理完的某个数据,就要用到join方法了,它等待调用join方法的子线程结束,再继续执行主线程,这使得主线程和子线程的并行执行变为串行执行

   join底层是wait方法,所以子线程调用该方法时,暂停主线程的同时也会释放主线程持有的锁资源,而sleep在同步的方法中是不释放锁资源的,只有同步方法执行完毕,其他线程才可以执行。

6. currentThread ()

   Thread类提供了一个静态方法currentThread ()用以获得当前正在执行的线程,有点类似this

7. isAlive()

   线程处于创建(new)状态时,调用isAlive()方法返回false。在线程的run()方法结束之前,即没有进入死亡状态之前,线程调用isAlive()方法返回true.





本文原创https://blog.csdn.net/RicardoDing/article/details/82745910,转载请与本人联系:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值