第四章:线程状态
4.1 线程状态概述
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API
中java.lang.Thread.State
这个枚举中给出了六种线程状态:
-
状态描述
- NEW:至今尚未启动的线程
- RUNNABLE:正在 java 虚拟机中执行的线程
- BLOCKER:受阻塞并等待某个监视器锁的线程
- TIMED_WAITING:在指定的等待时间内都是处于休眠的状态
- WAITING:无限期地休眠
- TERMINATED:已退出的线程
-
状态图
-
阻塞和休眠的区别:
阻塞状态:具有CPU的执行资格,等待CPU空闲时执行
休眠状态:放弃CPU的执行资格,CPU空闲,也不执行
4.2 Waiting (无限等待)和线程间通信
Waiting状态在API中的介绍为:一个正在无限期等待另一个线程执行唤醒动作的线程。这里其实涉及了关于线程间通信的知识——等待唤醒机制。
- 用一个顾客买包子的案例比喻等待唤醒机制:
-
案例分析
-
创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait()方法,放弃使用cpu的执行,进入到WAITING状态(无限等待)
-
创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify()方法,唤醒顾客吃包子
-
注意:
- 顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
- 同步使用的锁对象必须保证唯一
- 只有锁对象才能调用wait和notify方法
-
Object类中的方法
-
void wait()
在其它线程调用 此对象的
notify()
方法或notifyAll()
方法前,导致当前线程等待 -
void wait(long miles)
有参数的话,就是计时等待,时间一到,不用唤醒也能自动醒来,跳到RUNNABLE或BLOCKED状态
-
void notify()
唤醒在此对象监视器上等待的单个线程,如果有多个线程,会随机唤醒一个
-
void notifyAll()
唤醒在此对象监视器上等待的所有线程
-
-
-
代码演示
public class WaitAndNotify { public static void main(String[] args) { //创建锁对象,保证唯一 Object obj = new Object(); //创建一个顾客线程 new Thread() { public void run() { //保证等待和唤醒的线程只能有一个执行,需要使用同步技术 synchronized(obj) { System.out.println("顾客:告知老板要的包子的种类和数量"); //调用wait方法,放弃CPU的执行,进入到WAITING无限等待状态 try { //编译期异常,但是不能用throws声明,因为父类的run方法没有抛异常声明,子类也不能 obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } //被唤醒之后的代码 System.out.println("顾客:开吃"); } } }.start(); //创建一个老板线程(生产者) new Thread() { public void run() { //老板花了5秒钟做包子 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(obj) { //包子已经做好,唤醒顾客吃包子 System.out.println("老板:包子已经做好了"); //调用notify()方法,唤醒顾客线程 obj.notify(); } } }.start(); } } //补充:刚开始老板线程一直在睡,那肯定是顾客线程先进入到同步代码块,顾客线程先开始执行 /*输出: 顾客:告知老板要的包子的种类和数量 老板:包子已经做好了 顾客:开吃 */
4.3 线程间通信代码实战
-
我们继续用4.2中的买包子作为实例,进行细致分析
-
首先包子这个资源是生产者和消费者共享的资源,通过判断包子的状态来决定是哪一个线程去运行
- 刚开始包子的状态为:没有 (false)—>吃货线程唤醒包子铺线程---->吃货线程等待---->包子铺线程开始做包子---->做好包子---->修改包子的状态为:有 (true)
- 有包子---->包子铺线程唤醒吃货线程---->包子铺线程等待---->吃货吃包子---->吃完包子---->修改包子的状态为:没有 (false)
- 没有包子---->吃货线程唤醒包子铺线程---->吃货线程等待---->包子铺线程做包子---->做好包子---->修改包子的状态为:有 (true)
-
代码分析:需要哪些类
-
资源类(包子类)。它的属性有
-
皮
-
馅
-
包子的状态:有(true),没有(false);
public class BaoZi { //皮 String pi; //馅 String xian; //包子的状态,初始状态为没有包子 boolean flag = false; }
-
-
生产者类(包子铺类):是一个线程类,可以继承Thread
-
设置线程任务(run):生产包子
-
对包子的状态进行判断
true:有包子
包子铺调用wait()方法进入等待状态,等吃货吃完包子
false:没有包子
包子铺生产包子
增加一些趣味:交替生产包子,有两种包子(i%2==0),
包子铺生产好了包子,修改包子的状态为true有,唤醒吃货线程起来吃包子
/* * 注意: * 包子铺线程和吃货线程关系--->通信(互斥) * 必须使用同步技术保证两个线程只能有一个在执行 * 锁对象必须保证唯一,可以使用包子对象作为锁对象 * 包子铺类和吃货类就需要把包子对象作为参数传进去 * 1. 需要在成员位置创建一个包子变量 * 2. 使用带参数构造方法,为这个包子变量赋值 */ public class BaoZiPu extends Thread { //1. 需要在成员位置创建一个包子变量,作为锁对象 private BaoZi bz; //2. 使用带参数构造方法,为这个包子变量赋值 public BaoZiPu(BaoZi bz) { this.bz = bz; } //设置线程任务(run), 生产包子 public void run() { //定义一个变量,这个变量决定到底要做什么馅的包子 int count = 0; //死循环,让包子铺一直生产包子 while(true) { //必须使用同步技术保证两个线程只能有一个在执行 synchronized(bz) { //对包子的状态进行判断 if(bz.flag == true) { //已经有包子了,不用生产,包子铺调用wait方法进入等待状态 try { bz.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //被吃货唤醒之后,证明没有包子了,又要开始生产包子 //增加一些趣味性:交替生产包子 if(count % 2 == 0) { //生产 薄皮三鲜馅饺子 bz.pi = "薄皮"; bz.xian = "三鲜馅"; }else { //生产 冰皮 牛肉大葱馅 bz.pi = "冰皮"; bz.xian = "牛肉大葱馅"; } count++; System.out.println("包子铺正在生产:" + bz.pi + bz.xian + "包子"); //生产包子需要3秒钟 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //包子铺生产好了包子,修改包子的状态为true有 bz.flag = true; //唤醒吃货线程起来吃包子 bz.notify(); //包子铺已经生产好了包子 System.out.println("包子铺已经生产好了" + bz.pi + bz.xian + "包子" + "吃货可以开始吃了"); } } } }
-
-
消费者类(吃货类):是一个线程类,可以继承Thread
-
设置线程任务(run):吃包子
-
对包子的状态进行判断
false:没有包子
吃货线程调用wait方法进入等待状态
true:有包子
吃货吃包子
吃货吃完包子
修改包子的状态为false没有
吃货唤醒包子铺线程,生产包子
public class ChiHuo extends Thread{ //1. 需要在成员位置创建一个包子变量,作为锁对象 private BaoZi bz; //2. 使用带参数构造方法,为这个包子变量赋值 public ChiHuo(BaoZi bz) { this.bz = bz; } //设置线程任务(run), 吃包子 public void run() { //死循环,让吃货一直吃包子 while(true) { //必须使用同步技术保证两个线程只能有一个在执行 synchronized(bz) { //对包子的状态进行判断 if(bz.flag == false) { //发现没有包子,吃货调用wait方法进入等待状态,等包子铺做好包子 try { bz.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //被包子铺唤醒之后,证明有包子吃了 System.out.println("吃货正在吃:" + bz.pi + bz.xian + "的包子"); //吃货吃完包子 //修改包子的状态为false没有 bz.flag = false; //吃货唤醒包子铺线程,继续生产包子 bz.notify(); System.out.println("吃货已经把:" + bz.pi + bz.xian + "的包子吃完了"); System.out.println("---------------------------------------------------------------------------"); } } } }
-
-
测试类:
- 包含main方法,程序执行的入口,启动程序
- 创建包子对象:创建包子铺程序,创建吃货程序,开启
public class test { public static void main(String[] args) { //创建包子对象,作为锁对象 BaoZi bz = new BaoZi(); //创建包子铺线程 new BaoZiPu(bz).start(); //开启吃货线程 new ChiHuo(bz).start(); } }
-
第五章:线程池
5.1 线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
-
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多的资源
-
线程池的底层原理
-
工作原理
5.2 线程池的使用
线程池:
JDK1.5
之后提供的
java.util.concurrent.Executors
是线程池的工厂类,用来生成线程池
- Executors类中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池
- 参数:
int nThreads
:创建线程池中的线程数量- 返回值:
ExecutorService
接口返回的是ExecutorService
接口的实现类对象,我们可以使用ExecutorService
接口接收(面向接口编程)java.util.concurrent.Executors
:线程池接口sumbit(Runnale task)
用来从线程池中获取线程,调用start方法,执行线程任务- void shutdown() 关闭销毁线程池的方法
- 线程池的使用步骤
- 使用线程池的工厂类Executors里边提供的静态方法
newFixedThreadPool
生产一个指定数量的线程池- 创建一个类,实现Runnable接口,重写run方法,设置线程任务
- 调用
ExecutorService
中的方法sumbit
,传递线程任务(实现类),开启线程,执行run方法- 调用
ExecutorService
中的方法shutdown销毁线程池(不建议使用)
代码演示:
-
写一个Runnable的子类,实现Runnable接口
public class RunnableImplements implements Runnable{ //2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务 @Override public void run() { System.out.println(Thread.currentThread().getName() + "创建一个新的线程"); } }
-
在main中实验
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class threadpool { public static void main(String[] args) { //1. 使用线程池的工厂类Executors里边提供的静态方法`newFixedThreadPool`生产一个指定数量的线程池 ExecutorService es = Executors.newFixedThreadPool(2); //3. 调用`ExecutorService`中的方法`sumbit`,传递线程任务(实现类),开启线程,执行run方法 es.submit(new RunnableImplements()); //线程池会一直开启,除非调用shutdown方法 //使用完线程之后,会自动把线程归还给任务(实现类),线程可以继续使用 es.submit(new RunnableImplements()); es.submit(new RunnableImplements()); //4. 调用`ExecutorService`中的方法shutdown销毁线程池(不建议使用) es.shutdown(); } } /*输出结果: pool-1-thread-1创建一个新的线程 pool-1-thread-2创建一个新的线程 pool-1-thread-2创建一个新的线程 因为线程池只能放2个线程,当Thread-2执行完任务之后,就会自动回到实现类中,再接着执行第二个任务 */