java多线程编程模式
前言
区别于java设计模式,下面介绍的是在多线程场景下,如何设计出合理的思路。
不可变对象模式
场景
1. 对象的变化频率不高
每一次变化就是一次深拷贝,会影响cpu以及gc,如果频繁操作会影响性能
2. 作为hashmap的key
key如果是可变的,那么会无法从hashmap中找到原来的数据
3. 单线程写,多线程读或者遍历等场景
这种场景在读或写的任何操作都不需要加锁,如果是多线程场景那么在写的时候需要加锁。
思路
让对象从初始化开始就不能被修改从而满足天然的线程安全条件,也就是说其他任何操作都是读操作,不再有写操作。当该对象遇到需要写操作的场景时,再通过对其深拷贝的方式,创建出一个新的对象来代替。核心特征有下面3个
1. 类用final修饰
2. 所有字段用final修饰
3. 如果用到其他可变的对象,那么再对外提供对象时需要进行深拷贝。
JDK案例
CopyOnWriteArrayList
每一次写操作都会深拷贝其内部的一个数组。只需要在写的时候枷锁,这是为了防止多线程写导致的并发问题,在读取或者遍历的时候不用加锁。所以这个数据结果的场景是多读少写的场景。
保护性暂挂模式
场景
线程a想要执行一个操作,但是需要等待线程b完成另一个操作
思路
抽象出中间类(下面用block代替)来保证线程安全和同步,将线程a需要执行的逻辑传给block,block基于java的Lock和Condition实现通用的await和notify,线程b在操作完后调用block的释放方法。说白了就是把await和notify提取出来,实现和对象无关的等待唤醒。
JDK案例
LinkedBlockingQueue
LinkedBlockingQueue采用了两类锁,put锁和take锁,也就是读锁和写锁。与之对应的衍生出了两个Condition,这个队列的特点是阻塞,当put的时候如果队列满了,那么会阻塞直到队列有空间,take操作也一样,如果队列没数据则会一直等待直到有获取到数据。
两阶段模式
场景
- 需要在优雅的关闭某个线程,比如某个sock正在循环监听
- 需要在JVM结束前结束某个工作线程(与守护线程相对)
思路
所谓两阶段终止,就是把停止1个线程拆成两步,第一步修改线程中的停止标志位,常见的线程都是自循环的,改变标志位意味着在此次逻辑后不再进入下一循环;第二步是中断线程,每个线程都有自己的中断逻辑,比如在wait的都notify了,在sleep的都interrupt了,从而达到快速停止的效果。
JDK案例
ThreadPoolExecutor
ThreadPoolExecutor.shutdown()的实现思路就是将状态置为SHUTDOWN,然后将没有工作的线程直接中断interrupt,最后等待正在工作的线程执行完最后一段逻辑。
承诺模式
场景
在保护性暂挂模式场景下,a线程需要b线程的执行结果,但是除此之外,a线程还需要其他操作,也就是说需要两个线程一起执行。
思路
a线程先提交b线程,并获取b线程的执行小票,等a线程执行完自己的逻辑后再根据执行小票获取b线程的执行结果。
JDK案例
FutureTask
java自带了promise的库,可以直接使用FutureTask类,再通过线程提交,从而达到异步效果。
生产者消费者模式
场景
生产者消费者模式可能是我们接触的最多的模式了,比如事件分发,任务调度
思路
通过将生产者线程和消费者线程解耦,引入通道的概念,让生产者把数据发到通道中,消费者再从通道中获取数据
JDK案例
ThreadPoolExecutor
ThreadPoolExecutor的整体结构就是生产者和消费者,客户端在submit任务或者execute任务的时候起到生产者的操作,当最大线程数到达阈值后,新进来的任务就会加入队列,而ThreadPoolExecutor本身的构造函数就需要一个阻塞队列,起到管道的作用,最后ThreadPoolExecutor内部有一个线程池来不断的获取管道的任务,从而执行任务。
主动对象模式
场景
这个模式的名称听起来可能有点抽象,其实就是抽象出一个对象来管理和维护异步任务执行,并对外提供任务提交等接口。对这听起来就是一个线程池的功能。
思路
将异步任务的提交和执行解耦,构建一个专门维护所有异步任务的对象,当使用者需要执行异步任务,那么可以将异步任务提交给该对象,并快速返回,不用再关心任务的执行和调度。
JDK案例
ThreadPoolExecutor
ThreadPoolExecutor管理了一个线程池用于执行异步任务(这个模式不关心是线程还是线程池,只是想表达有一个能够独立维护管理异步任务执行的对象),并对外提供了submit和execute两个提交任务的方法,这两个方法原理一样,只是submit会将Runnable对象封装成FutureTask对象,从而可以获取返回值。当客户端调用这两个方法的时候,ThreadPoolExecutor会根据当前的线程数量,队列空间来决定任务的执行,等待和拒绝,这些过程对客户端来说都是无需等待的。
线程池模式
场景
需要周期性的去进行异步操作,要知道创建和销毁线程的代价是很大的,所以需要对零散的线程进行统一管理。
思路
通过构建一个线程池列表,维护所有的线程。为了满足不同的cpu资源使用场景需要,需要能够配置线程池的最大线程数最限制。为了减少线程在空闲时间占用的资源,需要能够配置对空闲线程的回收时间以及常驻线程数量大小。为了提供异步任务排队的概念,需要能够配置待执行任务的队列。为了能自己控制创建线程的属性,需要能够配置线程构建工厂。为了解决异步任务提交失败的场景,需要能够配置任务提交的出错策略。说了这么多,其实就在说ThreadPoolExecutor的构造函数。
JDK案例
ThreadPoolExecutor
ThreadPoolExecutor是JDK1.5之后提供的一个线程池实现,强力推荐使用。下面列一个典型的构建函数实现。
// 创建一个
// 常驻线程数为2,
// 最大线程数量上限为10,
// 空闲线程过60s就回收,
// 任务等待队列为最大容量为10的基于链表的阻塞队列
// 线程的创建为默认线程工厂,
// 任务提交失败则抛出异常
// 线程池 ThreadPoolExecutor threadPoolExecutor
= new ThreadPoolExecutor(
2,
10,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(20),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor. AbortPolicy());
线程特有存储模式
场景
在多线程场景下,某个对象需要被共享给多个线程,并且多个线程会对此对象进行修改和读取操作,除此之外,共享的对象占用空间很小,修改的频率很高。最常见的就是利用线程本地存储来共享一些环境配置。
思路
在高频率的多线程修改场景下,需要尽可能的避免锁,否则线程之间会疯狂竞争锁导致性能下降。那么将这个对象在每个线程中都有一个拷贝是很好的选择,每个线程维护各自的对象,不需要加任何锁。
JDK案例
ThreadLocal
ThreadLocal通过Thread中内置的ThreadMap来存储数据,从而实现每个线程拥有各自的对象。ThreadMap中用ThreadLocal作为key,存储的数据作为value。需要注意的是,当该某个线程执行完之后,需要手动把该线程的数据remove,避免内存泄露。
说起来线程特有存储模式和之前讲到的不可变模式的思路有点像,只是前者缓存了对象,后者在需要用对象的时候重新深拷贝一个。可以说是用空间换时间的操作。
串行线程封闭模式
把多个异步任务加入队列,用单工作线程去执行,从而实现串行的效果。感觉这个模式可以简单理解为最大线程数是1的线程池,就不多说了。
主仆模式
思路
将一个复杂的单个任务拆成多个子任务,每个子任务由不同的线程去执行,执行完后再汇总。这就形成了主仆模式
流水线模式
思路
可以理解成串行封闭模式+主仆模式
半同步半异步模式
思路
对异步任务执行进行aop,意思就是说可以自定义异步任务的执行前,执行后进行的相关逻辑,从而实现相关同步的操作。
总结
JDK提供了很多开箱即用的对象,特别是ThreadPoolExecutor,囊括了多种编程模式。
参考
《Java多线程编程实战指南-设计模式篇》
java多线程编程模式的更多相关文章
- Java多线程编程模式实战指南(三):Two-phase Termination模式
停止线程是一个目标简单而实现却不那么简单的任务.首先,Java没有提供直接的API用于停止线程.此外,停止线程时还有一些额外的细节需要考虑,如待停止的线程处于阻塞(等待锁)或者等待状态(等待其它线程) ...
- Java多线程编程模式实战指南(三):Two-phase Termination模式--转载
本文由本人首次发布在infoq中文站上:http://www.infoq.com/cn/articles/java-multithreaded-programming-mode-two-phase-t ...
- Java多线程编程模式实战指南(二):Immutable Object模式--转载
本文由本人首次发布在infoq中文站上:http://www.infoq.com/cn/articles/java-multithreaded-programming-mode-immutable-o ...
- Java多线程编程模式实战指南:Active Object模式(上)
Active Object模式简介 Active Object模式是一种异步编程模式.它通过对方法的调用与方法的执行进行解耦来提高并发性.若以任务的概念来说,Active Object模式的核心则是它 ...
- Java多线程编程模式实战指南(二):Immutable Object模式
多线程共享变量的情况下,为了保证数据一致性,往往需要对这些变量的访问进行加锁.而锁本身又会带来一些问题和开销.Immutable Object模式使得我们可以在不使用锁的情况下,既保证共享变量访问的线 ...
- Java多线程编程模式实战指南一:Active Object模式(上)
Active Object模式简介 Active Object模式是一种异步编程模式.它通过对方法的调用与方法的执行进行解耦来提高并发性.若以任务的概念来说,Active Object模式的核心则是它 ...
- Java多线程编程模式实战指南之Promise模式
Promise模式简介(转) Promise模式是一种异步编程模式 .它使得我们可以先开始一个任务的执行,并得到一个用于获取该任务执行结果的凭据对象,而不必等待该任务执行完毕就可以继续执行其他操作.等 ...
- Java多线程编程模式实战指南(一):Active Object模式--转载
本文由黄文海首次发布在infoq中文站上:http://www.infoq.com/cn/articles/Java-multithreaded-programming-mode-active-obj ...
- Java多线程编程模式实战指南:Active Object模式(下)
Active Object模式的评价与实现考量 Active Object模式通过将方法的调用与执行分离,实现了异步编程.有利于提高并发性,从而提高系统的吞吐率. Active Object模式还有个 ...
随机推荐
- 交换机-查看mac地址表
1.使用交换机命令行 en 或者 enable 进入特权模式 Switch> Switch>en Switch# Switch# 2.查看交换机中的MAC地址表 Switch#sh ...
- Kubernetes 1.8火热出炉:稳定性、安全性与存储支持能力全面提升
版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/M2l0ZgSsVc7r69eFdTj/article/details/78130225 周三,Kub ...
- 008-jdk1.7版本新特性
一.JDK1.7 名称:Dolphin(海豚) 发布日期:2011-07-28 新特性: 1.1.switch-case中可以使用字串 区分大小写.Java编译器通过switch使用String对象的 ...
- 万恶之源 - Python初识函数
什么是函数 我们目前为止,已经可以完成一些软件的基本功能了,那么我们来完成这样一个功能:约x pint("拿出手机") print("打开陌陌") print( ...
- [kx]宇宙-银河
行星/恒星/卫星的区分 目前太阳系内有8颗行星,分别是:水星.金星.地球.火星.木星.土星.天王星.海王星. 参考 恒星是自发光,而行星(行星通常指自身不发光,其公转方向常与所绕恒星的自转方向相同.) ...
- 用Python实现的数据结构与算法:开篇
一.概述 用Python实现的数据结构与算法 涵盖了常用的数据结构与算法(全部由Python语言实现),是 Problem Solving with Algorithms and Data Struc ...
- Centos安装ELK5.3.2
一.注意情况 1.elk的版本要一致. 2.ElasticSearch是基于lucence开发的,也就是运行需要java支持.所以要先安装JAVA环境.由于es5.x依赖于JDK1.8,所以需要安装J ...
- 梯度消失与梯度爆炸 ==> 如何选择随机初始权重
梯度消失与梯度爆炸 当训练神经网络时,导数或坡度有时会变得非常大或非常小,甚至以指数方式变小,这加大了训练的难度 这里忽略了常数项b.为了让z不会过大或者过小,思路是让w与n有关,且n越大,w应该越小 ...
- [lr & ps] 色彩空间管理
色彩空间 • 定义 色彩空间,Color Space,又称作色域.在色彩学中,人们建立了许多色彩模型,以一维.二维.三维甚至四维空间坐标来表示某一色彩,这种坐标系统所能定义的色彩范围即色彩空间.我们经 ...
- Header实现文件下载
function download($file){ //文件根路径 $filename=$_SERVER['DOCUMENT_ROOT'].__ROOT__.'/'.$file; //下载文件 if( ...