本节内容:java
线程一共有六种状态,分别是New(新建)、Runnable(可运行)、Blocked(阻塞)、Waiting(等待)、Timed WaitIng(计时等待)、Terminated(终结) 设计模式
状态流转图安全
NEW(新建) 多线程
当咱们new一个新线程的时候,若是还未调用start()方法,则该线程的状态就是NEW,而一旦调用了start()方法,它就会从NEW变成Runnableide
Runnable(可运行)this
java中的可运行状态分为两种,一种是可运行,一种是运行中,若是当前线程调用了start()方法以后,还未获取CPU时间片,此时该线程处于可运行状态,等待被分配CPU资源,若是得到CPU资源后,该线程就是运行状态。spa
Blocked(阻塞) 线程
java中的阻塞也分三种状态:Blocked(被阻塞)、Waiting(等待)、Timed Waiting(计时等待),这三种状态统称为阻塞状态。设计
Blocked状态(被阻塞):从结合图中能够看出从Runnable状态进入Blocked状态只有进入synchronized保护的代码时,没有获取到锁monitor锁,就会处于Blocked状态code
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()方法
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()方法执行完毕,正常退出
首先wait方法必须在sychronized保护的同步代码中使用,在wait方法的源码注释中就有说:
在使用wait方法是必须把wait方法写在synchronized保护的while代码中,而且始终判断执行条件是否知足,若是知足就继续往下执行,不知足就执行wait方法,并且执行wait方法前,必须先持有对象的synchronized锁.
上面主要是两点:
wait方法要在synchronized同步代码中调用.
咱们先分析第一点,结合如下场景分析为何要这么设计
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方法的调用错过了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方法的异同
相同点:
它们均可以让线程阻塞
不一样点:
wait方法必须在synchronized同步代码中调用,sleep方法没有这个要求
调用sleep不会释放monitor锁,调用wait方法就释放monitor锁
sleep要求等待一段时间后会自动恢复,可是wait方法没有设置超时时间的话会一直等待,直到被中断或者被唤醒,不然不能主动恢复
正确的中止线程方式是经过使用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.
生产者消费者模式是程序设计中常见的一种设计模式,咱们经过下图来理解生产者消费者模式:
使用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实现.