Java~Thread的API是如何改变线程的状态和分析解决生产者|消费者模型中的假死现象与wait条件改变异常

    科技2022-07-21  144

    文章目录

    首先了解Thread的API是如何改变线程的状态的假死现象解决 wait条件改变异常

    首先了解Thread的API是如何改变线程的状态的

    1)新创建一个新的线程对象后,再调用它的start()方法,系统会为此线程分配CPU资源,使其处于Runnable(可运行)状态,这是一个准备运行的阶段。如果线程抢占到CPU资源,此线程就处于Running(运行)状态。2)Runnable状态和Running状态可相互切换,因为有可能线程运行一段时间后,有其他高优先级的线程抢占了CPU资源,这时此线程就从Running状态变成Runnable状态。线程进入Runnable状态大体分为如下5种情况: -> 调用sleep()方法后经过的时间超过了指定的休眠时间。 ->线程调用的阻塞IO已经返回,阻塞方法执行完毕。 ->线程成功地获得了试图同步的监视器。 ->线程正在等待某个通知,其他线程发出了通知。 ->处于挂起状态的线程调用了resume恢复方法。3 )Blocked是阻塞的意思,例如遇到了一个IO操作,此时CPU处于空闲状态,可能会转而把CPU时间片分配给其他线程,这时也可以称为“暂停”状态。Blocked状态结束后,进人Runnable状态,等待系统重新分配资源。出现阻塞的情况大体分为如下5种: ->线程调用sleep方法,主动放弃占用的处理器资源。 ->线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞。 ->线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。 ->线程等待某个通知。 ->程序调用了suspend方法将该线程挂起。此方法容易导致死锁,尽量避免使用该方法。4 ) run()方法运行结束后进入销毁阶段,整个线程执行完毕。 每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度;反之,一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒。

    假死现象

    “假死”的现象其实就是线程进入 WAITING等待状态。如果全部线程都进人WAITING状态,则程序就不再执行任何业务功能了,整个项目呈停止状态。这在使用生产者与消费者模式时经常遇到。服务类 public class ServiceResources { private List<String> list = new ArrayList<>(); synchronized public void push() throws InterruptedException { while (list.size() > 0) { //说明有产品 System.out.println(Thread.currentThread().getName() + " 生产者进入睡眠"); this.wait(); } System.out.println(Thread.currentThread().getName() + " 生产"); list.add("anything"); this.notify(); } synchronized public void take() throws InterruptedException { while (list.size() == 0) { //说明此时没有产品 System.out.println(Thread.currentThread().getName() + " 消费者进入睡眠"); this.wait(); } System.out.println(Thread.currentThread().getName() + " 消费"); list.remove(0); this.notify(); } } 多线程 public class ThreadP extends Thread { private ServiceResources resources; public ThreadP(ServiceResources resources, String name) { this.resources = resources; this.setName(name); } @Override public void run() { while (true) { try { resources.push(); } catch (InterruptedException e) { break; } } } } public class ThreadC extends Thread { private ServiceResources resources; public ThreadC(ServiceResources resources, String name) { this.resources = resources; this.setName(name); } @Override public void run() { while (true) { try { resources.take(); } catch (InterruptedException e) { break; } } } } 运行类 public class Run { public static void main(String[] args) throws InterruptedException { ServiceResources resources = new ServiceResources(); //创建多个生产者和多个消费者 ThreadP threadP1 = new ThreadP(resources, "P1"); ThreadP threadP2 = new ThreadP(resources, "P2"); ThreadC threadC1 = new ThreadC(resources, "C1"); ThreadC threadC2 = new ThreadC(resources, "C2"); threadP1.start(); threadP2.start(); threadC1.start(); threadC2.start(); Thread.sleep(5000); threadP1.interrupt(); threadP2.interrupt(); threadC1.interrupt(); threadC2.interrupt(); //获得所有线程的状态 Thread[] threadArray = new Thread[Thread.currentThread().getThreadGroup().activeCount()]; Thread.currentThread().getThreadGroup().enumerate(threadArray); for (int i = 0; i < threadArray.length; i++) { System.out.println(threadArray[i].getName() + " " + threadArray[i].getState()); } } } 运行结果 从打印的信息来看,呈假死状态的进程中所有的线程都呈WAITING状态。为什么会出现这样的情况呢?在代码中已经用了wait/notify啊?在代码中确实已经通过wait/notify进行通信了,但不保证notify唤醒的是异类,也许是同类,比如“生产者”唤醒“生产者”,或“消费者”唤醒“消费者”这样的情况。如果按这样情况运行的比率积少成多,就会导致所有的线程都不能继续运行下去,大家都在等待,都呈WAITING状态,程序最后也就呈“假死”状态,不能继续运行下去了。

    解决

    解决“假死”的情况很简单,将P.java和C.java文件中的notifyO改成notifyAll0)方法即可,它的原理就是不光通知同类线程,也包括异类。这样就不至于出现假死的状态了,程序会一直运行下去。

    wait条件改变异常

    这个问题多见与少生产多消费中, 简单来说就是生产赶不上消费, 导致在消费的时候发生异常服务类 public class ServiceResources { private List<String> list = new ArrayList<>(); synchronized public void push() throws InterruptedException { if (list.size() > 0) { //说明有产品 System.out.println(Thread.currentThread().getName() + " 生产者进入睡眠"); this.wait(); } System.out.println(Thread.currentThread().getName() + " 生产"); list.add("anything"); this.notifyAll(); } synchronized public void take() throws InterruptedException { if (list.size() == 0) { //说明此时没有产品 System.out.println(Thread.currentThread().getName() + " 消费者进入睡眠"); this.wait(); } System.out.println(Thread.currentThread().getName() + " 消费"); list.remove(0); this.notifyAll(); } } 运行类 public class Run { public static void main(String[] args) throws InterruptedException { ServiceResources resources = new ServiceResources(); //创建一个生产者和多个消费者 ThreadP threadP1 = new ThreadP(resources, "P1"); ThreadC threadC1 = new ThreadC(resources, "C1"); ThreadC threadC2 = new ThreadC(resources, "C2"); ThreadC threadC3 = new ThreadC(resources, "C3"); ThreadC threadC4 = new ThreadC(resources, "C4"); ThreadC threadC5 = new ThreadC(resources, "C5"); threadP1.start(); threadC1.start(); threadC2.start(); threadC3.start(); threadC4.start(); threadC5.start(); Thread.sleep(5000); //获得所有线程的状态 Thread[] threadArray = new Thread[Thread.currentThread().getThreadGroup().activeCount()]; Thread.currentThread().getThreadGroup().enumerate(threadArray); for (int i = 0; i < threadArray.length; i++) { System.out.println(threadArray[i].getName() + " " + threadArray[i].getState()); } } }

    比问题的出现就是因为在服务类中使用了if语句作为条件判断,代码如下:

    if (list.size() == 0) { //说明此时没有产品 System.out.println(Thread.currentThread().getName() + " 消费者进入睡眠"); this.wait(); }

    因为条件发生改变时并没有得到及时的响应,所以多个呈wait状态的线程被唤醒,继而执行list.remove(0)代码而出现异常。解决这个办法是,将if改成while语句即可。

    Processed: 0.014, SQL: 8