Java多线程<三>常见的多线程设计模式

多线程的设计模式

两阶段线程终止

image-20220522205120212

  • park方法

image-20220522205104932

  • interrupted() 会让他失效。

  • 使用volatile关键字进行改写

image-20220522210245081

单例模式 双锁检测

保护性暂停

image-20220529184223468

  • 实现1:
package threadBase.model;/*** @author: Zekun Fu* @date: 2022/5/29 19:01* @Description:* 保护性暂停,* Future 中get方法的实现原理*/
public class GuardedObject {private Object response;// 获取结果public Object get() {synchronized (this) {// 等待结果while (response == null) {try {this.wait();} catch (Exception e) {e.printStackTrace();}}}return response;    // 这里自然释放锁}public void complete(Object response) {synchronized (this) {this.response = response;this.notifyAll();}}
}
  • 实现2:设置超时
package threadBase.model;import java.util.concurrent.ThreadLocalRandom;/*** @author: Zekun Fu* @date: 2022/5/29 19:01* @Description:* 保护性暂停,* Future 中get方法的实现原理***/
public class GuardedObject {private Object response;public Object get(long timeout) {synchronized (this) {long begin = System.currentTimeMillis();long passedTime = 0;while(response == null) {long waitTime = timeout - passedTime;   // 这里是为了防止虚假唤醒if (waitTime <= 0) {break;}try {this.wait(waitTime);} catch (Exception e) {e.printStackTrace();}System.out.println("被唤醒了");passedTime = System.currentTimeMillis() - begin;}return response;}}// 获取结果public Object get() {synchronized (this) {// 等待结果while (response == null) {try {this.wait();} catch (Exception e) {e.printStackTrace();}}return response;    // 这里自然释放锁}}public void complete(Object response) {synchronized (this) {this.response = response;this.notifyAll();}}public static void main(String[] args) {GuardedObject go = new GuardedObject();new Thread(()-> {// 等待两秒Object response = go.get(2000);System.out.println("结果是" + response);}).start();new Thread(()-> {try {// 3s才进行返回Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}go.complete(new Object());}).start();// 虚假唤醒new Thread(()->{try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}go.complete(null);}).start();}
}
  • Join的实现原理

image-20220529191720553

弱鸡版本的生产者消费者

  • 保护性暂停协议的扩展。
  • 解决线程之间的同步问题。
package threadBase.model;import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadLocalRandom;/*** @author: Zekun Fu* @date: 2022/5/29 19:01* @Description:* 保护性暂停,* Future 中get方法的实现原理** 写信者和收信者1:1实现模型。* 写信的人和收信的人是一一对应的关系。* 两个线程通过一个唯一的id确定彼此的身份。** 1. 解耦了收信人线程和写信人线程* 2. 防止写信人线程用完对象不销毁。* 3. MailBoxes获取之后应该销毁对象。*** 多个线程之间速度不匹配,所以使用消息队列。也就是生产者消费者模型。* 这里的停止等待也可以看作是一种一对一的解决速度不匹配问题的机制。** 加上MailBox并没有改变这个的本质,只是方便编码了而已,就是把future* 放入了一个公共的消息队列,然后消费者进行取出。** 可以看作是弱鸡版的生产者消费者模型。* 具有缓解速度不匹配问题的机制,但是必须要实现一对一的模型。***/class MailBoxes {           // 消息队列机制private static Map<Integer, GuardedObject> boxes = new Hashtable<>();private static int id = 1;// 产生唯一的idprivate static synchronized int generateId() {return id++;}public static GuardedObject createGuardedObject() {GuardedObject go = new GuardedObject(generateId());boxes.put(go.getId(), go);return go;}public static Set<Integer> getIds() {return boxes.keySet();}public static GuardedObject getGuardedObject(int id) {return boxes.remove(id);        // 使用remove方法,防止内存泄漏}
}/*
*
*   消费者线程消费特定的future也就是GuardObject。
* */class ReadPeople extends Thread{    // 生产者线程@Overridepublic void run() {//  收信// 1. 创建GuardedObject guardedObject = MailBoxes.createGuardedObject();System.out.println(Thread.currentThread().getName() + "等待收信 id:" + guardedObject.getId());Object mail = guardedObject.get(5000);System.out.println(Thread.currentThread().getName() + "受到信:" + guardedObject.getId() + " 内容:" + mail);}
}/*
*
* 生产者线程,生产特定的Future
**/
class WriteMan extends Thread{          // 消费者线程private int id;private String mail;WriteMan(int id, String mail) {this.id = id;this.mail = mail;}@Overridepublic void run() {GuardedObject guardedObject = MailBoxes.getGuardedObject(id);System.out.println(Thread.currentThread().getName() + "写信 id = " + id +" 内容:" + mail);guardedObject.complete(mail);}
}public class GuardedObject {        // future任务机制private int id;private Object response;public GuardedObject(int id) {this.id = id;}public int getId() {return id;}public Object get(long timeout) {synchronized (this) {long begin = System.currentTimeMillis();long passedTime = 0;while(response == null) {long waitTime = timeout - passedTime;   // 这里是为了防止虚假唤醒if (waitTime <= 0) {break;}try {
//                    System.out.println("等待中..");  // 如果被虚假唤醒this.wait(waitTime);} catch (Exception e) {e.printStackTrace();}
//                System.out.println("被唤醒了"); // 如果被虚假唤醒passedTime = System.currentTimeMillis() - begin;}return response;}}// 获取结果public Object get() {synchronized (this) {// 等待结果while (response == null) {try {this.wait();} catch (Exception e) {e.printStackTrace();}}return response;    // 这里自然释放锁}}public void complete(Object response) {synchronized (this) {this.response = response;this.notifyAll();}}public static void main(String[] args) throws InterruptedException {
//        GuardedObject go = new GuardedObject(1);
//        new Thread(()-> {
//            // 等待两秒
//            Object response = go.get(2000);
//            System.out.println("结果是" + response);
//        }).start();
//
//        new Thread(()-> {
//            try {
//                // 3s才进行返回
//                Thread.sleep(3000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            go.complete(new Object());
//        }).start();
//
//        // 虚假唤醒
//        new Thread(()->{
//            try {
//                Thread.sleep(1000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            go.complete(null);
//
//        }).start();// 模拟写信和收信的过程// 1. 三个收信的人 (消费者)for (int i = 0; i < 3; i++) {ReadPeople p = new ReadPeople();p.start();}// 2. 三个写信人 (必须对应三个写信者)Thread.sleep(1000);for (int idx: MailBoxes.getIds()) {new WriteMan(idx, "内容" + idx).start();}}
}

生产者消费者模型(终止协议修改)

1. 手动枷锁实现

  • 首先枷锁中的wait应该使用while循环
  • 其次MsgQue应该抛出异常,而不是捕捉。这样终止线程的决定权就在线程那里,而不是必须等消费完最后一个才进行。
  • 而放入队列的异常可以放在MsgQue或者消费者线程中,因为不管怎么样,生产的都需要放入队列中。
package threadBase.model;import java.util.LinkedList;
import java.util.List;
import java.util.Queue;/*** @author: Zekun Fu* @date: 2022/5/31 15:48* @Description: 生产者消费者,使用自己加锁实现*/
public class MessageQue {LinkedList<Message>que = new LinkedList<>();int capcity;public MessageQue(int capcity) {this.capcity = capcity;}public void put(Message x) throws InterruptedException{synchronized (que) {while (que.size() >= capcity) {System.out.println("队列已满," + Thread.currentThread().getName() + "等待");que.wait();}que.addLast(x);         // 尾部添加que.notifyAll();}}public Message get() throws InterruptedException{synchronized (que) {while (que.size() == 0) {que.wait();}Message msg = que.removeFirst();        // 头部取出que.notifyAll();return msg;}}public static void main(String[] args) throws InterruptedException {MessageQue mq = new MessageQue(2);for (int i = 0; i < 3; i++) {int idx = i;new Thread(()->{System.out.println("生产者线程" + idx + "生产完成");try {mq.put(new Message("消息" + idx));} catch (InterruptedException e) {e.printStackTrace();}},"生产者线程" + i).start();}Thread t = new Thread(()-> {Thread cur = Thread.currentThread();while (true) {if (cur.isInterrupted()) {break;}try {Thread.sleep(1000);     // 每隔1s消费一次Message msg = mq.get();System.out.println("消费" + msg.getMsg());} catch (InterruptedException e) {cur.interrupt();System.out.println("停止消费");}}}, "消费者线程");t.start();Thread.sleep(4000);t.interrupt();}
}class Message {private String msg;public Message(String mgs) {this.msg = mgs;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}
}

2. 使用Semphore实现

package threadBase.model;import java.util.LinkedList;
import java.util.concurrent.Semaphore;/*** @author: Zekun Fu* @date: 2022/5/31 16:30* @Description: 使用Semaphore实现****/
public class MessageQue2 implements MesQ{private Semaphore mutex = new Semaphore(1);private Semaphore full;private Semaphore empty;LinkedList<Message>list = new LinkedList<>();public MessageQue2(int capcity) {full = new Semaphore(capcity);empty = new Semaphore(0);}@Overridepublic void put(Message msg) throws InterruptedException {full.acquire();mutex.acquire();list.addLast(msg);mutex.release();empty.release();}@Overridepublic Message get() throws InterruptedException {empty.acquire();mutex.acquire();Message ans = list.removeFirst();mutex.release();full.release();return ans;}public static void main(String[] args) {Test.testMesQ(new MessageQue2(2));}
}

哲学家进餐问题

  • 参考博客

哲学家进餐问题:

  1. 哲学家进餐,不应该有阻塞的线程,因为线程阻塞的条件是等待其他线程的完成通知。

1. 锁住房子破坏了请求保持

请求与保持,就是线程在运行期间,拥有一部分资源,但是仍旧需要更多的资源才能继续运行。

简单的办法,就是采用静态分配的方式,首先把所有的资源都分配好,程序就不会在进行请求了。

简单办法2,就是只有当能够同时拿到两双筷子的时候,才进行分配,如果没有同时拿到两双筷子,就直接放下。

缺点就是:如果程序运行时间很长,而某些资源虽然用的很快就用完了,但是也得等到程序运行完成之后才能进行释放。导致资源利用效率很低。同时也会导致某些进程的饥饿。

第二种的缺点很明显,拿起筷子和放下筷子都是需要消耗cpu和存储资源的,如果拿起放下时间很长,那么就会导致性能低下,资源利用效率低,同时有可能导致活锁问题。

Java中可以使用Mutex加上Synchornized进行资源的静态分配。也就是先设置一个房间。只允许其中的一部分人进去。

img

import java.util.concurrent.Semaphore;public class MealOfPhilosopher {static Semaphore[] chopsticks = new Semaphore[5];static Semaphore mutex = new Semaphore(1);static {for (int i = 0; i < 5; i++) {chopsticks[i] = new Semaphore(1);}}public static void main(String[] args) {for (int i = 0; i < 5; i++) {int j = i;new Thread(()->{while (true) {try {mutex.acquire();chopsticks[j].acquire();System.out.println(Thread.currentThread().getName() + "拿走了他左边的" + j + "号筷子");chopsticks[(j + 1) % 5].acquire();System.out.println(Thread.currentThread().getName() + "拿走了他右边的" + (j + 1) % 5 + "号筷子");mutex.release();System.out.println(Thread.currentThread().getName() + "正在吃饭。");chopsticks[j].release();System.out.println(Thread.currentThread().getName() + "放下了他左边的" + j + "号筷子");chopsticks[(j + 1) % 5].release();System.out.println(Thread.currentThread().getName() + "放下了他右边的" + (j + 1) % 5 + "号筷子");System.out.println(Thread.currentThread().getName() + "正在思考。");} catch (InterruptedException e) {e.printStackTrace();}}}, "哲学家" + i + "号").start();}}}

2. 规定顺序破坏了循环等待

方法是,申请资源一定要按照某种顺序进行。比如设备的id进行从小到达的申请这种。

缺点是,资源的扩展性不好,如果新来了资源,上面的申请逻辑就需要改变。同时因为线程申请资源和使用资源的顺序可能不一致,从而导致请求到的资源无法投入使用的情况,从而导致资源的利用效率低

实现方法1:奇数的只能拿左手边的,偶数的只能拿右手边的

实现方法2:

img

3. 尝试上锁破坏了不可剥夺

img

缺点是,代价很大,可能线程已经运行了一半了,又得重新运行。

实现就是,使用tryAquire的方式获取锁。而不是aquire的方式。

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;public class MealOfPhilosopher {static final Semaphore[] chopsticks = new Semaphore[5];static final Semaphore mutex = new Semaphore(1);static {for (int i = 0; i < 5; i++) {chopsticks[i] = new Semaphore(1);}}public static void main(String[] args) {for (int i = 0; i < 5; i++) {int j = i;new Thread(()->{while (true) {try {System.out.println(Thread.currentThread().getName() + "拿走了他左边的" + j + "号筷子");if (!chopsticks[j].tryAcquire(10, TimeUnit.SECONDS))System.out.println(Thread.currentThread().getName() + "等待了好长时间,他只好放下他左边的" + j + "号筷子");System.out.println(Thread.currentThread().getName() + "拿走了他右边的" + (j + 1) % 5 + "号筷子");if (!chopsticks[(j + 1) % 5].tryAcquire(10, TimeUnit.SECONDS))System.out.println(Thread.currentThread().getName() + "等待了好长时间,他只好放下他右边的" + (j + 1) % 5 + "号筷子");System.out.println(Thread.currentThread().getName() + "正在吃饭。");System.out.println(Thread.currentThread().getName() + "放下了他左边的" + j + "号筷子");chopsticks[j].release();System.out.println(Thread.currentThread().getName() + "放下了他右边的" + (j + 1) % 5 + "号筷子");chopsticks[(j + 1) % 5].release();System.out.println(Thread.currentThread().getName() + "正在思考。");} catch (InterruptedException e) {e.printStackTrace();}}}, "哲学家" + i + "号").start();}}}
  • 实现而使用Reentrantlock
package threadBase.model;import leetcode.Philosophiers;import java.util.TreeMap;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;/*** @author: Zekun Fu* @date: 2022/6/1 14:59* @Description: 哲学家进餐问题,* 破坏不可剥夺 -- 使用tryLock* 破坏请求保持 -- 静态分配* 破坏循环等待 -- 规定顺序。可能会导致某一个线程饥饿。*      因为有的线程和两个人竞争,而有的线程在一个时刻只和一个人竞争**      实现细节:*      1. 筷子可以继承锁变量*      2. 可以使用Semphore实现*      3. 可以使用ReentrantLock 实现的可以说是无锁的,因为线程一直处于就绪和执行装态。**      4. 为什么不进入等待队列中呢?*          因为这不是一个同步问题,没有线程之间的协作,没有一个线程通知另外一个线程这种事情。*          也就是说,不会有人通知他醒过来。*          所以他需要不断的死循环去尝试,去抢筷子。**/class Chopstic extends ReentrantLock {}
public class Philosopher extends Thread {Chopstic left;Chopstic right;public Philosopher(Chopstic left, Chopstic right) {this.left = left;this.right = right;}@Overridepublic void run() {Thread t = Thread.currentThread();while(true) {if (t.isInterrupted()) {System.out.println(t.getName() + "嗝屁了");break;}if (left.tryLock()) {       // 如果拿到了左筷子try {if (right.tryLock()) {  // 尝试拿右筷子, 没拿到try {eat();} finally {right.unlock();     // 如果拿到了,吃饭,放下锁}}}finally {left.unlock();}}try {Thread.sleep(1000);} catch (InterruptedException e) {t.interrupt(); // 重新设置打断标记}}}public void eat() {Thread t = Thread.currentThread();System.out.println(t.getName() + "正在吃饭...");}public static void main(String[] args) throws InterruptedException {Chopstic[] chopstics = new Chopstic[5];for (int i = 0; i < 5; i++) chopstics[i] = new Chopstic();String[] names = {"阿基米德","柏拉图","牛顿","柯西","亚里士多德"};Philosopher[] ps = new Philosopher[5];for (int i = 0; i < 5; i++) {Philosopher p = new Philosopher(chopstics[i], chopstics[(i + 1) % 5]);p.setName(names[i]);p.start();ps[i] = p;}Thread.sleep(10000);for (int i = 0; i < 5; i++) {ps[i].interrupt();}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/311770.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mysql实时数据同步工具Alibaba Canal 使用

目录 Mysql实时数据同步工具Alibaba Canal 使用Canal是什么&#xff1f;工作原理重要版本更新说明 环境准备安装Canalwindow Java : Canal Client 集成依赖编码 工作流程开启原生MQRocketMQ 安装部署 canal配置说明1.1 canal.properties常用配置介绍&#xff1a;2.common参数定…

2023-12-20 LeetCode每日一题(判别首字母缩略词)

2023-12-20每日一题 一、题目编号 2828. 判别首字母缩略词二、题目链接 点击跳转到题目位置 三、题目描述 给你一个字符串数组 words 和一个字符串 s &#xff0c;请你判断 s 是不是 words 的 首字母缩略词 。 如果可以按顺序串联 words 中每个字符串的第一个字符形成字符…

【深度学习】Normalizing flow原理推导+Pytorch实现

1、前言 N o r m a l i z i n g f l o w \boxed{Normalizing \hspace{0.1cm} flow} Normalizingflow​&#xff0c;流模型&#xff0c;一种能够与目前流行的生成模型—— G A N 、 V A E \boxed{\mathbf{GAN、VAE}} GAN、VAE​相媲美的模型。其也是一个生成模型&#xff0c;可是…

ZYNQ 7020 之 FPGA知识点重塑笔记一——串口通信

目录 一&#xff1a;串口通信简介 二&#xff1a;三种常见的数据通信方式—RS232串口通信 2.1 实验任务 2.2 串口接收模块的设计 2.2.1 代码设计 2.3 串口发送模块的设计 2.3.1 代码设计 2.4 顶层模块编写 2.4.1 代码设计 2.4.2 仿真验证代码 2.4.3 仿真结果 2.4.4…

阿里云PolarDB数据库优惠价格表11元一天起

阿里云数据库PolarDB租用价格表&#xff0c;云数据库PolarDB MySQL版2核4GB&#xff08;通用&#xff09;、2个节点、60 GB存储空间55元5天&#xff0c;云数据库 PolarDB 分布式版标准版2核16G&#xff08;通用&#xff09;57.6元3天&#xff0c;阿里云百科aliyunbaike.com分享…

uni-app js语法

锋哥原创的uni-app视频教程&#xff1a; 2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中...共计23条视频&#xff0c;包括&#xff1a;第1讲 uni…

Lesson 06 vector类(上)

C&#xff1a;渴望力量吗&#xff0c;少年&#xff1f; 文章目录 一、vector是什么&#xff1f;二、vector的使用1. 构造函数2. vector iterator3. vector 空间增长问题4. vector增删查改 三、vector实际使用 一、vector是什么&#xff1f; vector是表示可变大小数组的序列容器…

DFS

目录 DFS 实现数字全排列 N 皇后问题 DFS 算法的理解 优先考虑深度&#xff0c;换句话说就是一条路走到黑&#xff0c;直到无路可走的情况下&#xff0c;才会选择回头&#xff0c;然后重新选择一条路。空间复杂度&#xff1a;O&#xff08;h&#xff09;和高度成正比 不具…

设计模式——适配器模式(Adapter Pattern)

概述 适配器模式可以将一个类的接口和另一个类的接口匹配起来&#xff0c;而无须修改原来的适配者接口和抽象目标类接口。适配器模式(Adapter Pattern)&#xff1a;将一个接口转换成客户希望的另一个接口&#xff0c;使接口不兼容的那些类可以一起工作&#xff0c;其别名为包装…

UE蓝图 RPG动作游戏(一) day15

角色状态制作 制作角色动画混合空间 创建一个动混合空间 添加动作在混合空间 动画蓝图 创建一个动画蓝图 先使用混合空间进行移动&#xff0c;后续优化后再使用状态机 编写垂直水平速度逻辑初始化&#xff0c;获取到此动画的角色组件 获取Horizontal与Vertical的速度逻辑 …

C语言之指针和函数

目录 作为函数参数的指针 二值互换 scanf函数和指针 指针的类型 空指针 标量型 在C语言程序中&#xff0c;指针的一个重要作用就是作为函数参数使用&#xff0c;下面我们就来学习作为函数参数的指针的相关内容。 作为函数参数的指针 假如我有一个神奇的能力&#xff0c;能…

【Vue2+3入门到实战】(18)VUE之Vuex状态管理器概述、VueX的安装、核心概念 State状态代码实现 详细讲解

目录 一、[Vuex](https://vuex.vuejs.org/zh/) 概述1.是什么2.使用场景3.优势4.注意&#xff1a; 二、需求: 多组件共享数据1.创建项目2.创建三个组件, 目录如下3.源代码如下 三、vuex 的使用 - 创建仓库1.安装 vuex2.新建 store/index.js 专门存放 vuex3.创建仓库 store/index…