前言

区别于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操作也一样,如果队列没数据则会一直等待直到有获取到数据。

两阶段模式

场景

  1. 需要在优雅的关闭某个线程,比如某个sock正在循环监听
  2. 需要在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多线程编程模式的更多相关文章

  1. Java多线程编程模式实战指南(三):Two-phase Termination模式

    停止线程是一个目标简单而实现却不那么简单的任务.首先,Java没有提供直接的API用于停止线程.此外,停止线程时还有一些额外的细节需要考虑,如待停止的线程处于阻塞(等待锁)或者等待状态(等待其它线程) ...

  2. Java多线程编程模式实战指南(三):Two-phase Termination模式--转载

    本文由本人首次发布在infoq中文站上:http://www.infoq.com/cn/articles/java-multithreaded-programming-mode-two-phase-t ...

  3. Java多线程编程模式实战指南(二):Immutable Object模式--转载

    本文由本人首次发布在infoq中文站上:http://www.infoq.com/cn/articles/java-multithreaded-programming-mode-immutable-o ...

  4. Java多线程编程模式实战指南:Active Object模式(上)

    Active Object模式简介 Active Object模式是一种异步编程模式.它通过对方法的调用与方法的执行进行解耦来提高并发性.若以任务的概念来说,Active Object模式的核心则是它 ...

  5. Java多线程编程模式实战指南(二):Immutable Object模式

    多线程共享变量的情况下,为了保证数据一致性,往往需要对这些变量的访问进行加锁.而锁本身又会带来一些问题和开销.Immutable Object模式使得我们可以在不使用锁的情况下,既保证共享变量访问的线 ...

  6. Java多线程编程模式实战指南一:Active Object模式(上)

    Active Object模式简介 Active Object模式是一种异步编程模式.它通过对方法的调用与方法的执行进行解耦来提高并发性.若以任务的概念来说,Active Object模式的核心则是它 ...

  7. Java多线程编程模式实战指南之Promise模式

    Promise模式简介(转) Promise模式是一种异步编程模式 .它使得我们可以先开始一个任务的执行,并得到一个用于获取该任务执行结果的凭据对象,而不必等待该任务执行完毕就可以继续执行其他操作.等 ...

  8. Java多线程编程模式实战指南(一):Active Object模式--转载

    本文由黄文海首次发布在infoq中文站上:http://www.infoq.com/cn/articles/Java-multithreaded-programming-mode-active-obj ...

  9. Java多线程编程模式实战指南:Active Object模式(下)

    Active Object模式的评价与实现考量 Active Object模式通过将方法的调用与执行分离,实现了异步编程.有利于提高并发性,从而提高系统的吞吐率. Active Object模式还有个 ...

随机推荐

  1. 【Jmeter】如何通过文件导入方式对用户名和密码进行参数化设置

    JMeter 参数化 注意:param和data body只能用一个.所有任何一个里面有内容,切换都会报错,这不是问题,jmeter是这么设计的 方法一:通过添加CSV Data Set Config ...

  2. windows平台mysql密码设置

    登录mysql默认没有指定账号 查看默认账号是谁 select user(); mysql> select user();+----------------+| user() |+------- ...

  3. 配合dedecms内容模型实现后台输入栏目id前端输出文章列表

    为了简化开发的工作量,也方便编辑快速操作,决定将后台进行重新设置.配合dedecms内容模型实现后台输入栏目id前端输出文章列表,这样制作科室专题页也变快了很多.比如,我们添加一个“科室专家栏目id” ...

  4. 帝国cms调用缩略图和具体文章的方法

    我们在用帝国cms建站的时候经常会在首页或者分类页等调用一些文章,如果文章带有展示图也把图片调用出来.帝国cms调用缩略图和具体文章怎么操作呢?我们用帝国cms的灵动标签[e:loop],只要记住常用 ...

  5. vue学习之六路由系统

    一.vueRouter实现原理 VueRouter的实现原理是根据监控锚点值的改变,从而不断修改组件内容来实现的,我们来试试不使用VueRouter,自己实现路由控制,如下代码: <!DOCTY ...

  6. 如何实现在H5里调起高德地图APP?

    http://www.cnblogs.com/milkmap/p/5912350.html 这一篇文章,将讲述如何在H5里调起高德地图APP,并展示兴趣点.适合于展示某个餐馆,商场等,让用户自行选择前 ...

  7. [LeetCode] 310. Minimum Height Trees_Medium tag: BFS

    For a undirected graph with tree characteristics, we can choose any node as the root. The result gra ...

  8. opencv之颜色过滤只留下图片中的红色区域

    如图,这次需要在图片中找到卷尺的红色刻度,所以需要对图像做过滤,只留下红色部分. 一开始的想法是分别找到RGB值,然后找到红色区域的部分保留就可以了,不过好像很难确定红色区域的RGB取值范围,所以要把 ...

  9. 怎样在div中添加图片或设置颜色

    1.插入图片<div><img src="图片地址" /></div>2.图片做背景<div style="background ...

  10. Fortran入门:Windows平台的Fortran编译器安装和使用

    因为课程需要,今年开始学习FORTRAN语言.之前学校的计算概论用的是C,后来又学了C++和Python作为面向对象的工具,数值计算方面主要通过学校的许可证用的MATLAB.因为专业侧重数值模拟和反演 ...