前言

区别于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. 【HTML5】实例练习

    1.许多时髦的网站都提供视频.如果在网页上展示视频? <!DOCTYPE HTML> <html> <body> <video width="320 ...

  2. OC convertRect

    举个例子: redView = [[UIView alloc]initWithFrame:CGRectMake(50, 100, 100, 100)]; redView.backgroundColor ...

  3. Scala集合类详解

    对scala中的集合类虽然有使用,但是一直处于一知半解的状态.尤其是与java中各种集合类的混合使用,虽然用过很多次,但是一直也没有做比较深入的了解与分析.正好趁着最近项目的需要,加上稍微有点时间,特 ...

  4. Python安装常见问题(1):zipimport.ZipImportError: can't decompress data

    在CentOS以及其他的Linux系统中遇到安装包安装错误的原因,大多数都是因为缺少依赖包导致的,所以对于错误:zipimport.ZipImportError: can’t decompress d ...

  5. [LeetCode] 561. Array Partition I_Easy tag: Sort

    Given an array of 2n integers, your task is to group these integers into n pairs of integer, say (a1 ...

  6. 机器学习理论基础学习10--- 高斯混合模型GMM

    一.什么是高斯混合模型? 高斯混合模型(Gaussian Mixed Model)指的是多个高斯分布函数的线性组合,理论上GMM可以拟合出任意类型的分布,通常用于解决同一集合下的数据包含多个不同的分布 ...

  7. Vue项目图片剪切上传——vue-cropper的使用

    最近自己在研究vue,然后做了一个小型的后台管理系统用来练手,开发过程中,想到了剪切图片上传用户头像的需求.上网百度了一番,发现好多用的都是vue-cropper.我也就用了,个人感觉还是挺好用的.现 ...

  8. Javascript-蔬菜运算价格

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  9. MFC CFile类读写文件详解

    CFile类提供了对文件进行打开,关闭,读,写,删除,重命名以及获取文件信息等文件操作的基本功能,足以处理任意类型的文件操作. 一个读写文件的例子: 文件I/O 虽然使用CArchive类内建的序列化 ...

  10. linux常用命令:chmod 命令

    chmod命令用于改变linux系统文件或目录的访问权限.用它控制文件或目录的访问权限.该命令有两种用法.一种是包含字母和操作符表达式的文字设定法:另一种是包含数字的数字设定法. Linux系统中的每 ...