第一章 等待唤醒机制

1.1 线程间通信

概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。

比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。

为什么要处理线程间通信:

多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。

如何保证线程间通信有效利用资源:

多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时,避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制

1.2 等待唤醒机制

什么是等待唤醒机制

这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。

就是在一个线程进行了规定操作后,就进入等待状态(wait()),等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时,如果需要,可以使用notifyAll()来唤醒所有的等待线程。

wait/notify就是线程间的一种协作机制。

等待唤醒中的方法

等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:

  1. wait:线程不再活动,不再参与调度,进入wait set中,因此不会浪费CPU资源,也不会去竞争锁了,这时的线程状态即是WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知notify)”在这个对象上等待的线程从wait set中释放出来,重新进入到调度队列(ready queue)中
  2. notify:则选取所通知对象的wait set中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
  3. notifyAll:则释放所通知对象的wait set上的全部线程。

注意:

哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。

总结如下:

  • 如果能获取锁,线程就从WAITING状态变成RUNNABLE状态;

  • 否则,从 wait set 出来,又进入 entry set,线程就从WAITING状态又变成BLOCKED状态

调用waitnotify方法需要注意的细节

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

1.3 生产者与消费者问题

等待唤醒机制其实就是经典的“生产者与消费者”的问题。

就拿生产包子消费包子来说等待唤醒机制如何有效利用资源:

包子铺线程生产包子,吃货线程消费包子。当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。接下来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。

代码演示:

包子资源类:

package com.dcxuexi.java20;/**** @Title BaoZi* @Description TOTD* @Auter DongChuang* @Date 2022/12/10 21:46* @Version 1.0.0*/
public class BaoZi {private String  pier ;private String  xianer ;boolean  flag = false ;//包子资源 是否存在  包子资源状态public String getPier() {return pier;}public void setPier(String pier) {this.pier = pier;}public String getXianer() {return xianer;}public void setXianer(String xianer) {this.xianer = xianer;}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}@Overridepublic String toString() {return "BaoZi{" +"pier='" + pier + '\'' +", xianer='" + xianer + '\'' +", flag=" + flag +'}';}
}

吃货线程类:

package com.dcxuexi.java20;/**** @Title ChiHuo* @Description TOTD* @Auter DongChuang* @Date 2022/12/10 21:47* @Version 1.0.0*/
public class ChiHuo extends Thread{private BaoZi bz;public ChiHuo(String name,BaoZi bz){super(name);this.bz = bz;}@Overridepublic void run() {while(true){synchronized (bz){if(bz.flag == false){//没包子try {bz.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("吃货正在吃"+bz.getPier()+bz.getXianer()+"包子");bz.flag = false;bz.notify();}}}
}

包子铺线程类:

package com.dcxuexi.java20;/**** @Title BaoZiPu* @Description TOTD* @Auter DongChuang* @Date 2022/12/10 21:48* @Version 1.0.0*/
public class BaoZiPu extends Thread{private BaoZi bz;public BaoZiPu(String name,BaoZi bz){super(name);this.bz = bz;}@Overridepublic void run() {int count = 0;//造包子while(true){//同步synchronized (bz){if(bz.flag == true){//包子资源  存在try {bz.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 没有包子  造包子System.out.println("包子铺开始做包子");if(count%2 == 0){// 冰皮  五仁bz.setPier("冰皮");bz.setXianer("五仁");}else{// 薄皮  牛肉大葱bz.setPier("薄皮");bz.setPier("牛肉大葱");}count++;bz.flag=true;System.out.println("包子造好了:"+bz.getPier()+bz.getXianer());System.out.println("吃货来吃吧");//唤醒等待线程 (吃货)bz.notify();}}}
}

测试类:

package com.dcxuexi.java20;/**** @Title Demo* @Description TOTD* @Auter DongChuang* @Date 2022/12/10 21:53* @Version 1.0.0*/
public class Demo {public static void main(String[] args) {//等待唤醒案例BaoZi bz = new BaoZi();ChiHuo ch = new ChiHuo("吃货",bz);BaoZiPu bzp = new BaoZiPu("包子铺",bz);ch.start();bzp.start();}
}

执行效果:

第二章 线程池

2.1 线程池思想概述

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?

Java中可以通过线程池来达到这样的效果。

2.2 线程池概念

  • 线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

由于线程池中有很多操作都是与优化资源相关的,我们在这里就不多赘述。我们通过一张图来了解线程池的工作原理:

合理利用线程池能够带来三个好处:

  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

2.3 线程池的使用

Java里面线程池的顶级接口是java.util.concurrent.Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在java.util.concurrent.Executors线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。

Executors类中有个创建线程池的方法如下:

  • public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)

获取到了一个线程池ExecutorService对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:

  • public Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行

Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。

使用线程池中线程对象的步骤:

  1. 创建线程池对象。
  2. 创建Runnable接口子类对象。(task
  3. 提交Runnable接口子类对象。(take task
  4. 关闭线程池(一般不做)。

Runnable实现类代码:

package com.dcxuexi.java20;/**** @Title MyRunnable* @Description TOTD* @Auter DongChuang* @Date 2022/12/10 22:21* @Version 1.0.0*/
public class MyRunnable implements Runnable{@Overridepublic void run() {System.out.println("我要一个教练");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("教练来了: " + Thread.currentThread().getName());System.out.println("教我游泳,交完后,教练回到了游泳池");}
}

线程池测试类:

package com.dcxuexi.java20;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/**** @Title ThreadPool_Demo* @Description TOTD* @Auter DongChuang* @Date 2022/12/10 22:21* @Version 1.0.0*/
public class ThreadPool_Demo {public static void main(String[] args) {// 创建线程池对象ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象// 创建Runnable实例对象MyRunnable r = new MyRunnable();//自己创建线程对象的方式// Thread t = new Thread(r);// t.start(); ---> 调用MyRunnable中的run()// 从线程池中获取线程对象,然后调用MyRunnable中的run()service.submit(r);// 再获取个线程对象,调用MyRunnable中的run()service.submit(r);service.submit(r);// 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。// 将使用完的线程又归还到了线程池中// 关闭线程池//service.shutdown();}
}

Java基础:线程池相关推荐

  1. java定时线程池_java 定时器线程池(ScheduledThreadPoolExecutor)的实现

    前言 定时器线程池提供了定时执行任务的能力,即可以延迟执行,可以周期性执行.但定时器线程池也还是线程池,最底层实现还是ThreadPoolExecutor,可以参考我的另外一篇文章多线程–精通Thre ...

  2. Java 自定义线程池

    Java 自定义线程池 https://www.cnblogs.com/yaoxiaowen/p/6576898.html public ThreadPoolExecutor(int corePool ...

  3. Java并发—线程池ThreadPoolExecutor基本总结

    原文作者:Matrix海子 原文地址:Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线 ...

  4. Java中线程池,你真的会用吗

    转载自   Java中线程池,你真的会用吗 在<深入源码分析Java线程池的实现原理>这篇文章中,我们介绍过了Java中线程池的常见用法以及基本原理. 在文中有这样一段描述: 可以通过Ex ...

  5. 自定义java线程池_我的Java自定义线程池执行器

    自定义java线程池 ThreadPoolExecutor是Java并发api添加的一项功能,可以有效地维护和重用线程,因此我们的程序不必担心创建和销毁线程,也不必关注核心功能. 我创建了一个自定义线 ...

  6. 我的Java自定义线程池执行器

    ThreadPoolExecutor是Java并发api添加的一项功能,可以有效地维护和重用线程,因此我们的程序不必担心创建和销毁线程,而将精力放在核心功能上. 我创建了一个自定义线程池执行程序,以更 ...

  7. java中线程池的几种实现方式

    1.线程池简介:     多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力.         假设一个服务器完成一项任务所需时间为:T1 ...

  8. Java中线程池,你真的会用吗?

    在<深入源码分析Java线程池的实现原理>这篇文章中,我们介绍过了Java中线程池的常见用法以及基本原理. 在文中有这样一段描述: 可以通过Executors静态工厂构建线程池,但一般不建 ...

  9. java异步线程池同时请求多个接口数据

    java异步线程池同时请求多个接口数据 一.适合的使用场景 复杂的网页爬虫,如要同时请求多个不同网页的数据,并且需要执行不同的数据处理,这个是非常合适的,执行线程传递的参数到最后callback是会附 ...

  10. java 线程池 复用机制,java的线程池框架及线程池的原理

    java 线程池详解 什么是线程池? 提供一组线程资源用来复用线程资源的一个池子 为什么要用线程池? 线程的资源是有限的,当处理一组业务的时候,我们需要不断的创建和销毁线程,大多数情况下,我们需要反复 ...

最新文章

  1. android控件常用的属性,android?常用的控件属性
  2. 《微服务设计》(三)---- 集成
  3. OpenCV Cut Image via ROI 根据兴趣区域剪裁图片
  4. vue 初始化方法_前端发展方向指南—Vue源码初始化
  5. 用生产者和消费者模式实现奇数偶数不同线程交替输出
  6. sscanf和sprintf的高级用法
  7. Java中TreeMap集合讲解
  8. 这二十个问题,可能是你技术人生中已经或即将遭遇的痛点,怎么解?
  9. 什么是顶级域名,二级域名和三级域名
  10. 为什么说c语言是关键字,为什么说C语言既有高级语言又有低级语言的特点
  11. Android OpenGL ES 3.0 FBO 离屏渲染
  12. html5 预览图片原理,html5实现图片预览和查看原图
  13. 破壳2周造百万销量,荣耀20系列:三个超级炸弹的“寒潮逆袭”
  14. 3DMAX建模入门:美国队长的盾牌图文教程,过程炒鸡详细(下)
  15. JAVA连接SQL2005
  16. 第11章:图像金字塔
  17. 硬盘 U盘 的SN 号码 javascript 读取 PID,VID,SN信息
  18. 论文阅读 || 目标检测系列 —— RCNN详解
  19. python 之Entry
  20. 机器学习实战----贝叶斯之概览篇

热门文章

  1. guestbook.php注入,Destoon 6.0 guestbook.php 通用SQL注入漏洞
  2. js给对象动态添加属性的2种方法
  3. Android支付宝蚂蚁森林能量自动收取插件开发原理解析
  4. 程序员受够了所有压迫之后……
  5. Python 复制一份文件
  6. 允许Traceroute探测
  7. 普乐蛙VR航天科技馆太空体验馆VR太空舱体验馆vr飞碟遨游太空
  8. 十大游戏低延迟高续航蓝牙耳机,商城爆款高性价比蓝牙耳机推荐
  9. mysql去除指定字段里的最后一个字符
  10. 我才不愿做那个任人欺负的人