Java性能调优实战
刘超
金山软件西山居技术经理
立即订阅
7535 人已学习
课程目录
已完结 48 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 怎样才能做好性能调优?
免费
模块一 · 概述 (2讲)
01 | 如何制定性能调优标准?
02 | 如何制定性能调优策略?
模块二 · Java编程性能调优 (10讲)
03 | 字符串性能优化不容小觑,百M内存轻松存储几十G数据
04 | 慎重使用正则表达式
05 | ArrayList还是LinkedList?使用不当性能差千倍
加餐 | 推荐几款常用的性能测试工具
06 | Stream如何提高遍历集合效率?
07 | 深入浅出HashMap的设计与优化
08 | 网络通信优化之I/O模型:如何解决高并发下I/O瓶颈?
09 | 网络通信优化之序列化:避免使用Java序列化
10 | 网络通信优化之通信协议:如何优化RPC网络通信?
11 | 答疑课堂:深入了解NIO的优化实现原理
模块三 · 多线程性能调优 (10讲)
12 | 多线程之锁优化(上):深入了解Synchronized同步锁的优化方法
13 | 多线程之锁优化(中):深入了解Lock同步锁的优化方法
14 | 多线程之锁优化(下):使用乐观锁优化并行操作
15 | 多线程调优(上):哪些操作导致了上下文切换?
16 | 多线程调优(下):如何优化多线程上下文切换?
17 | 并发容器的使用:识别不同场景下最优容器
18 | 如何设置线程池大小?
19 | 如何用协程来优化多线程业务?
20 | 答疑课堂:模块三热点问题解答
加餐 | 什么是数据的强、弱一致性?
模块四 · JVM性能监测及调优 (6讲)
21 | 磨刀不误砍柴工:欲知JVM调优先了解JVM内存模型
22 | 深入JVM即时编译器JIT,优化Java编译
23 | 如何优化垃圾回收机制?
24 | 如何优化JVM内存分配?
25 | 内存持续上升,我该如何排查问题?
26 | 答疑课堂:模块四热点问题解答
模块五 · 设计模式调优 (6讲)
27 | 单例模式:如何创建单一对象优化系统性能?
28 | 原型模式与享元模式:提升系统性能的利器
29 | 如何使用设计模式优化并发编程?
30 | 生产者消费者模式:电商库存设计优化
31 | 装饰器模式:如何优化电商系统中复杂的商品价格策略?
32 | 答疑课堂:模块五思考题集锦
模块六 · 数据库性能调优 (8讲)
33 | MySQL调优之SQL语句:如何写出高性能SQL语句?
34 | MySQL调优之事务:高并发场景下的数据库事务调优
35 | MySQL调优之索引:索引的失效与优化
36 | 记一次线上SQL死锁事故:如何避免死锁?
37 | 什么时候需要分表分库?
38 | 电商系统表设计优化案例分析
39 | 数据库参数设置优化,失之毫厘差之千里
40 | 答疑课堂:MySQL中InnoDB的知识点串讲
模块七 · 实战演练场 (4讲)
41 | 如何设计更优的分布式锁?
42 | 电商系统的分布式事务调优
43 | 如何使用缓存优化系统性能?
44 | 记一次双十一抢购性能瓶颈调优
结束语 (1讲)
结束语 | 栉风沐雨,砥砺前行!
Java性能调优实战
登录|注册

29 | 如何使用设计模式优化并发编程?

刘超 2019-07-27
你好,我是刘超。
在我们使用多线程编程时,很多时候需要根据业务场景设计一套业务功能。其实,在多线程编程中,本身就存在很多成熟的功能设计模式,学好它们,用好它们,那就是如虎添翼了。今天我就带你了解几种并发编程中常用的设计模式。

线程上下文设计模式

线程上下文是指贯穿线程整个生命周期的对象中的一些全局信息。例如,我们比较熟悉的 Spring 中的 ApplicationContext 就是一个关于上下文的类,它在整个系统的生命周期中保存了配置信息、用户信息以及注册的 bean 等上下文信息。
这样的解释可能有点抽象,我们不妨通过一个具体的案例,来看看到底在什么的场景下才需要上下文呢?
在执行一个比较长的请求任务时,这个请求可能会经历很多层的方法调用,假设我们需要将最开始的方法的中间结果传递到末尾的方法中进行计算,一个简单的实现方式就是在每个函数中新增这个中间结果的参数,依次传递下去。代码如下:
public class ContextTest {
// 上下文类
public class Context {
private String name;
private long id
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
// 设置上下文名字
public class QueryNameAction {
public void execute(Context context) {
try {
Thread.sleep(1000L);
String name = Thread.currentThread().getName();
context.setName(name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 设置上下文ID
public class QueryIdAction {
public void execute(Context context) {
try {
Thread.sleep(1000L);
long id = Thread.currentThread().getId();
context.setId(id);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 执行方法
public class ExecutionTask implements Runnable {
private QueryNameAction queryNameAction = new QueryNameAction();
private QueryIdAction queryIdAction = new QueryIdAction();
@Override
public void run() {
final Context context = new Context();
queryNameAction.execute(context);
System.out.println("The name query successful");
queryIdAction.execute(context);
System.out.println("The id query successful");
System.out.println("The Name is " + context.getName() + " and id " + context.getId());
}
}
public static void main(String[] args) {
IntStream.range(1, 5).forEach(i -> new Thread(new ContextTest().new ExecutionTask()).start());
}
}
执行结果:
The name query successful
The name query successful
The name query successful
The name query successful
The id query successful
The id query successful
The id query successful
The id query successful
The Name is Thread-1 and id 11
The Name is Thread-2 and id 12
The Name is Thread-3 and id 13
The Name is Thread-0 and id 10
然而这种方式太笨拙了,每次调用方法时,都需要传入 Context 作为参数,而且影响一些中间公共方法的封装。
那能不能设置一个全局变量呢?如果是在多线程情况下,需要考虑线程安全,这样的话就又涉及到了锁竞争。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java性能调优实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(16)

  • 讲真,我是觉得设计模式是优化代码组织结构的,性能提升只是因为你的实现途径导致了你适合用某种设计模式,so感觉这样标题怪怪的。

    如果要这么说的话,mapreduce或者说javase引入的forkjoin,流水线模式,cow就都是了。

    作者回复: 是的,有些设计模式更多的是优化代码逻辑结构,但还是有很多设计模式也起到了优化性能效果。例如,文中的Worker-Thread 设计模式其实就是一种线程池的优化方式,在并发量大时,一般的代码逻辑要么是串行执行,要么使用创建线程并发执行,在大量并发时两者都可能会出现性能瓶颈,而这种Worker-Thread 这样的设计方式则即可以并发执行,又避免创建过多的线程导致性能瓶颈。

    2019-07-27
    6
  • Jxin
    1.最常用的多线程设计模式应该是生产者消费者模式,在分布式系统,该模式现在一般也由Mq来承接。(以rocketMq为例)好处有:消峰,解耦,消息堆积,多broker并行消费,单broker串行(顺序)消费,发布订阅,分组消费,失败重试,死信管理等等。
    2.其他的业务不常用,比如lmmutability(不变模式,Long类的内部静态类对象池),还有个实时赋值COW,指得一提的应该还有个Actor,但java不支持,要玩又得引第三方包,所以java生产也不会用。
    3.forkjoin并行处理也是使用的多线程执行子任务,但这个应该算不上多线程设计模式,感觉说是多线程应用更好,其中的任务窃取挺有意思。

    作者回复: 很赞

    2019-07-29
    3
  • 张德
    老师好 我还用过reactor模式 这个多线程的thread-per-message感觉和reactor模式有点像 又有一些区别 但我就是总结不出之间的区别 老师能不能点化一下 多谢

    作者回复: 我们在之前也讲过reactor模式,reactor模式是基于事件驱动,并且有专门有一个监听线程池监听事件,一旦有事件进来,将会再通过handler线程池来处理具体的业务,实现更为复杂,性能要比thread-per-message更佳

    2019-07-30
    1
    2
  • aoe
    除了文中第一个设计模式,其他设计模式在《图解Java多线程设计模式》中都有更详细的介绍。感谢老师的分享,如果能在专栏的第一篇文章中推荐一些相关的书籍就更好了。
    2019-10-10
    1
  • 风轻扬
    //InputStreamReader读取原始的字节流,FileReader继承自InputStreamReader
                    br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    out = new PrintWriter(socket.getOutputStream(), true);
                    while ((msg = br.readLine()) != null && msg.length() != 0) {
                        System.out.println("server received:" + msg);
                        out.write("received~\n");
                        out.flush();
                    }
    老师,您在新建PrintWriter实例的时候,已经设置了自动刷新。为什么还要out.flush呢?这一句是多余的吧?

    作者回复: 是的,多余了

    2019-09-14
    1
  • -W.LI-
    老师啊!有没有好的书推荐,我觉得设计模式很好,源码应该是学习设计模式最好的老师,可是我的能力看源码感觉太早了。我就之前看过header first。感觉理解完全不够。好的设计模式和算法都能在系统性能有瓶颈的时候提升系统。我认识到它的重要了可是不得门,入不了难受啊。

    作者回复: 我记得大学时候读过一本《大话设计模式》的书籍不错,讲解的通俗易懂。

    2019-07-28
    2
    1
  • QQ怪
    比起Worker-Thread 设计模式类似工厂车间工人的工作模式,还有用的比较多的是生产者和消费者模式,与之前的不同的是,生产者和消费者模式核心是一个任务队列,生产者生产任务到任务队列中,消费者从队列消费任务,优点是解耦和平衡两者之前的速度差异。

    作者回复: 对的,分析的很到位。下一讲中我们会详细聊到生产者消费者模式。

    2019-07-27
    1
  • nightmare
    一个注册逻辑,下面有注册实现数组,注册实现里面有队列,并且本身实现runable ,注册门面依次从注册实现数组获取一个注册实现 并把请求放到注册实现的队列中,请求由一个注册实现来完成,请求由唯一的注册实现来完成,不会有并发问题 而且如果 注册实现有复杂业务 还可以加上 work thread模式来优化

    作者回复: 赞,现学现用

    2019-07-27
    1
  • 风轻扬
    老师,前几天看了一篇帖子,上面说System.currentMills在高并下下也会有性能问题。我看您直接用的,并没有做优化。您在实际工作中,有没有碰见过System.currentMills影响性能的例子呢?

    作者回复: 这个性能我猜是说的相对的,只是说System.currentTimeMillis() 性能损耗更大

    2019-09-14
  • godtrue
    我们的项目中有用上下文模式,为了记录业务中间的关键操作步骤,不过比较挫是传输参数的方式,下次可以改版为使用ThreadLocal形式的。
    看到有同学问异步的问题,我也有这样的疑问,我想异步调用为了更好的性能,但是我也想要调用后的结果。
    问题如下:
    1:这种异步是真异步嘛?
    2:这种异步最终还是必须阻塞等待获取响应结果的,性能提升有多大空间呢?看主线程继续处理某些事情的速度快,还是异步任务线程的执行速度快?如果异步执行的响应速度更快,那他需要等待主线程执行完再给他结果?还是会中断主线程将结果给他?
    3:最关键是异步的结果怎么拿到,这是什么原理?

    作者回复: 异步是有场景条件的,例如,异步调用用户不需要感知结果,或者用户的客户端与服务端是一个tcp长连接,处理之后由服务端推送处理结果给用户。

    2019-09-12
  • 晓杰
    请问老师,worker thread模式和线程池是不是一样的

    作者回复: 实现的基本原理是一样的

    2019-08-08
  • Liam
    老师好,文中提到,通过threadlocal动态切换数据源是什么意思?指的是用一个threadlocal的map管理多数据源的连接,每次都从map去拿不同datasource的连接吗?

    作者回复: 对的,例如读写分离,ThreadLocal存放了读从库数据源和写主库数据源,如果是查询操作,则切换为读数据源,如果是新增修改删除操作,则切换为写数据源。

    2019-07-29
  • -W.LI-
    老师好!Thread-Per-Message 设计模式就是分离阻塞和非阻塞的呗。阻塞的部分通过多路复用。非阻塞的的那部分就丢线程池并发处理。
    2019-07-28
  • 晓杰
    请问老师packageChannel中的成员变量count不会存在线程安全问题吗

    作者回复: 我们对put take方法已经加锁了,所以是线程安全的。

    2019-07-28
  • 陆离
    老师,讲到并发这里,我想问一个前面讲过的synchronized锁升级的几个问题。
    1.当锁由无锁状态升级到偏向锁时,除了将Mark Work中的线程ID替换是否还有其他操作?替换完线程ID就代表获取到了锁吗?
    2.当锁状态为偏向锁状态时, 其他线程竞争锁只是CAS替换线程ID吗?如果之前的线程还没有执行完呢?
    3.针对第2个问题,假设线程T1获取到了偏向锁,将线程ID设为T1。线程T2尝试获取偏向锁时,先检测锁的Mark Word线程ID是否为T2,如果不是,会CAS替换,这个时候的期望值为null,更新值为T2,失败后进入偏向锁撤销。stop-the-world后检测T1是否存活,如果否清空Mark work线程ID,锁恢复为无锁状态,唤醒T2,接着尝试获取锁。流程是这样的吗?
    4.当锁升级为轻量级锁时,获取锁的标志是锁指针指向线程的锁记录,当有其他线程尝试CAS获取锁时,期望值是无锁时,Mark word中为hash age 01这样的内容吗?
    5.当线程释放轻量锁时,需要将锁记录替换回Mark Word中,这种情况下锁还未释放为什么会有失败?
    6.当锁升级为重量锁后,开始使用monitor对象,为什么Mark Word中还会把内容替换为指向线程锁记录的指针?这个时候还需要使用Mark word吗?
    期待老师及同学的解答

    作者回复: 1、替换之后,“是否偏向锁”标志位设置为 1;
    2、如果获取锁失败,代表当前锁有一定的竞争,偏向锁将升级为轻量级锁;
    3、是的;
    4、标志位为00;
    5、如果是锁释放轻量级锁,直接将指向轻量级锁的指针置为null就可以了;
    6、这个是轻量级升级为重量级就会带过来的,暂时没有观察到具体的作用

    2019-07-27
    2
  • undifined
    老师,有一个问题没想明白,就是异步的请求处理中,每一个线程接收将请求交给处理的线程后,怎么拿到返回结果并返回给用户呢

    作者回复: 可以通过Future模式拿到返回结果,虽然是异步执行,如果要等待返回结果,则主线程还是在阻塞等待。

    2019-07-27
收起评论
16
返回
顶部