mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-17 16:56:40 +08:00
1839 lines
35 KiB
HTML
1839 lines
35 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>28 如何使用设计模式优化并发编程?.md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
|
||
|
||
|
||
<body>
|
||
|
||
|
||
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/00 开篇词你为什么需要学习并发编程?.md.html">00 开篇词你为什么需要学习并发编程?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/01 如何制定性能调优标准?.md.html">01 如何制定性能调优标准?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/02 如何制定性能调优策略?.md.html">02 如何制定性能调优策略?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/03 字符串性能优化不容小觑,百M内存轻松存储几十G数据.md.html">03 字符串性能优化不容小觑,百M内存轻松存储几十G数据.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/04 慎重使用正则表达式.md.html">04 慎重使用正则表达式.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/05 ArrayList还是LinkedList?使用不当性能差千倍.md.html">05 ArrayList还是LinkedList?使用不当性能差千倍.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/06 Stream如何提高遍历集合效率?.md.html">06 Stream如何提高遍历集合效率?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/07 深入浅出HashMap的设计与优化.md.html">07 深入浅出HashMap的设计与优化.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/08 网络通信优化之IO模型:如何解决高并发下IO瓶颈?.md.html">08 网络通信优化之IO模型:如何解决高并发下IO瓶颈?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/09 网络通信优化之序列化:避免使用Java序列化.md.html">09 网络通信优化之序列化:避免使用Java序列化.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/10 网络通信优化之通信协议:如何优化RPC网络通信?.md.html">10 网络通信优化之通信协议:如何优化RPC网络通信?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/11 答疑课堂:深入了解NIO的优化实现原理.md.html">11 答疑课堂:深入了解NIO的优化实现原理.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/12 多线程之锁优化(上):深入了解Synchronized同步锁的优化方法.md.html">12 多线程之锁优化(上):深入了解Synchronized同步锁的优化方法.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/13 多线程之锁优化(中):深入了解Lock同步锁的优化方法.md.html">13 多线程之锁优化(中):深入了解Lock同步锁的优化方法.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/14 多线程之锁优化(下):使用乐观锁优化并行操作.md.html">14 多线程之锁优化(下):使用乐观锁优化并行操作.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/15 多线程调优(上):哪些操作导致了上下文切换?.md.html">15 多线程调优(上):哪些操作导致了上下文切换?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/16 多线程调优(下):如何优化多线程上下文切换?.md.html">16 多线程调优(下):如何优化多线程上下文切换?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/17 并发容器的使用:识别不同场景下最优容器.md.html">17 并发容器的使用:识别不同场景下最优容器.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/18 如何设置线程池大小?.md.html">18 如何设置线程池大小?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/19 如何用协程来优化多线程业务?.md.html">19 如何用协程来优化多线程业务?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/20 磨刀不误砍柴工:欲知JVM调优先了解JVM内存模型.md.html">20 磨刀不误砍柴工:欲知JVM调优先了解JVM内存模型.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/21 深入JVM即时编译器JIT,优化Java编译.md.html">21 深入JVM即时编译器JIT,优化Java编译.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/22 如何优化垃圾回收机制?.md.html">22 如何优化垃圾回收机制?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/23 如何优化JVM内存分配?.md.html">23 如何优化JVM内存分配?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/24 内存持续上升,我该如何排查问题?.md.html">24 内存持续上升,我该如何排查问题?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/25 答疑课堂:模块四热点问题解答.md.html">25 答疑课堂:模块四热点问题解答.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/26 单例模式:如何创建单一对象优化系统性能?.md.html">26 单例模式:如何创建单一对象优化系统性能?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/27 原型模式与享元模式:提升系统性能的利器.md.html">27 原型模式与享元模式:提升系统性能的利器.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
<a class="current-tab" href="/专栏/Java并发编程实战/28 如何使用设计模式优化并发编程?.md.html">28 如何使用设计模式优化并发编程?.md.html</a>
|
||
|
||
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/29 生产者消费者模式:电商库存设计优化.md.html">29 生产者消费者模式:电商库存设计优化.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/30 装饰器模式:如何优化电商系统中复杂的商品价格策略?.md.html">30 装饰器模式:如何优化电商系统中复杂的商品价格策略?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/31 答疑课堂:模块五思考题集锦.md.html">31 答疑课堂:模块五思考题集锦.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/32 MySQL调优之SQL语句:如何写出高性能SQL语句?.md.html">32 MySQL调优之SQL语句:如何写出高性能SQL语句?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/33 MySQL调优之事务:高并发场景下的数据库事务调优.md.html">33 MySQL调优之事务:高并发场景下的数据库事务调优.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/34 MySQL调优之索引:索引的失效与优化.md.html">34 MySQL调优之索引:索引的失效与优化.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/35 记一次线上SQL死锁事故:如何避免死锁?.md.html">35 记一次线上SQL死锁事故:如何避免死锁?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/36 什么时候需要分表分库?.md.html">36 什么时候需要分表分库?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/37 电商系统表设计优化案例分析.md.html">37 电商系统表设计优化案例分析.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/38 数据库参数设置优化,失之毫厘差之千里.md.html">38 数据库参数设置优化,失之毫厘差之千里.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/39 答疑课堂:MySQL中InnoDB的知识点串讲.md.html">39 答疑课堂:MySQL中InnoDB的知识点串讲.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/41 如何设计更优的分布式锁?.md.html">41 如何设计更优的分布式锁?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/42 电商系统的分布式事务调优.md.html">42 电商系统的分布式事务调优.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/43 如何使用缓存优化系统性能?.md.html">43 如何使用缓存优化系统性能?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/44 记一次双十一抢购性能瓶颈调优.md.html">44 记一次双十一抢购性能瓶颈调优.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/加餐 什么是数据的强、弱一致性?.md.html">加餐 什么是数据的强、弱一致性?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/加餐 推荐几款常用的性能测试工具.md.html">加餐 推荐几款常用的性能测试工具.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/答疑课堂:模块三热点问题解答.md.html">答疑课堂:模块三热点问题解答.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/Java并发编程实战/结束语 栉风沐雨,砥砺前行!.md.html">结束语 栉风沐雨,砥砺前行!.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
|
||
<div class="sidebar-toggle-inner"></div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<script>
|
||
|
||
function add_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.add('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function remove_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function sidebar_toggle() {
|
||
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let content = document.querySelector('.off-canvas-content')
|
||
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
|
||
sidebar_toggle.classList.remove('extend')
|
||
|
||
sidebar.classList.remove('hide')
|
||
|
||
content.classList.remove('extend')
|
||
|
||
} else { // hide
|
||
|
||
sidebar_toggle.classList.add('extend')
|
||
|
||
sidebar.classList.add('hide')
|
||
|
||
content.classList.add('extend')
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
function open_sidebar() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.add('show')
|
||
|
||
overlay.classList.add('show')
|
||
|
||
}
|
||
|
||
function hide_canvas() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.remove('show')
|
||
|
||
overlay.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
<div class="off-canvas-content">
|
||
|
||
<div class="columns">
|
||
|
||
<div class="column col-12 col-lg-12">
|
||
|
||
<div class="book-navbar">
|
||
|
||
<!-- For Responsive Layout -->
|
||
|
||
<header class="navbar">
|
||
|
||
<section class="navbar-section">
|
||
|
||
<a onclick="open_sidebar()">
|
||
|
||
<i class="icon icon-menu"></i>
|
||
|
||
</a>
|
||
|
||
</section>
|
||
|
||
</header>
|
||
|
||
</div>
|
||
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
|
||
overflow-x: auto;
|
||
|
||
overflow-y: hidden;">
|
||
|
||
<div class="book-post">
|
||
|
||
<p id="tip" align="center"></p>
|
||
|
||
<div><h1>28 如何使用设计模式优化并发编程?</h1>
|
||
|
||
<p>你好,我是刘超。</p>
|
||
|
||
<p>在我们使用多线程编程时,很多时候需要根据业务场景设计一套业务功能。其实,在多线程编程中,本身就存在很多成熟的功能设计模式,学好它们,用好它们,那就是如虎添翼了。今天我就带你了解几种并发编程中常用的设计模式。</p>
|
||
|
||
<h2>线程上下文设计模式</h2>
|
||
|
||
<p>线程上下文是指贯穿线程整个生命周期的对象中的一些全局信息。例如,我们比较熟悉的 Spring 中的 ApplicationContext 就是一个关于上下文的类,它在整个系统的生命周期中保存了配置信息、用户信息以及注册的 bean 等上下文信息。</p>
|
||
|
||
<p>这样的解释可能有点抽象,我们不妨通过一个具体的案例,来看看到底在什么的场景下才需要上下文呢?</p>
|
||
|
||
<p>在执行一个比较长的请求任务时,这个请求可能会经历很多层的方法调用,假设我们需要将最开始的方法的中间结果传递到末尾的方法中进行计算,一个简单的实现方式就是在每个函数中新增这个中间结果的参数,依次传递下去。代码如下:</p>
|
||
|
||
<pre><code>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());
|
||
|
||
}
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>执行结果:</p>
|
||
|
||
<pre><code>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
|
||
|
||
</code></pre>
|
||
|
||
<p>然而这种方式太笨拙了,每次调用方法时,都需要传入 Context 作为参数,而且影响一些中间公共方法的封装。</p>
|
||
|
||
<p>那能不能设置一个全局变量呢?如果是在多线程情况下,需要考虑线程安全,这样的话就又涉及到了锁竞争。</p>
|
||
|
||
<p>除了以上这些方法,其实我们还可以使用 ThreadLocal 实现上下文。ThreadLocal 是线程本地变量,可以实现多线程的数据隔离。ThreadLocal 为每一个使用该变量的线程都提供一份独立的副本,线程间的数据是隔离的,每一个线程只能访问各自内部的副本变量。</p>
|
||
|
||
<p>ThreadLocal 中有三个常用的方法:set、get、initialValue,我们可以通过以下一个简单的例子来看看 ThreadLocal 的使用:</p>
|
||
|
||
<pre><code>private void testThreadLocal() {
|
||
|
||
Thread t = new Thread() {
|
||
|
||
ThreadLocal<String> mStringThreadLocal = new ThreadLocal<String>();
|
||
|
||
|
||
|
||
@Override
|
||
|
||
public void run() {
|
||
|
||
super.run();
|
||
|
||
mStringThreadLocal.set("test");
|
||
|
||
mStringThreadLocal.get();
|
||
|
||
}
|
||
|
||
};
|
||
|
||
|
||
|
||
t.start();
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>接下来,我们使用 ThreadLocal 来重新实现最开始的上下文设计。你会发现,我们在两个方法中并没有通过变量来传递上下文,只是通过 ThreadLocal 获取了当前线程的上下文信息:</p>
|
||
|
||
<pre><code>public class ContextTest {
|
||
|
||
// 上下文类
|
||
|
||
public static 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;
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
// 复制上下文到 ThreadLocal 中
|
||
|
||
public final static class ActionContext {
|
||
|
||
|
||
|
||
private static final ThreadLocal<Context> threadLocal = new ThreadLocal<Context>() {
|
||
|
||
@Override
|
||
|
||
protected Context initialValue() {
|
||
|
||
return new Context();
|
||
|
||
}
|
||
|
||
};
|
||
|
||
|
||
|
||
public static ActionContext getActionContext() {
|
||
|
||
return ContextHolder.actionContext;
|
||
|
||
}
|
||
|
||
|
||
|
||
public Context getContext() {
|
||
|
||
return threadLocal.get();
|
||
|
||
}
|
||
|
||
|
||
|
||
// 获取 ActionContext 单例
|
||
|
||
public static class ContextHolder {
|
||
|
||
private final static ActionContext actionContext = new ActionContext();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
// 设置上下文名字
|
||
|
||
public class QueryNameAction {
|
||
|
||
public void execute() {
|
||
|
||
try {
|
||
|
||
Thread.sleep(1000L);
|
||
|
||
String name = Thread.currentThread().getName();
|
||
|
||
ActionContext.getActionContext().getContext().setName(name);
|
||
|
||
} catch (InterruptedException e) {
|
||
|
||
e.printStackTrace();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
// 设置上下文 ID
|
||
|
||
public class QueryIdAction {
|
||
|
||
public void execute() {
|
||
|
||
try {
|
||
|
||
Thread.sleep(1000L);
|
||
|
||
long id = Thread.currentThread().getId();
|
||
|
||
ActionContext.getActionContext().getContext().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() {
|
||
|
||
queryNameAction.execute();// 设置线程名
|
||
|
||
System.out.println("The name query successful");
|
||
|
||
queryIdAction.execute();// 设置线程 ID
|
||
|
||
System.out.println("The id query successful");
|
||
|
||
|
||
|
||
System.out.println("The Name is " + ActionContext.getActionContext().getContext().getName() + " and id " + ActionContext.getActionContext().getContext().getId())
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
public static void main(String[] args) {
|
||
|
||
IntStream.range(1, 5).forEach(i -> new Thread(new ContextTest().new ExecutionTask()).start());
|
||
|
||
}
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>运行结果:</p>
|
||
|
||
<pre><code>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-2 and id 12
|
||
|
||
The Name is Thread-0 and id 10
|
||
|
||
The Name is Thread-1 and id 11
|
||
|
||
The Name is Thread-3 and id 13
|
||
|
||
</code></pre>
|
||
|
||
<h2>Thread-Per-Message 设计模式</h2>
|
||
|
||
<p>Thread-Per-Message 设计模式翻译过来的意思就是每个消息一个线程的意思。例如,我们在处理 Socket 通信的时候,通常是一个线程处理事件监听以及 I/O 读写,如果 I/O 读写操作非常耗时,这个时候便会影响到事件监听处理事件。</p>
|
||
|
||
<p>这个时候 Thread-Per-Message 模式就可以很好地解决这个问题,一个线程监听 I/O 事件,每当监听到一个 I/O 事件,则交给另一个处理线程执行 I/O 操作。下面,我们还是通过一个例子来学习下该设计模式的实现。</p>
|
||
|
||
<pre><code>//IO 处理
|
||
|
||
public class ServerHandler implements Runnable{
|
||
|
||
private Socket socket;
|
||
|
||
|
||
|
||
public ServerHandler(Socket socket) {
|
||
|
||
this.socket = socket;
|
||
|
||
}
|
||
|
||
|
||
|
||
public void run() {
|
||
|
||
BufferedReader in = null;
|
||
|
||
PrintWriter out = null;
|
||
|
||
String msg = null;
|
||
|
||
try {
|
||
|
||
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||
|
||
out = new PrintWriter(socket.getOutputStream(),true);
|
||
|
||
while ((msg = in.readLine()) != null && msg.length()!=0) {// 当连接成功后在此等待接收消息(挂起,进入阻塞状态)
|
||
|
||
System.out.println("server received : " + msg);
|
||
|
||
out.print("received~\n");
|
||
|
||
out.flush();
|
||
|
||
}
|
||
|
||
} catch (Exception e) {
|
||
|
||
e.printStackTrace();
|
||
|
||
} finally {
|
||
|
||
try {
|
||
|
||
in.close();
|
||
|
||
} catch (IOException e) {
|
||
|
||
e.printStackTrace();
|
||
|
||
}
|
||
|
||
try {
|
||
|
||
out.close();
|
||
|
||
} catch (Exception e) {
|
||
|
||
e.printStackTrace();
|
||
|
||
}
|
||
|
||
try {
|
||
|
||
socket.close();
|
||
|
||
} catch (IOException e) {
|
||
|
||
e.printStackTrace();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
//Socket 启动服务
|
||
|
||
public class Server {
|
||
|
||
|
||
|
||
private static int DEFAULT_PORT = 12345;
|
||
|
||
private static ServerSocket server;
|
||
|
||
|
||
|
||
public static void start() throws IOException {
|
||
|
||
start(DEFAULT_PORT);
|
||
|
||
}
|
||
|
||
|
||
|
||
public static void start(int port) throws IOException {
|
||
|
||
if (server != null) {
|
||
|
||
return;
|
||
|
||
}
|
||
|
||
|
||
|
||
try {
|
||
|
||
// 启动服务
|
||
|
||
server = new ServerSocket(port);
|
||
|
||
// 通过无线循环监听客户端连接
|
||
|
||
while (true) {
|
||
|
||
|
||
|
||
Socket socket = server.accept();
|
||
|
||
// 当有新的客户端接入时,会执行下面的代码
|
||
|
||
long start = System.currentTimeMillis();
|
||
|
||
new Thread(new ServerHandler(socket)).start();
|
||
|
||
|
||
|
||
long end = System.currentTimeMillis();
|
||
|
||
|
||
|
||
System.out.println("Spend time is " + (end - start));
|
||
|
||
}
|
||
|
||
} finally {
|
||
|
||
if (server != null) {
|
||
|
||
System.out.println(" 服务器已关闭。");
|
||
|
||
server.close();
|
||
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
public static void main(String[] args) throws InterruptedException{
|
||
|
||
|
||
|
||
// 运行服务端
|
||
|
||
new Thread(new Runnable() {
|
||
|
||
public void run() {
|
||
|
||
try {
|
||
|
||
Server.start();
|
||
|
||
} catch (IOException e) {
|
||
|
||
e.printStackTrace();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
}).start();
|
||
|
||
|
||
|
||
}
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>以上,我们是完成了一个使用 Thread-Per-Message 设计模式实现的 Socket 服务端的代码。但这里是有一个问题的,你发现了吗?</p>
|
||
|
||
<p>使用这种设计模式,如果遇到大的高并发,就会出现严重的性能问题。如果针对每个 I/O 请求都创建一个线程来处理,在有大量请求同时进来时,就会创建大量线程,而此时 JVM 有可能会因为无法处理这么多线程,而出现内存溢出的问题。</p>
|
||
|
||
<p>退一步讲,即使是不会有大量线程的场景,每次请求过来也都需要创建和销毁线程,这对系统来说,也是一笔不小的性能开销。</p>
|
||
|
||
<p>面对这种情况,我们可以使用线程池来代替线程的创建和销毁,这样就可以避免创建大量线程而带来的性能问题,是一种很好的调优方法。</p>
|
||
|
||
<h2>Worker-Thread 设计模式</h2>
|
||
|
||
<p>这里的 Worker 是工人的意思,代表在 Worker Thread 设计模式中,会有一些工人(线程)不断轮流处理过来的工作,当没有工作时,工人则会处于等待状态,直到有新的工作进来。除了工人角色,Worker Thread 设计模式中还包括了流水线和产品。</p>
|
||
|
||
<p>这种设计模式相比 Thread-Per-Message 设计模式,可以减少频繁创建、销毁线程所带来的性能开销,还有无限制地创建线程所带来的内存溢出风险。</p>
|
||
|
||
<p>我们可以假设一个场景来看下该模式的实现,通过 Worker Thread 设计模式来完成一个物流分拣的作业。</p>
|
||
|
||
<p>假设一个物流仓库的物流分拣流水线上有 8 个机器人,它们不断从流水线上获取包裹并对其进行包装,送其上车。当仓库中的商品被打包好后,会投放到物流分拣流水线上,而不是直接交给机器人,机器人会再从流水线中随机分拣包裹。代码如下:</p>
|
||
|
||
<pre><code>// 包裹类
|
||
|
||
public class Package {
|
||
|
||
private String name;
|
||
|
||
private String address;
|
||
|
||
|
||
|
||
public String getName() {
|
||
|
||
return name;
|
||
|
||
}
|
||
|
||
|
||
|
||
public void setName(String name) {
|
||
|
||
this.name = name;
|
||
|
||
}
|
||
|
||
|
||
|
||
public String getAddress() {
|
||
|
||
return address;
|
||
|
||
}
|
||
|
||
|
||
|
||
public void setAddress(String address) {
|
||
|
||
this.address = address;
|
||
|
||
}
|
||
|
||
|
||
|
||
public void execute() {
|
||
|
||
System.out.println(Thread.currentThread().getName()+" executed "+this);
|
||
|
||
}
|
||
|
||
}
|
||
|
||
// 流水线
|
||
|
||
public class PackageChannel {
|
||
|
||
private final static int MAX_PACKAGE_NUM = 100;
|
||
|
||
|
||
|
||
private final Package[] packageQueue;
|
||
|
||
private final Worker[] workerPool;
|
||
|
||
private int head;
|
||
|
||
private int tail;
|
||
|
||
private int count;
|
||
|
||
|
||
|
||
public PackageChannel(int workers) {
|
||
|
||
this.packageQueue = new Package[MAX_PACKAGE_NUM];
|
||
|
||
this.head = 0;
|
||
|
||
this.tail = 0;
|
||
|
||
this.count = 0;
|
||
|
||
this.workerPool = new Worker[workers];
|
||
|
||
this.init();
|
||
|
||
}
|
||
|
||
|
||
|
||
private void init() {
|
||
|
||
for (int i = 0; i < workerPool.length; i++) {
|
||
|
||
workerPool[i] = new Worker("Worker-" + i, this);
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
|
||
* push switch to start all of worker to work
|
||
|
||
*/
|
||
|
||
public void startWorker() {
|
||
|
||
Arrays.asList(workerPool).forEach(Worker::start);
|
||
|
||
}
|
||
|
||
|
||
|
||
public synchronized void put(Package packagereq) {
|
||
|
||
while (count >= packageQueue.length) {
|
||
|
||
try {
|
||
|
||
this.wait();
|
||
|
||
} catch (InterruptedException e) {
|
||
|
||
e.printStackTrace();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
this.packageQueue[tail] = packagereq;
|
||
|
||
this.tail = (tail + 1) % packageQueue.length;
|
||
|
||
this.count++;
|
||
|
||
this.notifyAll();
|
||
|
||
}
|
||
|
||
|
||
|
||
public synchronized Package take() {
|
||
|
||
while (count <= 0) {
|
||
|
||
try {
|
||
|
||
this.wait();
|
||
|
||
} catch (InterruptedException e) {
|
||
|
||
e.printStackTrace();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
Package request = this.packageQueue[head];
|
||
|
||
this.head = (this.head + 1) % this.packageQueue.length;
|
||
|
||
this.count--;
|
||
|
||
this.notifyAll();
|
||
|
||
return request;
|
||
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
// 机器人
|
||
|
||
public class Worker extends Thread{
|
||
|
||
private static final Random random = new Random(System.currentTimeMillis());
|
||
|
||
private final PackageChannel channel;
|
||
|
||
|
||
|
||
public Worker(String name, PackageChannel channel) {
|
||
|
||
super(name);
|
||
|
||
this.channel = channel;
|
||
|
||
}
|
||
|
||
|
||
|
||
@Override
|
||
|
||
public void run() {
|
||
|
||
while (true) {
|
||
|
||
channel.take().execute();
|
||
|
||
|
||
|
||
try {
|
||
|
||
Thread.sleep(random.nextInt(1000));
|
||
|
||
} catch (InterruptedException e) {
|
||
|
||
e.printStackTrace();
|
||
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
public class Test {
|
||
|
||
public static void main(String[] args) {
|
||
|
||
// 新建 8 个工人
|
||
|
||
final PackageChannel channel = new PackageChannel(8);
|
||
|
||
// 开始工作
|
||
|
||
channel.startWorker();
|
||
|
||
// 为流水线添加包裹
|
||
|
||
for(int i=0; i<100; i++) {
|
||
|
||
Package packagereq = new Package();
|
||
|
||
packagereq.setAddress("test");
|
||
|
||
packagereq.setName("test");
|
||
|
||
channel.put(packagereq);
|
||
|
||
}
|
||
|
||
}
|
||
|
||
}
|
||
|
||
</code></pre>
|
||
|
||
<p>我们可以看到,这里有 8 个工人在不断地分拣仓库中已经包装好的商品。</p>
|
||
|
||
<h2>总结</h2>
|
||
|
||
<p>平时,如果需要传递或隔离一些线程变量时,我们可以考虑使用上下文设计模式。在数据库读写分离的业务场景中,则经常会用到 ThreadLocal 实现动态切换数据源操作。但在使用 ThreadLocal 时,我们需要注意内存泄漏问题,在之前的[第 25 讲]中,我们已经讨论过这个问题了。</p>
|
||
|
||
<p>当主线程处理每次请求都非常耗时时,就可能出现阻塞问题,这时候我们可以考虑将主线程业务分工到新的业务线程中,从而提高系统的并行处理能力。而 Thread-Per-Message 设计模式以及 Worker-Thread 设计模式则都是通过多线程分工来提高系统并行处理能力的设计模式。</p>
|
||
|
||
<h2>思考题</h2>
|
||
|
||
<p>除了以上这些多线程的设计模式,平时你还使用过其它的设计模式来优化多线程业务吗?</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/Java并发编程实战/27 原型模式与享元模式:提升系统性能的利器.md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/Java并发编程实战/29 生产者消费者模式:电商库存设计优化.md.html">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"7099719a6ce33d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
|
||
</body>
|
||
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
|
||
<script>
|
||
|
||
window.dataLayer = window.dataLayer || [];
|
||
|
||
|
||
|
||
function gtag() {
|
||
|
||
dataLayer.push(arguments);
|
||
|
||
}
|
||
|
||
|
||
|
||
gtag('js', new Date());
|
||
|
||
gtag('config', 'G-NPSEEVD756');
|
||
|
||
var path = window.location.pathname
|
||
|
||
var cookie = getCookie("lastPath");
|
||
|
||
console.log(path)
|
||
|
||
if (path.replace("/", "") === "") {
|
||
|
||
if (cookie.replace("/", "") !== "") {
|
||
|
||
console.log(cookie)
|
||
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
setCookie("lastPath", path)
|
||
|
||
}
|
||
|
||
|
||
|
||
function setCookie(cname, cvalue) {
|
||
|
||
var d = new Date();
|
||
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
|
||
var expires = "expires=" + d.toGMTString();
|
||
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
|
||
}
|
||
|
||
|
||
|
||
function getCookie(cname) {
|
||
|
||
var name = cname + "=";
|
||
|
||
var ca = document.cookie.split(';');
|
||
|
||
for (var i = 0; i < ca.length; i++) {
|
||
|
||
var c = ca[i].trim();
|
||
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
|
||
}
|
||
|
||
return "";
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
</html>
|
||
|