共享模型之管程(五)

news2025/1/8 5:48:38

1.多线程设计模式

1.1.同步模式之保护性暂停

1.1.1.定义

1>.即Guarded Suspension,用在一个线程等待另一个线程的执行结果的场景中;

2>.使用场景

①.有一个结果(数据)需要从一个线程传递到另一个线程,让他们关联同一个 GuardedObject;
②.如果有结果(数据)不断从一个线程到另一个线程那么可以使用消息队列;
③.JDK中,join的实现、Future的实现,采用的就是此模式;
④.因为要等待另一方的结果,因此归类到同步模式;
在这里插入图片描述

1.1.2.代码实现

@Slf4j
public class TestGuardedObject {
    public static void main(String[] args) {
        //创建用于线程之间传递结果的桥梁类
        GuardedObject guardedObject = new GuardedObject();

        //线程1等待线程2的处理结果
        new Thread(() -> {
            //线程t1调用该方法获取线程t2的结果会被阻塞
            log.info("线程t1等待线程t2的执行结果");
            Object result = guardedObject.get();
            log.info(result.toString());
        }, "t1").start();

        //线程2处理业务,将结果传递到GuardedObject中传递到线程1
        new Thread(() -> {
            log.info("线程t2处理业务...");

            Integer result = 0;
            for (int i = 0; i < 10; i++) {
                result = result + i;
            }

            //将处理结果传递到线程t2
            //方法内部会唤醒其他处于WAITING状态的线程
            guardedObject.complete(result);
        }, "t2").start();
    }
}

//创建一个GuardedObject桥梁对象用于在线程之间传递结果
class GuardedObject {
    //要传递的结果,共享变量
    private Object response;

    //获取结果
    public Object get() {
        synchronized (this) {
            //线程进入等待状态的条件,为了防止虚假唤醒,需要使用while循环进行不停的判断,而不是只判断一次
            while (this.response == null) {
                //没有获取到结果,等待
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            return this.response;
        }
    }

    //产生结果
    public void complete(Object response) {
        synchronized (this) {
            //给成员变量赋值
            this.response = response;
            //唤醒所有处于WAITING状态的线程
            this.notifyAll();
        }
    }
}

在这里插入图片描述

可以看到使用保护性暂停同步模式可以让多个线程同时执行(/一个线程等待另一个线程的结果),而join必须一个等待某一个线程执行完毕,该线程才能继续执行;

1.2.同步模式之保护性暂停增强1

1.2.1.带超时版GuardedObject

@Slf4j
public class TestGuardedObject {
    public static void main(String[] args) {
        //创建用于线程之间传递结果的桥梁类
        GuardedObject guardedObject = new GuardedObject();

        //线程1等待线程2的处理结果
        new Thread(() -> {
            //线程t1调用该方法获取线程t2的处理结果会被阻塞
            log.info("线程t1等待线程t2的执行结果");
            Object result = guardedObject.get(2000);
            log.info("线程t1获取到的结果:{}",result == null ? null : result.toString());
        }, "t1").start();

        //线程2处理业务,将结果传递到GuardedObject中传递到线程1
        new Thread(() -> {
            log.info("线程t2处理业务...");

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            Integer result = 0;
            for (int i = 0; i < 10; i++) {
                result = result + i;
            }

            //将处理结果传递到线程t2
            //方法内部会唤醒其他处于WAITING状态的线程
            log.info("业务处理完成,准备唤醒其他线程...");
            guardedObject.complete(result);
        }, "t2").start();
    }
}

//创建一个GuardedObject桥梁对象用于在线程之间传递结果
@Slf4j
class GuardedObject {
    //要传递的结果,共享变量
    private Object response;

    //获取结果
    //timeout表示要等待的最大时间
    public Object get(long timeout) {
        synchronized (this) {
            long begin = System.currentTimeMillis();
            long passTime = 0; //记录线程已经等待的时间
            //线程进入等待状态的条件,为了防止虚假唤醒,需要使用while循环进行不停的判断,而不是只判断一次!
            while (response == null) {
                //waitTime表示本轮循环中线程要等待的时间,如果该值小于等于0就表示,线程无需再等待了!!!
                long waitTime = timeout - passTime;

                if (waitTime <= 0) {
                    log.info("线程等待时间已到达,没有获取到结果,自动结束等待!");
                    //注意:由于目前使用while循环防止虚假唤醒,因此当线程等待超过一定的时间(/最大等待时间被用完),
                    //那么线程就需要自动结束等待继续往后执行,即需要退出while循环,避免再进入下一轮等待!!!
                    break;
                }

                //没有获取到结果,等待
                try {
                    //注意:线程等待时间并不总是timeout,假如被提前虚假唤醒了,
                    //那么下一轮等待时间就是(等待的最大时间timeout - 上一轮已经等待过的时间)!!
                    this.wait(waitTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //线程已经等待的时间或者说线程经历过的时间
                passTime = System.currentTimeMillis() - begin;
            }

            return response;
        }
    }

    //产生结果
    public void complete(Object response) {
        synchronized (this) {
            //给成员变量赋值
            this.response = response;
            //唤醒所有处于WAITING状态的线程
            this.notifyAll();
        }
    }
}

在这里插入图片描述

1.3.join的原理

1>.join是Thread线程类中的方法;

2>.join源码

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            //join底层也使用了保护性暂停同步模式
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

1.4.同步模式之保护性暂停增强2

1.4.1.多任务版GuardedObject

1>.架构说明
在这里插入图片描述
说明:

①.图中Futures就好比居民楼一层的信箱(每个信箱有房间编号),左侧的t0,t2,t4就好比等待邮件的居民,右侧的t1,t3,t5就好比邮递员;

②.如果需要在多个类之间使用GuardedObject对象作为参数传递不是很方便,因此需要设计一个用来解耦的中间类,这样不仅能够解耦"结果等待者"和"结果生产者",还能够同时支持多个任务的管理;

2>.代码实现

@Slf4j
public class TestGuardedObject2 {
    public static void main(String[] args) throws InterruptedException{
        for (int i = 0; i < 3; i++) {
            new People(i+1).start();
        }

        TimeUnit.SECONDS.sleep(1);

        //找到所有的信箱编号
        for (Integer id : MailBoxes.getIds()) {
            //根据信箱编号找到信箱,将对应的信件
            new Postman(id, "将信件投递到编号为{" + id + "}的信箱中").start();
        }
    }
}

//业务类,居民从对应的GuardedObject对象中获取信件
@Slf4j
class People extends Thread {

    //居民编号
    private int no;

    public People(int no) {
        this.no = no;
    }

    @Override
    public void run() {
        //关联指定的GuardedObject对象(信箱)
        GuardedObject2 guardedObject = MailBoxes.createGuardedObject();
        log.info("居民{}开始从编号为{}的信箱中收信", this.no, guardedObject.getId());
        //如果获取到的信件为空,则该线程会释放对象锁进入WaitSet中变成WAITING状态进行等待一段时间
        Object result = guardedObject.get(5000);
        log.info("居民{}从编号为{}的信箱中收到内容为{}的信件", this.no, guardedObject.getId(), result == null ? null : result.toString());
    }
}

//业务类,邮递员将信件投递到对应的GuardedObject对象中
@Slf4j
class Postman extends Thread {

    //信件属性
    private int id;
    private String mail;

    public Postman(int id, String mail) {
        this.id = id;
        this.mail = mail;
    }

    @Override
    public void run() {
        //根据信件上的信箱ID查询到指定的GuardedObject对象(信箱)
        GuardedObject2 guardedObject = MailBoxes.getGuardedObject(id);
        log.info("邮递员将信件投递到编号为{}的信箱中,信件内容为{}", guardedObject.getId(), mail);
        //将信件投递到对应的GuardedObject对象(信箱)中
        guardedObject.complete(mail);
    }
}

//中间解耦类,里面管理了多个GuardedObject对象
class MailBoxes {
    //存放多个GuardedObject对象的容器,共享变量;线程安全
    private static Map<Integer, GuardedObject2> boxes = new ConcurrentHashMap<Integer, GuardedObject2>();

    //共享变量
    //ID就是信箱编号
    private static int id = 1;

    //产生唯一ID
    private static synchronized int generateId() {
        //临界区
        return id++;
    }

    //根据信件ID取出Map容器中指定的信箱,用于邮递员投递信件
    public static GuardedObject2 getGuardedObject(int id) {
        //避免线创建的GuardedObject对象多而导致Map容器占用大量的堆内存,这里使用remove()方法删除每次取出的GuardedObject对象
        return boxes.remove(id);
    }

    //创建信箱存入到Map容器中,便于后续使用
    public static GuardedObject2 createGuardedObject() {
        GuardedObject2 guardedObject2 = new GuardedObject2(generateId());
        boxes.put(guardedObject2.getId(), guardedObject2);
        return guardedObject2;
    }

    //获取所有信箱ID
    public static Set<Integer> getIds() {
        return boxes.keySet();
    }
}

//信箱类,用于邮递员投递信件以及居民类接收信件
@Slf4j
class GuardedObject2 {

    //标识多个GuardedObject对象
    //信箱编号
    private int id;

    //要传递的结果,共享变量
    private Object response;

    public GuardedObject2(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    //获取结果
    //timeout表示要等待的最大时间
    public Object get(long timeout) {
        synchronized (this) {
            long begin = System.currentTimeMillis();
            long passTime = 0; //记录线程已经等待的时间
            //线程进入等待状态的条件,为了防止虚假唤醒,需要使用while循环进行不停的判断,而不是只判断一次!
            while (response == null) {
                //waitTime表示本轮循环中线程要等待的时间,如果该值小于等于0就表示,线程无需再等待了!!!
                long waitTime = timeout - passTime;

                if (waitTime <= 0) {
                    log.info("线程等待时间已到达,没有获取到结果,自动结束等待!");
                    //注意:由于目前使用while循环防止虚假唤醒,因此当线程等待超过一定的时间(/最大等待时间被用完),
                    //那么线程就需要自动结束等待继续往后执行,即需要退出while循环,避免再进入下一轮等待!!!
                    break;
                }

                //没有获取到结果,等待
                try {
                    //注意:线程等待时间并不总是timeout,假如被提前虚假唤醒了,
                    //那么下一轮等待时间就是(等待的最大时间timeout - 上一轮已经等待过的时间)!!
                    this.wait(waitTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //线程已经等待的时间或者说线程经历过的时间
                passTime = System.currentTimeMillis() - begin;
            }

            return response;
        }
    }

    //产生结果
    public void complete(Object response) {
        synchronized (this) {
            //给成员变量赋值
            this.response = response;
            //唤醒所有处于WAITING状态的线程
            this.notifyAll();
        }
    }
}

在这里插入图片描述

1.5.异步模式之生产者与消费者

1.5.1.定义

1>.与前面的保护性暂停中的GuardObject不同,不需要产生结果和消费结果的线程一一对应;

2>.消费队列可以用来平衡生产和消费的线程资源;

3>.生产者仅负责产生结果数据,不关心数据该如何处理,而消费者专心处理结果数据;

4>.消息队列是有容量限制的,满时不会再加入数据,空时不会再消耗数据;

5>.JDK中各种阻塞队列,采用的就是这种模式;
在这里插入图片描述
注意:这里的消息队列是用于在Java进程内多个线程之间进行通信的,而不是其他的如RabbitMQ,RocketMQ等下消息队列框架是在多个Java进程之间通信,千万别弄混了!!!

1.5.2.实现

@Slf4j
public class TestConsumerOrProduct {
    public static void main(String[] args) {
        MessageQueue messageQueue = new MessageQueue(2);

        for (int i = 0; i < 3; i++) {
            int id = i+1;
            new Thread(() -> {
                //注意:lambda表达式中引用的外部局部变量必须是"final"修饰的!!!
                //可以在lambda表达式外部定义的一个局部变量接收外部的局部变量!
                messageQueue.put(new Message(id, "消息:" + id));
            }, "生产者线程:" + (i + 1)).start();
        }

        new Thread(()->{
            while (true){
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                messageQueue.take();
            }
        },"消费者线程").start();
    }
}

//消息队列类,Java进程内多个线程之间的通信
@Slf4j
class MessageQueue {

    //创建一个存储message对象的容器--双向链表
    private LinkedList<Message> linkedList = new LinkedList<Message>();
    private int capacity;

    public MessageQueue(int capacity) {
        this.capacity = capacity;
    }

    //消费消息
    public Message take() {
        synchronized (linkedList) {
            while (linkedList.isEmpty()) {
                try {
                    log.info("消息队列为空,无法消费消息,[{}]等待", Thread.currentThread().getName());
                    //消息队列为空,线程等待,释放对象锁
                    linkedList.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //从链表的头部取元素(Message对象)
            Message message = linkedList.removeFirst();
            log.info("[{}]已经消费了一个消息{}", Thread.currentThread().getName(), message);
            //消费了一个消息,消息队列中有空位,唤醒生产者线程,生产消息
            linkedList.notifyAll();
            return message;
        }
    }

    //生产消息
    public void put(Message message) {
        synchronized (linkedList) {
            while (linkedList.size() == this.capacity) {
                try {
                    log.info("消息队列已满,无法生产消息,[{}]等待", Thread.currentThread().getName());
                    //消息队列已满,线程等待,释放对象锁
                    linkedList.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //从链表的尾部添加元素(Message对象)
            linkedList.addLast(message);
            log.info("[{}]已经生产了一个消息{}", Thread.currentThread().getName(), message);
            //生产一个消息,消息队列中不为空,唤醒消费者线程,消费消息
            linkedList.notifyAll();
        }
    }
}

//消息类
//保证线程安全:①.final修饰,没有子类;②.没有修改属性的setter方法;
final class Message {
    private int id;
    private Object value;

    public Message(int id, Object value) {
        this.id = id;
        this.value = value;
    }

    public int getId() {
        return id;
    }

    public Object getValue() {
        return value;
    }

    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", value=" + value +
                '}';
    }
}

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vitepress(一):基础教程

什么是Vitepress Vitepress是使用Vue3Vite来快速搭建一个个人网站的工具&#xff0c;网站搭建者不需要掌握Vue3&#xff0c;Vite等的具体内容&#xff0c;只需要简单的配置就可以生成Vue风格的个人网站 官方地址&#xff1a;https://vitejs.cn/vitepress/ 本教程希望教会大家…

SD Nand 与 SD卡 SDIO模式应用流程

SD Nand/SD卡 SDIO模式应用流程 文章目录SD Nand/SD卡 SDIO模式应用流程1. 前言1.1 参考文档1.2 概述2. Response响应类型及格式3. 各步骤流程3.1 卡识别流程3.2 通讯速率及总线宽度修改流程3.3 擦除流程3.4 单块读流程3.5 单块写流程3.6 多块读流程3.7 多块写流程4. 结束语SD …

Java初识泛型 | 如何通过泛型类/泛型方法实现求数组元素最大值?

目录 一、引言 二、编程分析 1、泛型类实现 思路 代码 2、泛型方法实现 思路 代码 三、拓展&#xff1a;数组排序&#xff08;以冒泡排序为例&#xff09; 1、int类型 原代码 2、泛型类 3、泛型方法 一、引言 给定一个整型数组&#xff0c;求数组中所有元素的最大…

JVM知识体系学习三:class文件初始化过程、硬件层数据一致性(硬件层)、缓存行、指令乱序执行问题、如何保证不乱序(volatile等)

文章目录前言一、class文件初始化过程1、概述2、初始化过程-案例1a、代码T001_ClassLoadingProcedure 类加载过程b、解析3、初始化过程-案例2a、代码b、解析二、单例模式-双重检查三、硬件层数据一致性1、硬件层的并发优化基础知识b、Intel 的缓存一致性协议&#xff1a;MESI四…

Vivado综合设置之-keep_equivalent_registers

-keep_equivalent_registers即保留等效寄存器&#xff0c;所谓等效寄存器是指共享输入端口&#xff08;输入时钟端口clk和输入数据端口rst&#xff09;的寄存器。 勾选它时&#xff0c;意味着Vivado不会对等效寄存器进行优化&#xff1b; 不勾选它时&#xff08;默认情况&…

eclipse安装UML插件

安装AmaterasUML AmaterasUML 是一个用于 Eclipse 的轻量级 UML 和 ER 图编辑器。 将AmaterasUML的3个jar包拷到Eclpise的plugins文件下&#xff1a; 重启eclipse 在新建菜单中可以发现已经出现了UML文件选项 安装GEF插件&#xff08;Eclipse2018-12 以后无需安装&#xf…

②电子产品拆解分析-电动牙刷

②电子产品拆解分析-电动牙刷一、功能介绍二、电路分析以及器件作用1、振动电机开关控制电路2、锂电池供电与充电电路三、本产品的优缺点1、优点&#xff1a;2、缺点&#xff1a;一、功能介绍 ①5档工作模式&#xff1b;②2分钟倒计时停止工作&#xff1b;③工作续航一个星期以…

【MySQL】详解索引操作

索引什么是索引&#xff1f;索引的优势和劣势索引类型按数据结构分类按物理存储分类按字段特性分类主键索引唯一索引普通索引全文索引前缀索引按字段个数分类索引操作创建索引创建主键索引唯一索引的创建普通索引的创建全文索引的创建explain工具查询索引删除索引索引最好设置为…

SQL 注入学习路线

学习路线&#xff08;大致&#xff09; HTML > SQL > Python > SQL 注入&#xff08;使用 sqli-labs 靶场来学习 SQL 注入&#xff09; HTML 视频 【前端开发入门教程&#xff0c;web前端零基础html5 css3前端项目视频教程】 要求 使用该视频进行 HTML 基础部分…

Python之字符串的特点

1.布尔值 Python2中没有布尔值&#xff0c;直接用数字0表示Flase&#xff0c;用数字1表示True。Python3中&#xff0c;把True和False定义成了关键字&#xff0c;但他们的本质还是1和0&#xff0c;甚至可以和数字相加。 >>> a True >>> b 3 >>> …

[多图,秒懂]如何训练一个“万亿大模型”?

1. 背景近几年&#xff0c;随着“大模型”概念的提出&#xff0c;深度学习模型越来越大&#xff0c;如何训练这些大模型成为一个亟待解决的工程问题。最初的视觉模型只有几百兆的参数量&#xff0c;而现在的语言模型中&#xff0c;动则百亿&#xff0c;千亿的参数量&#xff0c…

[golang工作日记] for range 踩坑

1、for range指针赋值 Ops的数据保存在两个表中&#xff0c;一个是ops_tab&#xff0c;另一个是staff_tab&#xff0c;其中ops_tab的staff_id是staff_tab的外键&#xff0c;两个表都有staff_id字段。 type OpsDetail struct {OpsId stringStaffId stringOps *model.Ops…

Node.js安装配置

目录1. 下载node2. 安装3. 检查是否安装成功4. 配置缓存路径5. 实现一个demo1. 下载node 官方地址https://nodejs.org/en/下载长期支持版本 2. 安装 一路next node一起安装npm 这个地方不要勾选 安装成功 3. 检查是否安装成功 cmd运行下面命令 # 查看node的版本 node -v # …

【博客571】“时序敏感应用“ 如何prometheus自定义上报时间戳

“时序敏感应用” 如何prometheus自定义上报时间戳 1、场景 在物理网络监控中&#xff0c;对于流量趋势是极其敏感的&#xff0c;物理网络监控流量的点通常是秒级别甚至毫秒级别&#xff0c;此时这些时许点通过各种上报上传到监控系统中&#xff0c;由于网络波动&#xff0c;可…

Kubernetes v1.24.2高可用部署

sskubeasz 1、Kubeasy简介 kubeasz 致力于提供快速部署高可用k8s集群的工具, 同时也努力成为k8s实践、使用的参考书&#xff1b;基于二进制方式部署和利用ansible-playbook实现自动化&#xff1b;既提供一键安装脚本, 也可以根据安装指南分步执行安装各个组件。 kubeasz 从每…

【lc刷题 day12】堆/栈/队列

BM42 用两个栈实现队列 easy import java.util.Stack;public class Solution {Stack<Integer> stack1 new Stack<Integer>();Stack<Integer> stack2 new Stack<Integer>();public void push(int node) {stack1.push(node);}public int pop() {if(st…

随便聊聊浪潮开务数据库

今天这个话题挺随意&#xff0c;我们来聊聊浪潮开务数据库&#xff0c;原因主要是我的微信朋友圈被这个数据库刷屏了。当然我对这款号称多模数据库的非开源数据库也很感兴趣&#xff0c;也有很多疑问&#xff0c;希望各位专家能帮忙答疑解惑&#xff0c;揭开这款即将发布的 Kai…

机器学习--多层感知机、卷积神经网络、循环神经网络

目录 一、多层感知机 二、卷积神经网络 三、循环神经网络 总结 一、多层感知机 手工提取特征&#xff08;用人的知识进行&#xff09; --> 神经网络来提取特征。 神经网络&#xff08;可能更懂机器学习&#xff09;来提取 可能对后面的线性或softmax回归可能会更好一…

【UE4 第一人称射击游戏】23-添加子弹伤害

上一篇&#xff1a;https://blog.csdn.net/ChaoChao66666/article/details/128589063?spm1001.2014.3001.5501本篇效果&#xff1a;步骤&#xff1a;创建一个蓝图类&#xff08;父类为Character&#xff09;&#xff0c;命名为“SimpleAI”双击打开“SimpleAI”&#xff0c;点…

非对称加密实战(一):JDK生成keystore获取公钥私钥及代码验证【附源码】

目录使用说明非对称加密生成keystore文件公钥私钥互相解密获取fd-alias.keystore中的公钥私钥使用生成公钥私钥进行解密源码地址使用说明 非对称加密 非对称加密算法主要有&#xff1a;RSA、Elgamal、背包算法、Rabin、D-H、ECC&#xff08;椭圆曲线加密算法&#xff09;。下…