当前位置: 首页 > news >正文

【JavaEE】多线程案例

 1.单例模式

    单例模式就是在设计某个程序时,某个类只能实例一次对象,如果再次实例一个对象就会报错。然而我们人类有时也是会出错的,只有机器是固定的,我们就是通过编译器编写一个强制要求的代码实现单例模式。

单例模式又分两种,一种是饿汉模式,另一种是懒汉模式。

1.1饿汉模式

饿汉模式是通过方法得到已创建的实例对象,而实例对象是在类加载时创建的(比较急切的想要)。

class Singleton{private static Singleton singleton = new Singleton(); //将其实例对象设置为私有,并设置为静态变量,以方便别的成员调用public static Singleton getSingleton(){//通过方法获得已创建的对象return singleton;}private Singleton(){//将调用方法设置为私有,为了防止重新实例对象(重新实例会报错),}
}

1.2懒汉模式

懒汉模式是在第一次使用时创建的(比较从容,用到了再创建)。

针对这两个模式分析,如果都再多线程条件下是否线程安全?

饿汉模式安全,懒汉不安全。之前讲述的线程安全问题出现的情况有:多个线程同时修改一个变量等。何况懒汉模式中还涉及到读取和修改,更容易被多线程穿插【在判断是否为空时,先读取后存在并执行另一个线程,这样就是穿插,如下图】。解决线程安全问题的主要做法就是:加锁

加锁就是要阻止存在穿插行为,既然如此,那我们直接在判断是否为空前加锁,如下图:

我们在深入研究,又出现了新的问题。我们实现懒汉模式只需要创建一次对象,而我们为了防止第一次创建而加锁导致后面的每一次调用对象都需要加锁,然而加锁就会出现锁冲突,锁冲突就容易导致阻塞等待。解决这个问题只需要添加 if 从句,判断是否是第一次创建。

指令重排序:编译器为了提高效率,可能会调整代码顺序(前提是保证逻辑不变)。

那这样就没有问题了吗?答:在创建对象的过程中可能出现指令重排序。new操作可以拆分成三步,第一步:申请空间;第二步:在内存空间上构造对象(构造方法);第三步:把内存的地址赋值给new的对象singlelazy。指令重排序可能将顺序调整为1 3 2,这样在单线程也是可以执行的。就是如果是多线程下,可能就会出现问题,比如:线程一执行到new操作,指令重排序将顺序调整后,先执行第一步(必须先有空间才能之后后面操作),再执行第三步将内存地址赋值给singlelazy ,此时的内存中是一个还没有初始化的非法对象。然后线程二开始执行,此时线程二中的singlelazy是非空的并且内存地址是没初始化的,然后 if 从句不满足直接跳出,最后将没有初始化的地址给了线程二中创建对象的变量,此时它指向的内存空间是非法的就出现问题了。

针对这个问题,我们可以用到volatile【之前提到的内存可见性也是编译器为了优化】。以下是懒汉模式单例模式的代码(此代码如果仔细研究还是存在些问题,正常下使用也是可以的):

class Singletonlazy{private static volatile Singletonlazy singletonlazy = null;//先将变量置为空public static Singletonlazy getSingletonlayz(){if (singletonlazy == null) {synchronized (Singletonlazy.class) {if (singletonlazy == null) {return singletonlazy = new Singletonlazy();}}}return singletonlazy;}private Singletonlazy(){}
}

2.阻塞队列

阻塞队列:

1.线程安全

2.带有阻塞特性:

(a) 如果队列为空,继续出队列就会发生阻塞,阻塞到其他线程往队列里添加元素为止;

(b) 如果队列为满,继续入队列就会发生阻塞,阻塞到其他线程从队列中取走元素为止;

阻塞队列最大的用处就是用来实现“生产者消费者模型”;

生产者消费者模型

生产者负责生产物品,而消费者负责消耗东西。当生产者生产过快时就可以休息会;相反当生产者生产过慢时消费者就需要等待生产者生产物品。

生产者消费者模型意义
1.解耦合

两个模块,联系越大耦合越高。对分布式系统来说,解耦合的意义是很大的。

2.削峰填谷 

短时间内收到大量请求,服务器会把大量的请求写入阻塞队列中,其他服务器则正常从阻塞队列中获取请求。

实现方式

在Java标准库里已经提供了阻塞队列,我们直接使用即可,但是身为未来的程序员,我们还是需要知道它的底层实现原理并能熟习。

我们先讲标准库里的:BlockingQueue既能基于数组又能基于链表的,它是继承自Queue,所以可以使用Queue中的各种方法,但是这些都不具备 “阻塞” 的特性。put:阻塞式入队列;take:阻塞式出队列;

现在是我们自己实现的阻塞队列

其实很简单,我们只需要在普通的队列中加上线程安全以及阻塞即可,一个普通的队列怎么实现基于数组又基于链表,那就需要拿出我们的环形队列。

环形队列

想要实现阻塞功能,可以通过wait 、notify 实现,put 被阻塞等待 take 来唤醒,而 take 被阻塞等待 put 来唤醒。得到以下代码:

这个代码还有一个问题,就是当 put 方法因为队列满了,进入 wait 之后,wait 被唤醒时队列不一定时不满的。interrupt 方法是可以中断 wait 方法的,使用 interrupt 唤醒时会出现 InterrupttedException ,这个异常就是 wait 抛出的,我们使用 throws 是没有问题的,但是如果使用 try/catch 是有问题的,如果出现异常就直接往下进行了,接着就会把 tail 指向的元素覆盖掉,就相当于把一个有效的元素删掉了,并且此时队列还是满的,size 还会继续增加,后续还有继续put 。所以在使用 wait 时一定要注意是 notify 唤醒的还是 interrupt 唤醒的

针对这个问题如何解决呢?可以在 wait 结束后再判断是否 size 超过最大限制。

执行完下一个 if 后又面临同样的问题【interrupt唤醒还是notify唤醒】,那就接着 if ,如此一来就成了死循环了,不如直接用 while 实现知道队列不满。

在这过程中,size 、head 、tail 这三个变量不断的读取、修改,为了防止内存可见性,我们都给它添加一个 volatile 。以下就是阻塞队列实现的全部代码:

class MyBlockingQueue{private String[] queue = new String[20];private volatile int size;//这里选择通过一个变量来记录元素个数private volatile int head;//头部private volatile int tail;//尾部private final Object lock = new Object();public void put(String elem) throws InterruptedException {synchronized (lock) {while (size == queue.length) {lock.wait();}queue[tail] = elem;size++;tail++;if (tail == queue.length) {tail = 0;}lock.notify();}}public String take() throws InterruptedException {synchronized (lock) {while (size == 0) {lock.wait();}String temp = queue[head];size--;head++;if (head == queue.length) {head = 0;}lock.notify();return temp;}}
}

接着我们针对这个阻塞队列实现一个生产者消费者模型:

这里我们只实现 生产者较慢的情况,剩下的生产者快就是将消费者速度降低(直接在消费者中添加sheep可以实现),代码及结果如下:

生产者生产一个就消费者消费一个,下面是生产多个的结果

http://www.dtcms.com/a/363057.html

相关文章:

  • 删除⽂件之git
  • 前端20个高效开发的JS工具函数
  • 《水浒智慧》第二部“英雄是怎么炼成的”(下篇)读书笔记
  • 宋红康 JVM 笔记 Day11|直接内存
  • 怎么用redis lua脚本实现各分布式锁?Redisson各分布式锁怎么实现的?
  • Higress云原生API网关详解 与 Linux版本安装指南
  • lua脚本在redis中如何单步调试?
  • docker 安装 redis 并设置 volumes 并修改 修改密码(二)
  • MATLAB矩阵及其运算(四)矩阵的运算及操作
  • 互联网大厂求职面试记:谢飞机的搞笑答辩
  • Linux为什么不是RTOS
  • 对矩阵行化简操作几何含义的理解
  • 集群无法启动CRS-4124: Oracle High Availability Services startup failed
  • TSMC-1987《Convergence Theory for Fuzzy c-Means: Counterexamples and Repairs》
  • uni-app 实现做练习题(每一题从后端接口请求切换动画记录错题)
  • Nginx的反向代理与正向代理及其location的配置说明
  • 久等啦!Tigshop O2O多门店JAVA/PHP版本即将上线!
  • SpringBoot3 + Netty + Vue3 实现消息推送(最新)
  • B树和B+树,聚簇索引和非聚簇索引
  • 云计算学习100天-第44天-部署邮件服务器
  • vscode炒股插件-韭菜盒子AI版
  • 小白H5制作教程!一分钟学会制作企业招聘H5页面
  • Linux 环境配置 muduo 网络库详细步骤
  • WPF 开发必备技巧:TreeView 自动展开全攻略
  • gbase8s之导出mysql导入gbase8s
  • WebSocket STOMP协议服务端给客户端发送ERROR帧
  • 串口服务器技术详解:2025年行业标准与应用指南
  • 大文件稳定上传:Spring Boot + MinIO 断点续传实践
  • DevOps部署与监控
  • WPF中的DataContext以及常见的绑定方式