java线程间通信、线程池

第四章:线程状态

4.1 线程状态概述

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在APIjava.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() 关闭销毁线程池的方法
  • 线程池的使用步骤
  1. 使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定数量的线程池
  2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务
  3. 调用ExecutorService中的方法sumbit,传递线程任务(实现类),开启线程,执行run方法
  4. 调用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执行完任务之后,就会自动回到实现类中,再接着执行第二个任务
     */
    
  • 8
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值