目录

一、单例模式

1,什么是单例模式

2,单例模式分类

二,生产者消费者模型

1,阻塞队列是什么

2,标准库中的阻塞队列

3,生产者消费者模型

三、定时器

1,定时器是什么

2,标准库中的定时器

3,实现定时器

四、线程池

1,线程池是什么

2,标准库中的线程池

3,实现线程池


一、单例模式

1,什么是单例模式

设计模式好比象棋中的 "棋谱". 红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有 一些固定的套路. 按照套路来走局势就不会吃亏

软件开发中也有很多常见的 "问题场景". 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照 这个套路来实现代码, 也不会吃亏

2,单例模式分类

饿汉模式

类加载的同时, 创建实例.

//先创建一个表示单例的类//我们要求Singletion这个类只能有一个实例//饿汉模式,“饿”是指类被加载,实例就会被创建static class Singletion{//把构造方法变为私有,此时该类外部就无法new这个类的实例private Singletion(){}//创建一个static的成员,表示该类的唯一实例private static Singletion instance = new Singletion();public static Singletion getInstance() {return instance;}}public static void main(String[] args) {Singletion s1 = Singletion.getInstance();Singletion s2 = Singletion.getInstance();System.out.println(s1 == s2);}

懒汉模式

单线程

class Singleton {private static Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

但以上代码存在线程不安全的问题

对于懒汉模式,多线程调用getInstance,getInstance做了四件事

读取instance的内容

判断instance是否为空

如果instance为null,就new实例

返回实例地址

当一个类被new两次,就不是单例模式了,如果线程更多,new的次数可能更多,不符合单例模式

多线程

static class Singletion{private Singletion() { }private static Singletion instance = null;public static Singletion getInstance(){if (instance == null){instance = new Singletion();}return instance;}}

保证线程安全,我们选择加锁

static class Singletion{private Singletion() { }private static Singletion instance = null;public static Singletion getInstance(){synchronized (Singletion.class){if (instance == null){instance = new Singletion();}return instance;}}}

当多线程首次调用 getInstance, 大家可能都发现 instance 为 null, 于是又继续往下执行来竞争锁, 其中竞争成功的线程, 再完成创建实例的操作

当这个实例创建完了之后, 其他竞争到锁的线程就被里层 if 挡住了. 也就不会继续创建其他实例

 static class Singletion{private Singletion() { }private static Singletion instance = null;public static Singletion getInstance(){if (instance == null){synchronized (Singletion.class){if (instance == null){instance = new Singletion();}}}return instance;}}
}

涉及多个读操作,可能会被编译器优化,为了避免 "内存可见性" 导致读取的 instance 出现偏差, 于是补充上 volatile。

static class Singletion{private Singletion() { }private volatile static Singletion instance = null;public static Singletion getInstance(){if (instance == null){synchronized (Singletion.class){if (instance == null){instance = new Singletion();}}}return instance;}}

为了保证线程安全,涉及三个要点

1,加锁保证线程安全

2,双重if保证效率

3,volatile避免了内存可见性的问题

二,生产者消费者模型

1,阻塞队列是什么

阻塞队列是一种特殊的队列. 也遵守 "先进先出" 的原则. 阻塞队列能是一种线程安全的数据结构, 并且具有以下特性:

当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素.

当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插入元素.

阻塞队列的一个典型应用场景就是 "生产者消费者模型". 这是一种非常典型的开发模型

2,标准库中的阻塞队列

在 Java 标准库中内置了阻塞队列. 如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可.

BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.

put 方法用于阻塞式的入队列, take 用于阻塞式的出队列.

BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.

BlockingQueue<String> queue = new LinkedBlockingQueue<>();
// 入队列
queue.put("abc");
// 出队列. 如果没有 put 直接 take, 就会阻塞.
String elem = queue.take();

3,生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。 生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等 待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.

1) 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.

比如在 "秒杀" 场景下, 服务器同一时刻可能会收到大量的支付请求. 如果直接处理这些支付请求, 服务器可能扛不住(每个支付请求的处理都需要比较复杂的流程). 这个时候就可以把这些请求都放 到一个阻塞队列中, 然后再由消费者线程慢慢的来处理每个支付请求

2) 阻塞队列也能使生产者和消费者之间 解耦

比如过年一家人一起包饺子. 一般都是有明确分工, 比如一个人负责擀饺子皮, 其他人负责包. 擀饺 子皮的人就是 "生产者", 包饺子的人就是 "消费者". 擀饺子皮的人不关心包饺子的人是谁(能包就行, 无论是手工包, 借助工具, 还是机器包), 包饺子的人 也不关心擀饺子皮的人是谁(有饺子皮就行, 无论是用擀面杖擀的, 还是拿罐头瓶擀, 还是直接从超 市买的).

static class BlockingQueue{//基于链表//基于数组private int[] array = new int[100];private volatile int head = 0;private volatile int tail = 0;//head和tail构成的是一个前闭后开的区间//当两者重合的时候,可能表示队列空,也可能是表示队列满//为了区分空还是满,需要额外引入一个size来表示private volatile int size = 0;//入队列 出队列 取队首元素public void put(int value) throws InterruptedException {synchronized (this){while (size == array.length){wait();}//把value放在队尾即可array[tail] = value;tail++;if (tail == array.length){tail = 0;}size++;notify();}}public int take() throws InterruptedException {int ret = -1;synchronized (this) {while (size == 0) {this.wait();}ret = array[head];head++;if (head == array.length) {head = 0;}size--;notify();}return ret;}}public static void main(String[] args) {BlockingQueue blockingQueue = new BlockingQueue();//第一次,让消费者消费的快一些,生产者生产的慢一些//就会看到消费者阻塞等待,每次有新的生产者生产元素的时候,消费者才能消费//第二次,让消费者消费的慢一些,生产者生产的快一些//就会看到生产者往队列中插入元素,插入元素满了之后就会阻塞等待//随后消费者每次消费一个元素,生产才能产生新的元素Thread producer = new Thread(){@Overridepublic void run() {for (int i = 0; i < 10000; i++){try {blockingQueue.put(i);System.out.println("生产元素:" + i);} catch (InterruptedException e) {e.printStackTrace();}}}};producer.start();Thread consumer = new Thread(){@Overridepublic void run() {while (true){try {int ret = blockingQueue.take();System.out.println("消费元素:" + ret);} catch (InterruptedException e) {e.printStackTrace();}}}};consumer.start();}

此处两个wait触法的条件,一个是队列为空,一个是队列为满

实现阻塞队列的特点:

1,队列为空,继续出队列就会阻塞,一直阻塞到其他线程入队列为止

2,队列为满,继续入队列也会阻塞,一直阻塞到其他线程出队列为止

public static void main(String[] args) {BlockingQueue blockingQueue = new BlockingQueue();//第一次,让消费者消费的快一些,生产者生产的慢一些//就会看到消费者阻塞等待,每次有新的生产者生产元素的时候,消费者才能消费//第二次,让消费者消费的慢一些,生产者生产的快一些//就会看到生产者往队列中插入元素,插入元素满了之后就会阻塞等待//随后消费者每次消费一个元素,生产才能产生新的元素Thread producer = new Thread(){@Overridepublic void run() {for (int i = 0; i < 10000; i++){try {blockingQueue.put(i);System.out.println("生产元素:" + i);Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}};producer.start();Thread consumer = new Thread(){@Overridepublic void run() {while (true){try {int ret = blockingQueue.take();System.out.println("消费元素:" + ret);} catch (InterruptedException e) {e.printStackTrace();}}}};consumer.start();}

or (int i = 0; i < 10000; i++){try {blockingQueue.put(i);System.out.println("生产元素:" + i);} catch (InterruptedException e) {e.printStackTrace();}}}};producer.start();Thread consumer = new Thread(){@Overridepublic void run() {while (true){try {int ret = blockingQueue.take();System.out.println("消费元素:" + ret);Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}};consumer.start();}

三、定时器

1,定时器是什么

定时器也是软件开发中的一个重要组件. 类似于一个 "闹钟". 达到一个设定的时间之后, 就执行某个指定好的代码.

2,标准库中的定时器

标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule .

schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后 执行 (单位为毫秒).

Timer timer = new Timer();
timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello");}
}, 3000);

3,实现定时器

定时器的构成:

一个带优先级的阻塞队

队列中的每个元素是一个 Task 对象.

Task 中带有一个时间属性, 队首元素就是即将

同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行

import java.util.concurrent.PriorityBlockingQueue;public class Thread4 {//优先级队列的元素必须是可比较的//比较规则有两种://1,让Task实现Comparable接口//2,让优先级队列构造的时候,传入一个对象比较器static class Task implements Comparable<Task>{//Runnable中有一个run方法,借助run方法来执行具体的任务是啥private Runnable command;//time表示啥时候来执行command,是一个绝对时间private long time;//构造方法的参数public Task(Runnable command,long after){this.command = command;this.time = System.currentTimeMillis() + after;}//具体任务的逻辑public void run(){command.run();}@Overridepublic int compareTo(Task o) {return (int) (this.time - o.time);}}static class Worker extends Thread{private PriorityBlockingQueue<Task> queue = null;private Object mailBox = null;public Worker(PriorityBlockingQueue<Task> queue, Object mailBox){this.queue = queue;this.mailBox = mailBox;}@Overridepublic void run() {while (true){try{//1,取出队首元素Task task = queue.take();//2,检查当前任务时间是否到了long curTime = System.currentTimeMillis();if (task.time > curTime){//时间还没到,把任务塞回队列中queue.put(task);synchronized (mailBox){mailBox.wait(task.time - curTime);}}else {task.run();}}catch (InterruptedException e){e.printStackTrace();break;}}}}static class Timer{//为了避免忙等,需要使用wait方法//使用一个单独的对象来辅助wait//使用this也行private Object mailBox = new Object();//定时器的基本组成//1,用一个类来描述队伍//2.用一个阻塞优先级队列来组织若干任务,让队首元素就是时间最早的任务,如果队首元素时间未到,其他元素也不能执行//3.用一个线程来循环扫描当前阻塞队列的队首元素,如果时间到了,就让执行指定任务private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();public Timer(){//创建线程Worker worker = new Worker(queue,mailBox);worker.start();}//4.还需要提供一个方法,让调用者把任务给“安排“进来public void schedule(Runnable command,long after){Task task = new Task(command,after);queue.put(task);synchronized (mailBox){mailBox.notify();}}}public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("he he");timer.schedule(this,2000);}},2000);}}

1,有一个类来描述一个任务

2,有一个阻塞优先级队列来组织这些任务

 3,有一个扫描线程,定时扫描队首元素

4,有一个接口,让调用者安排任务给定时器

 5,解决忙等问题,加上wait和notify

初始情况下,队列为空,阻塞是在第一个位置take阻塞,调用schedlule会唤醒take位置的阻塞,取到task任务。

获取当前时间,获取task时间,比较发现时间还没到,继续wait。

当时间到的时候wait返回,下次循环中,又尝试从队首获取元素,队首有元素,不阻塞,直接取出来。

四、线程池

1,线程池是什么

想象这么一个场景: 在学校附近新开了一家快递店,老板很精明,想到一个与众不同的办法来经营。店里没有雇人, 而是每次有业务来了,就现场找一名同学过来把快递送了,然后解雇同学。这个类比我们平时来 一个任务,起一个线程进行处理的模式。

很快老板发现问题来了,每次招聘 + 解雇同学的成本还是非常高的。老板还是很善于变通的,知 道了为什么大家都要雇人了,所以指定了一个指标,公司业务人员会扩张到 3 个人,但还是随着 业务逐步雇人。于是再有业务来了,老板就看,如果现在公司还没 3 个人,就雇一个人去送快 递,否则只是把业务放到一个本本上,等着 3 个快递人员空闲的时候去处理。这个就是我们要带 出的线程池的模式。

线程池最大的好处就是减少每次启动、销毁线程的损耗。

2,标准库中的线程池

使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.

返回值类型为 ExecutorService

通过 ExecutorService.submit 可以注册一个任务到线程池中.

Executors 创建线程池的几种方式

newFixedThreadPool: 创建固定线程数的线程池

newCachedThreadPool: 创建线程数目动态增长的线程池.

newSingleThreadExecutor: 创建只包含单个线程的线程池.

newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.

Executors 本质上是 ThreadPoolExecutor 类的封装

ThreadPoolExecutor 提供了更多的可选参数, 可以进一步细化线程池行为的设定

3,实现线程池

核心操作为 submit, 将任务加入线程池中

使用 Worker 类描述一个工作线程. 使用 Runnable 描述一个任务.

使用一个 BlockingQueue 组织所有的任务

每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执行.

指定一下线程池中的最大线程数 maxWorkerCount; 当当前线程数超过这个最大值时, 就不再新增 线程了.

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;public class Thread5 {//使用一个类来描述这个工作线程static class Worker extends Thread{private int id = 0;//每个Worker线程都需要从任务队列中取任务//需要获取任务队列的实例private BlockingQueue<Runnable> queue = null;public Worker(BlockingQueue<Runnable> queue,int id){this.queue = queue;this.id = id;}@Overridepublic void run() {//此处的try把while包裹了,目的是只要线程收到异常,就立刻结束run方法(也是结束线程)try {while (!Thread.currentThread().isInterrupted()){Runnable command = queue.take();System.out.println("thread: " + id + "running...");command.run();}}catch (InterruptedException e){System.out.println("线程被终止");}}}//本质就是生产者消费模型//调用execute的代码就是生产者,生产了任务//worker就是消费者,消费队列中的任务//交易产所就是BlockingQueuestatic class MyThreadPool{//阻塞队列组织若干任务private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();//List组织若干线程private List<Worker> workers = new ArrayList<>();//先假设有10个线程容量private static final int maxWorkerCount = 10;//实现execute方法和shutdown方法public void execute(Runnable command) throws InterruptedException {//线程数目较少的时候,创建新的线程为工作线程//线程足够多,达到阈值,不用创建新线程if (workers.size() < maxWorkerCount){Worker worker = new Worker(queue,workers.size());worker.start();workers.add(worker);}queue.put(command);}//当shutdown结束,所有线程结束public void shutdown() throws InterruptedException {//终止所有线程for (Worker worker : workers){worker.interrupt();}//还需要等待每个线程执行结束for (Worker worker : workers){worker.join();}}static class Command implements Runnable{private int num;public Command(int num){this.num = num;}@Overridepublic void run() {System.out.println("正在执行任务:" + num);}}public static void main(String[] args) throws InterruptedException {MyThreadPool myThreadPool = new MyThreadPool();for (int i = 0; i < 1000; i++){myThreadPool.execute(new Command(i));}Thread.sleep(2000);myThreadPool.shutdown();System.out.println("线程已经被销毁");}}}

当最初创建线程池实例的时候,此时线程池中没有线程,继续调用execute的时候,就会触发创建线程的操作。

interrupt触发异常或者修改标记位,让线程run方法结束,线程也就随之结束了,对于的jion也就结束了。

如果队列为空,take就会被阻塞,阻塞到其他线程调用execute插入元素为止

【Javaweb】多线程案例相关推荐

  1. JavaWeb学习案例——学生管理系统

    JavaWeb学习案例--学生管理系统 引入jar包: 1. c3p0-0.9.1.2.jar // 第三方数据库连接池 2.commons-dbutils-1.4.jar // 第三方数据库操作方法 ...

  2. Java多线程——三个多线程案例总结

    Java多线程--三个多线程案例总结   非常经典的三道多线程案例   1.写两个线程,一个线程打印1-52,另一个线程打印A~Z,     打印顺序为:12A34B-5152Z   2.编写一个程序 ...

  3. Java多线程案例:模拟12306火车站售票系统

    Java多线程案例:模拟12306火车站售票系统 该系统一共涉及到3个类: 车票(Ticket) 12306系统(System12306) 售票窗口(Window) 车票类,涉及三个属性: 起始站 终 ...

  4. Java多线程案例之阻塞队列

    ⭐️前面的话⭐️ 本篇文章将介绍Java多线程案例,阻塞队列,阻塞队列在普通队列的基础上多了两种情况,一是阻塞队列为空时,如果进行出队操作,会使当前线程阻塞,直到有新元素插入阻塞队列,该线程才被通知继 ...

  5. 黑马程序员《JavaWeb程序设计案例教程》_课后习题答案

    第一章 [测一测] 学习完前面的内容,下面来动手测一测吧,请思考以下问题: 1.请描述HTML.CSS.DOM.JavaScript分别表示的含义. 2.请列举出HTML常用的标记.(至少10个) 3 ...

  6. Java多线程案例--生产者和消费者模型(送奶人和喝奶人的故事!)

    文章目录 一.进程和线程 1.进程 2.线程 3.进程与线程的区别 二.生产者和消费者模型 1.生产者消费者模式概述 2.奶箱类 3.生产者类 4.消费者类 三.测试 1.测试类(BoxDemo) 2 ...

  7. JavaWeb项目案例(一)

    WEB综合案 整个项目的源码及其详细笔记请私信博主免费领取!!!!!!!!!! 博主主页传送门 学习目标: 目标1:能够说出案例的系统架构和技术架构 目标2:能够说出案例的大致需求 目标3:完成案例工 ...

  8. 多线程案例----严格单例模式----和尚吃馒头问题

    在项目中,经常用到一种设计模式----单例模式,下面举一个小案例,说明线程安全的单例模式在多线程中的应用,以供学习参考: 和尚吃馒头: 100个馒头,30个和尚,每个和尚最少吃一个馒头,最多不超过4个 ...

  9. 多线程的创建和使用,多线程案例:火车站售票

    多线程 1.并发与并行 *并行:指两个或多个事件在同一时刻发生(同时发生). *并发:指两个或多个事件在同一个时间段内发生. 2.线程和进程 *进程:是指一个内存中运行的应用程序,每个进程都有一个独立 ...

最新文章

  1. Lucene查询索引(分页)
  2. spring boot源码分析之SpringApplication
  3. 如何检查某个用户是否具有某个权限对象上定义的某种权限
  4. Git commit your changes or stash them before you can merge
  5. [HAOI2008]移动玩具
  6. 128位计算机 ps2,64位就是最强电脑?难道就没有128位的电脑吗
  7. Three.js制作360度全景图
  8. zabbix mysql复制延迟_Zabbix监控mysql主从复制状态
  9. Python数据分析之一元线性回归
  10. Java初学者需掌握的30个概念
  11. mp4文件如何转换为webm格式
  12. 计算机硬盘分区信息,硬盘分区整数G计算公式及计算器
  13. cfe刷机教程 斐讯k3_PHICOMM 斐讯 K3 路由器 刷机教程
  14. 堆栈平衡:估计这是最详细的讲解堆栈平衡的了
  15. html5 在线抽奖,HTML5大转盘抽奖特效代码
  16. win10系统计算机如何分盘,windows10怎么分盘
  17. iPhone开发逻辑分辨率
  18. 吃饭,睡觉,打豆豆喽~~
  19. Report_Report Builder的一些基本概念(概念)
  20. 摄像头镜头焦距与照射距离关系图

热门文章

  1. [js] 显示原型和隐式原型
  2. iPhone 5C并非失败产品 只因世人误解
  3. cesium 测距 测面积 测高
  4. Python-PyMysql详解
  5. 火狐(Firefox)浏览器用户,你们的密码可能已经泄露
  6. 小米澎湃S2流片失败,根本原因是没有顶级高手
  7. 【转】数字图像处理中的形态学
  8. 计算机职称考试幻灯片,2017年职称计算机考试PowerPoint:插入新幻灯片
  9. oracle存储过程报无效的列索引的错误
  10. 美图将手机业务授权给小米背后:全年最高亏12亿 期待减负