本节内容:
线程的状态
wait/notify/notifyAll/sleep办法的介绍
如何正确进行线程
有哪些实现生产者消费者的办法
<span id=”jump1″>线程的状态/span>
线程一共有六种状态,别离是New(新建)、Runnable(可运行)、Blocked(阻塞)、Waiting(期待)、Timed WaitIng(计时期待)、Terminated(终结)
状态流转图
NEW(新建)
当咱们new一个新线程的时候,如果还未调用start()办法,则该线程的状态就是NEW,而一旦调用了start()办法,它就会从NEW变成Runnable
Runnable(可运行)
java中的可运行状态分为两种,一种是可运行,一种是运行中,如果以后线程调用了start()办法之后,还未获取CPU工夫片,此时该线程处于可运行状态,期待被调配CPU资源,如果取得CPU资源后,该线程就是运行状态。
Blocked(阻塞)
java中的阻塞也分三种状态:Blocked(被阻塞)、Waiting(期待)、Timed Waiting(计时期待),这三种状态统称为阻塞状态。
- Blocked状态(被阻塞):从联合图中能够看出从Runnable状态进入Blocked状态只有进入synchronized爱护的代码时,没有获取到锁monitor锁,就会处于Blocked状态
- Time Waiting(计时期待):Time Waiting和Waiting状态的区别是有没有工夫的限度,一下状况会进入Time Waiting:
- 设置了工夫参数的Thread.sleep(long millis)
- 设置了工夫参数的Object.wait(long timeout)
- 设置了工夫参数的Thread.join(long millis)
- 设置了工夫参数的LockSupport.parkNanos(long millis)和LockSupport.parkUntil(long deadline)
- Waiting状态(期待):线程进入Waiting状态有三种状况,别离是:
- 没有设置Timeout的Object.wait()办法
- 没有设置Timeout的Thread.join()办法
- LockSupport.park()办法
Blocked状态仅仅针对synchronized monitor锁,如果获取的锁是ReentrantLock等锁时,线程没有抢到锁就会进入Waiting状态,因为实质上它执行的是LockSupport.park()办法,所以会进入Waiting办法,同样Object.wait()、Thread.join()也会让线程进入waiting状态。Blocked和Waiting不同的是blocked期待其余线程开释monitor锁,而Waiting则是期待某个条件,相似join线程执行结束或者notify()\notifyAll()。
上图中能够看出处于Waiting、Time Waiting的线程调用notify()或者notifyAll()办法后,并不会进入Runnable状态而是进入Blocked状态,因为唤醒处于Waiting、Time Waiting状态的线程的线程在调用notify()或者notifyAll()时候,必须持有该monitor锁,所以处于Waiting、Time Waiting状态的线程被唤醒后,就会进入Blocked状态,直到执行了notify()\notifyAll()的线程开释了锁,被唤醒的线程才能够去争夺这把锁,如果抢到了就从Blocked状态转换到Runnable状态
Terminated(终结)
进入这个状态的线程分两种状况:
- run()办法执行结束,失常退出
- 产生异样,终止了run()办法。
<span id=”jump2″>wait/notify/notifyAll办法的应用</span>
首先wait办法必须在sychronized爱护的同步代码中应用,在wait办法的源码正文中就有说:
在应用wait办法是必须把wait办法写在synchronized爱护的while代码中,并且始终判断执行条件是否满足,如果满足就持续往下执行,不满足就执行wait办法,而且执行wait办法前,必须先持有对象的synchronized锁.
下面次要是两点:
- wait办法要在synchronized同步代码中调用.
- wait办法应该总是被调用在一个循环中
咱们先剖析第一点,联合以下场景剖析为什么要这么设计
public class TestDemo { private ArrayBlockingQueue<String> storage = new ArrayBlockingQueue(8); public void add(String data){ storage.add(data); notify(); } public String remove() throws InterruptedException { //wait不必synchronized关键字爱护,间接调用, while (storage.isEmpty()){ wait(); } return storage.remove(); } }
上述代码是一个简略的基于ArrayBlockingQueue实现的生产者、消费者模式,生产者调用add(String data)办法向storage中增加数据,消费者调用remove()办法从storage中生产数据.
代码中咱们能够看到如果wait办法的调用没有用synchronized爱护起来,那么就可能产生一下场景状况:
- 消费者线程调用remove()办法判断storage是否为空,如果是就调用wait办法,消费者线程进入期待,然而这就可能产生消费者线程调用完storage.isEmpty()办法后就被调度器暂停了,而后还没来得及执行wait办法.
- 此时生产者线程开始运行,开始执行了add(data)办法,胜利的增加了data数据并且执行了notify()办法,然而因为之前的消费者还没有执行wait办法,所以此时没有线程被唤醒.
- 生产者执行结束后,方才被调度器暂停的消费者再回来执行wait办法,并且进入了期待,此时storage中曾经有数据了.
以上的状况就是线程不平安的,因为wait办法的调用错过了notify办法的唤醒,导致应该被唤醒的线程无奈收到notify办法的唤醒.
正是因为wait办法的调用没有被synchronized关键字爱护,所以他和while判断不是原子操作,所以就会呈现线程平安问题.
咱们把以上代码改成如下,就实现了线程平安
public class TestDemo { private ArrayBlockingQueue<String> storage = new ArrayBlockingQueue(8); public void add(String data){ synchronized (this){ storage.add(data); notify(); } } public String remove() throws InterruptedException { synchronized (this){ while (storage.isEmpty()){ wait(); } return storage.remove(); } } }
咱们再来剖析第二点wait办法应该总是被调用在一个循环中?
之所以将wait办法放到循环中是为了避免线程“虚伪唤醒“(spurious wakeup),线程可能在没有被notify/notyfiAll,也没有被中断或者超时的状况下被唤醒,尽管这种概率产生十分小,然而为了保障产生虚伪唤醒的正确性,所以须要采纳循环构造,这样即使线程被虚伪唤醒了,也会再次查看while的
条件是否满足,不满足就调用wait办法期待.
为什么wait/notify/notifyAll被定义在Object类中
java中每个对象都是一个内置锁,都持有一把称为monitor监视器的锁,这就要求在对象头中有一个用来保留锁信息的地位.这个锁是对象级别的而非线程级别的,wait/notify/notifyAll也都是锁级别的操作,它们的锁属于对象,所以把它们定义在Object中最合适.
wait/notify和sleep办法的异同
相同点:
- 它们都能够让线程阻塞
- 它们都能够响应interrupt中断:在期待过程中如果收到中断信号,都能够进行响应并抛出InterruptedException异样
不同点:
- wait办法必须在synchronized同步代码中调用,sleep办法没有这个要求
- 调用sleep不会开释monitor锁,调用wait办法就开释monitor锁
- sleep要求期待一段时间后会主动复原,然而wait办法没有设置超时工夫的话会始终期待,直到被中断或者被唤醒,否则不能被动复原
- wait/notify是Object办法,sleep是Thread的办法
<span id=”jump3″>如何正确进行线程</span>
正确的进行线程形式是通过应用interrupt办法,interrupt办法仅仅起到了告诉须要被中断的线程的作用,被中断的线程有齐全的自主权,它能够立即进行,也能够执行一段时间再进行,或者压根不进行.这是因为java心愿程序之间能相互告诉、合作的实现工作.
interrupt()办法的应用
public class InterruptDemo implements Runnable{ public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new InterruptDemo()); thread.start(); Thread.sleep(5); thread.interrupt(); } @Override public void run() { int i =0; while (!Thread.currentThread().isInterrupted() && i<1000){ System.out.println(i++); } } }
上图中通过循环打印0~999,然而理论运行并不会打印到999,因为在线程打印到999之前,咱们对线程调用了interrupt办法使其中断了,而后依据while中的判断条件,办法提前终止,运行后果如下:
其中如果是通过sleep、wait办法使线程陷入休眠,处于休眠期间的线程如果被中断是能够感触到中断信号的,并且会抛出一个InterruptException异样,同时革除中断信号,将中断标记位设置为false.
<span id=”jump3″>有哪些实现生产者消费者的办法</span>
生产者消费者模式是程序设计中常见的一种设计模式,咱们通过下图来了解生产者消费者模式:
应用BolckingQueue实现生产者消费者模式
通过利用阻塞队列ArrayBlockingQueue实现一个简略的生产者消费者模式,创立两个线程用来生产对象,两个线程用来生产对象,如果ArrayBlockingQueue满了,那么生产者就会阻塞,如果ArrayBlockingQueue为空,那么消费者线程就会阻塞.线程的阻塞和唤醒都是通过ArrayBlockingQueue来实现的.
public void MyBlockingQueue1(){ BlockingQueue<Object> queue=new ArrayBlockingQueue<>(10); Runnable producer = () ->{ while (true){ try { queue.put(new Object()); } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(producer).start(); new Thread(producer).start(); Runnable consumer = () ->{ while (true){ try { queue.take(); } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(consumer).start(); new Thread(consumer).start(); }
应用Condition实现生产者消费者模式
如下代码其实也是相似ArrayBlockingQueue外部的实现原理.
如下代码所示,定义了一个队列容量是16的的queue,用来存放数据,定义一个ReentrantLock类型的锁,并在Lock锁的根底上创立了两个Condition,一个是notEmpty一个是notFull,别离代表队列没有空和没有满的条件,而后就是put和take办法.
put办法中,因为是多线程拜访环境,所以先上锁,而后在while条件中判断queue中是否曾经满了,如果满了,则调用notFull的await()办法阻塞生产者并开释Lock锁,如果没有满则往队列中放入数据,并且调用notEmpty.singleAll()办法唤醒所有的消费者线程,最初在finally中开释锁.
同理take办法和put办法相似,同样是先上锁,在判断while条件是否满足,而后执行对应的操作,最初在finally中开释锁.
public class MyBlockingQueue2 { private Queue queue; private int max; private ReentrantLock lock=new ReentrantLock(); private Condition notEmpty = lock.newCondition(); private Condition notFull =lock.newCondition(); public MyBlockingQueue2(int size){ this.max =size; queue = new LinkedList(); } public void put(Object o) throws InterruptedException { lock.lock(); try { while (queue.size() == max) { notFull.await(); } queue.add(o); //唤醒所有的消费者 notEmpty.signalAll(); } finally { lock.unlock(); } } public Object take() throws InterruptedException{ lock.lock(); try { //这里不能改用if判断,因为生产者唤醒了所有的消费者, //消费者唤醒后,必须在进行一次条件判断 while (queue.size() == 0) { notEmpty.await(); } Object remove = queue.remove(); //唤醒所有的生产者 notFull.signalAll(); return remove; }finally { lock.unlock(); } } }
应用wait/notify实现生产者消费者模式
如下代码所示,利用wait/notify实现生产者消费者模式次要是在put和take办法上加了synchronized锁,并且在各自的while办法中进行条件判断
public class MyBlockingQueue3 { private int max; private Queue<Object> queue; public MyBlockingQueue3(int size){ this.max =size; this.queue=new LinkedList<>(); } public synchronized void put(Object o) throws InterruptedException { while(queue.size() == max){ wait(); } queue.add(o); notifyAll(); } public synchronized Object take() throws InterruptedException { while (queue.size() == 0){ wait(); } Object remove = queue.remove(); notifyAll(); return remove; } }
以上就是三种实现生产者消费者模式的形式,第一种比较简单间接利用ArrayBlockingQueue外部的特色实现生产者消费者模式的实现场景,第二种是第一种背地的实现原理,第三种利用synchronzied实现.