learn.lianglianglee.com/专栏/Java并发编程实战/31 答疑课堂:模块五思考题集锦.md.html
2022-08-14 03:40:33 +08:00

497 lines
30 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>31 答疑课堂:模块五思考题集锦.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 开篇词你为什么需要学习并发编程?</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/01 如何制定性能调优标准?.md.html">01 如何制定性能调优标准?</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/02 如何制定性能调优策略?.md.html">02 如何制定性能调优策略?</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/03 字符串性能优化不容小觑百M内存轻松存储几十G数据.md.html">03 字符串性能优化不容小觑百M内存轻松存储几十G数据</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/04 慎重使用正则表达式.md.html">04 慎重使用正则表达式</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/05 ArrayList还是LinkedList使用不当性能差千倍.md.html">05 ArrayList还是LinkedList使用不当性能差千倍</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/06 Stream如何提高遍历集合效率.md.html">06 Stream如何提高遍历集合效率</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/07 深入浅出HashMap的设计与优化.md.html">07 深入浅出HashMap的设计与优化</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/08 网络通信优化之IO模型如何解决高并发下IO瓶颈.md.html">08 网络通信优化之IO模型如何解决高并发下IO瓶颈</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/09 网络通信优化之序列化避免使用Java序列化.md.html">09 网络通信优化之序列化避免使用Java序列化</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/10 网络通信优化之通信协议如何优化RPC网络通信.md.html">10 网络通信优化之通信协议如何优化RPC网络通信</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/11 答疑课堂深入了解NIO的优化实现原理.md.html">11 答疑课堂深入了解NIO的优化实现原理</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/12 多线程之锁优化深入了解Synchronized同步锁的优化方法.md.html">12 多线程之锁优化深入了解Synchronized同步锁的优化方法</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/13 多线程之锁优化深入了解Lock同步锁的优化方法.md.html">13 多线程之锁优化深入了解Lock同步锁的优化方法</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/14 多线程之锁优化(下):使用乐观锁优化并行操作.md.html">14 多线程之锁优化(下):使用乐观锁优化并行操作</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/15 多线程调优(上):哪些操作导致了上下文切换?.md.html">15 多线程调优(上):哪些操作导致了上下文切换?</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/16 多线程调优(下):如何优化多线程上下文切换?.md.html">16 多线程调优(下):如何优化多线程上下文切换?</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/17 并发容器的使用:识别不同场景下最优容器.md.html">17 并发容器的使用:识别不同场景下最优容器</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/18 如何设置线程池大小?.md.html">18 如何设置线程池大小?</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/19 如何用协程来优化多线程业务?.md.html">19 如何用协程来优化多线程业务?</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/20 磨刀不误砍柴工欲知JVM调优先了解JVM内存模型.md.html">20 磨刀不误砍柴工欲知JVM调优先了解JVM内存模型</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/21 深入JVM即时编译器JIT优化Java编译.md.html">21 深入JVM即时编译器JIT优化Java编译</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/22 如何优化垃圾回收机制?.md.html">22 如何优化垃圾回收机制?</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/23 如何优化JVM内存分配.md.html">23 如何优化JVM内存分配</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/24 内存持续上升,我该如何排查问题?.md.html">24 内存持续上升,我该如何排查问题?</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/25 答疑课堂:模块四热点问题解答.md.html">25 答疑课堂:模块四热点问题解答</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/26 单例模式:如何创建单一对象优化系统性能?.md.html">26 单例模式:如何创建单一对象优化系统性能?</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/27 原型模式与享元模式:提升系统性能的利器.md.html">27 原型模式与享元模式:提升系统性能的利器</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/28 如何使用设计模式优化并发编程?.md.html">28 如何使用设计模式优化并发编程?</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/29 生产者消费者模式:电商库存设计优化.md.html">29 生产者消费者模式:电商库存设计优化</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/30 装饰器模式:如何优化电商系统中复杂的商品价格策略?.md.html">30 装饰器模式:如何优化电商系统中复杂的商品价格策略?</a>
</li>
<li>
<a class="current-tab" href="/专栏/Java并发编程实战/31 答疑课堂:模块五思考题集锦.md.html">31 答疑课堂:模块五思考题集锦</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/32 MySQL调优之SQL语句如何写出高性能SQL语句.md.html">32 MySQL调优之SQL语句如何写出高性能SQL语句</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/33 MySQL调优之事务高并发场景下的数据库事务调优.md.html">33 MySQL调优之事务高并发场景下的数据库事务调优</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/34 MySQL调优之索引索引的失效与优化.md.html">34 MySQL调优之索引索引的失效与优化</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/35 记一次线上SQL死锁事故如何避免死锁.md.html">35 记一次线上SQL死锁事故如何避免死锁</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/36 什么时候需要分表分库?.md.html">36 什么时候需要分表分库?</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/37 电商系统表设计优化案例分析.md.html">37 电商系统表设计优化案例分析</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/38 数据库参数设置优化,失之毫厘差之千里.md.html">38 数据库参数设置优化,失之毫厘差之千里</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/39 答疑课堂MySQL中InnoDB的知识点串讲.md.html">39 答疑课堂MySQL中InnoDB的知识点串讲</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/41 如何设计更优的分布式锁?.md.html">41 如何设计更优的分布式锁?</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/42 电商系统的分布式事务调优.md.html">42 电商系统的分布式事务调优</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/43 如何使用缓存优化系统性能?.md.html">43 如何使用缓存优化系统性能?</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/44 记一次双十一抢购性能瓶颈调优.md.html">44 记一次双十一抢购性能瓶颈调优</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/加餐 什么是数据的强、弱一致性?.md.html">加餐 什么是数据的强、弱一致性?</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/加餐 推荐几款常用的性能测试工具.md.html">加餐 推荐几款常用的性能测试工具</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/答疑课堂:模块三热点问题解答.md.html">答疑课堂:模块三热点问题解答</a>
</li>
<li>
<a href="/专栏/Java并发编程实战/结束语 栉风沐雨,砥砺前行!.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>31 答疑课堂:模块五思考题集锦</h1>
<p>你好,我是刘超。</p>
<p>模块五我们都在讨论设计模式,在我看来,设计模式不仅可以优化我们的代码结构,使代码可扩展性、可读性强,同时也起到了优化系统性能的作用,这是我设置这个模块的初衷。特别是在一些高并发场景中,线程协作相关的设计模式可以大大提高程序的运行性能。</p>
<p>那么截至本周,有关设计模式的内容就结束了,不知你有没有发现这个模块的思考题都比较发散,很多同学也在留言区中写出了很多硬核信息,促进了技术交流。这一讲的答疑课堂我就来为你总结下课后思考题,希望我的答案能让你有新的收获。</p>
<h2>[第 26 讲]</h2>
<p><strong>除了以上那些实现单例的方式,你还知道其它实现方式吗?</strong></p>
<p>在[第 9 讲]中,我曾提到过一个单例序列化问题,其答案就是使用枚举来实现单例,这样可以避免 Java 序列化破坏一个类的单例。</p>
<p>枚举生来就是单例枚举类的域field其实是相应的 enum 类型的一个实例对象,因为在 Java 中枚举是一种语法糖,所以在编译后,枚举类中的枚举域会被声明为 static 属性。</p>
<p>在[第 26 讲]中,我已经详细解释了 JVM 是如何保证 static 成员变量只被实例化一次的,我们不妨再来回顾下。使用了 static 修饰的成员变量,会在类初始化的过程中被收集进类构造器即 <clinit> 方法中在多线程场景下JVM 会保证只有一个线程能执行该类的 <clinit> 方法,其它线程将会被阻塞等待。等到唯一的一次 <clinit> 方法执行完成,其它线程将不会再执行 <clinit> 方法转而执行自己的代码。也就是说static 修饰了成员变量,在多线程的情况下能保证只实例化一次。</p>
<p>我们可以通过代码简单了解下使用枚举实现的饿汉单例模式:</p>
<pre><code>// 饿汉模式 枚举实现
public enum Singleton {
INSTANCE;// 不实例化
public List&lt;String&gt; list = null;// list 属性
private Singleton() {// 构造函数
list = new ArrayList&lt;String&gt;();
}
public static Singleton getInstance(){
return INSTANCE;// 返回已存在的对象
}
}
</code></pre>
<p>该方式实现的单例没有实现懒加载功能,那如果我们要使用到懒加载功能呢?此时,我们就可以基于内部类来实现:</p>
<pre><code>// 懒汉模式 枚举实现
public class Singleton {
INSTANCE;// 不实例化
public List&lt;String&gt; list = null;// list 属性
private Singleton(){// 构造函数
list = new ArrayList&lt;String&gt;();
}
// 使用枚举作为内部类
private enum EnumSingleton {
INSTANCE;// 不实例化
private Singleton instance = null;
private EnumSingleton(){// 构造函数
instance = new Singleton();
}
public static Singleton getSingleton(){
return instance;// 返回已存在的对象
}
}
public static Singleton getInstance(){
return EnumSingleton.INSTANCE.getSingleton();// 返回已存在的对象
}
}
</code></pre>
<h2>[第 27 讲]</h2>
<p><strong>上一讲的单例模式和这一讲的享元模式都是为了避免重复创建对象,你知道这两者的区别在哪儿吗?</strong></p>
<p>首先,这两种设计模式的实现方式是不同的。我们使用单例模式是避免每次调用一个类实例时,都要重复实例化该实例,目的是在类本身获取实例化对象的唯一性;而享元模式则是通过一个共享容器来实现一系列对象的共享。</p>
<p>其次,两者在使用场景上也是有区别的。单例模式更多的是强调减少实例化提升性能,因此它一般是使用在一些需要频繁创建和销毁实例化对象,或创建和销毁实例化对象非常消耗资源的类中。</p>
<p>例如,连接池和线程池中的连接就是使用单例模式实现的,数据库操作是非常频繁的,每次操作都需要创建和销毁连接,如果使用单例,可以节省不断新建和关闭数据库连接所引起的性能消耗。而享元模式更多的是强调共享相同对象或对象属性,以此节约内存使用空间。</p>
<p>除了区别,这两种设计模式也有共性,单例模式可以避免重复创建对象,节约内存空间,享元模式也可以避免一个类的重复实例化。总之,两者很相似,但侧重点不一样,假如碰到一些要在两种设计模式中做选择的场景,我们就可以根据侧重点来选择。</p>
<h2>[第 28 讲]</h2>
<p><strong>除了以上这些多线程的设计模式线程上下文设计模式、Thread-Per-Message 设计模式、Worker-Thread 设计模式),平时你还使用过其它的设计模式来优化多线程业务吗?</strong></p>
<p>在这一讲的留言区undifined 同学问到了,如果我们使用 Worker-Thread 设计模式worker 线程如果是异步请求处理,当我们监听到有请求进来之后,将任务交给工作线程,怎么拿到返回结果,并返回给主线程呢?</p>
<p>回答这个问题的过程中就会用到一些别的设计模式,可以一起看看。</p>
<p>如果要获取到异步线程的执行结果,我们可以使用 Future 设计模式来解决这个问题。假设我们有一个任务,需要一台机器执行,但是该任务需要一个工人分配给机器执行,当机器执行完成之后,需要通知工人任务的具体完成结果。这个时候我们就可以设计一个 Future 模式来实现这个业务。</p>
<p>首先,我们申明一个任务接口,主要提供给任务设计:</p>
<pre><code>public interface Task&lt;T, P&gt; {
T doTask(P param);// 完成任务
}
</code></pre>
<p>其次我们申明一个提交任务接口类TaskService 主要用于提交任务,提交任务可以分为需要返回结果和不需要返回结果两种:</p>
<pre><code>public interface TaskService&lt;T, P&gt; {
Future&lt;?&gt; submit(Runnable runnable);// 提交任务,不返回结果
Future&lt;?&gt; submit(Task&lt;T,P&gt; task, P param);// 提交任务,并返回结果
}
</code></pre>
<p>接着,我们再申明一个查询执行结果的接口类,用于提交任务之后,在主线程中查询执行结果:</p>
<pre><code>public interface Future&lt;T&gt; {
T get(); // 获取返回结果
boolean done(); // 判断是否完成
}
</code></pre>
<p>然后,我们先实现这个任务接口类,当需要返回结果时,我们通过调用获取结果类的 finish 方法将结果传回给查询执行结果类:</p>
<pre><code>public class TaskServiceImpl&lt;T, P&gt; implements TaskService&lt;T, P&gt; {
/**
* 提交任务实现方法,不需要返回执行结果
*/
@Override
public Future&lt;?&gt; submit(Runnable runnable) {
final FutureTask&lt;Void&gt; future = new FutureTask&lt;Void&gt;();
new Thread(() -&gt; {
runnable.run();
}, Thread.currentThread().getName()).start();
return future;
}
/**
* 提交任务实现方法,需要返回执行结果
*/
@Override
public Future&lt;?&gt; submit(Task&lt;T, P&gt; task, P param) {
final FutureTask&lt;T&gt; future = new FutureTask&lt;T&gt;();
new Thread(() -&gt; {
T result = task.doTask(param);
future.finish(result);
}, Thread.currentThread().getName()).start();
return future;
}
}
</code></pre>
<p>最后我们再实现这个查询执行结果接口类FutureTask 中get 和 finish 方法利用了线程间的通信 wait 和 notifyAll 实现了线程的阻塞和唤醒。当任务没有完成之前通过 get 方法获取结果,主线程将会进入阻塞状态,直到任务完成,再由任务线程调用 finish 方法将结果传回给主线程,并唤醒该阻塞线程:</p>
<pre><code>public class FutureTask&lt;T&gt; implements Future&lt;T&gt; {
private T result;
private boolean isDone = false;
private final Object LOCK = new Object();
@Override
public T get() {
synchronized (LOCK) {
while (!isDone) {
try {
LOCK.wait();// 阻塞等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return result;
}
/**
* 获取到结果,并唤醒阻塞线程
* @param result
*/
public void finish(T result) {
synchronized (LOCK) {
if (isDone) {
return;
}
this.result = result;
this.isDone = true;
LOCK.notifyAll();
}
}
@Override
public boolean done() {
return isDone;
}
}
</code></pre>
<p>我们可以实现一个造车任务,然后用任务提交类提交该造车任务:</p>
<pre><code>public class MakeCarTask&lt;T, P&gt; implements Task&lt;T, P&gt; {
@SuppressWarnings(&quot;unchecked&quot;)
@Override
public T doTask(P param) {
String car = param + &quot; is created success&quot;;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return (T) car;
}
}
</code></pre>
<p>最后运行该任务:</p>
<pre><code>public class App {
public static void main(String[] args) {
// TODO Auto-generated method stub
TaskServiceImpl&lt;String, String&gt; taskService = new TaskServiceImpl&lt;String, String&gt;();// 创建任务提交类
MakeCarTask&lt;String, String&gt; task = new MakeCarTask&lt;String, String&gt;();// 创建任务
Future&lt;?&gt; future = taskService.submit(task, &quot;car1&quot;);// 提交任务
String result = (String) future.get();// 获取结果
System.out.print(result);
}
}
</code></pre>
<p>运行结果:</p>
<pre><code>car1 is created success
</code></pre>
<p>从 JDK1.5 起Java 就提供了一个 Future 类,它可以通过 get() 方法阻塞等待获取异步执行的返回结果,然而这种方式在性能方面会比较糟糕。在 JDK1.8 中Java 提供了 CompletableFuture 类它是基于异步函数式编程。相对阻塞式等待返回结果CompletableFuture 可以通过回调的方式来处理计算结果,所以实现了异步非阻塞,从性能上来说它更加优越了。</p>
<p>在 Dubbo2.7.0 版本中Dubbo 也是基于 CompletableFuture 实现了异步通信,基于回调方式实现了异步非阻塞通信,操作非常简单方便。</p>
<h2>[第 29 讲]</h2>
<p><strong>我们可以用生产者消费者模式来实现瞬时高并发的流量削峰,然而这样做虽然缓解了消费方的压力,但生产方则会因为瞬时高并发,而发生大量线程阻塞。面对这样的情况,你知道有什么方式可以优化线程阻塞所带来的性能问题吗?</strong></p>
<p>无论我们的程序优化得有多么出色,只要并发上来,依然会出现瓶颈。虽然生产者消费者模式可以帮我们实现流量削峰,但是当并发量上来之后,依然有可能导致生产方大量线程阻塞等待,引起上下文切换,增加系统性能开销。这时,我们可以考虑在接入层做限流。</p>
<p>限流的实现方式有很多,例如,使用线程池、使用 Guava 的 RateLimiter 等。但归根结底,它们都是基于这两种限流算法来实现的:漏桶算法和令牌桶算法。</p>
<p>漏桶算法是基于一个漏桶来实现的,我们的请求如果要进入到业务层,必须经过漏桶,漏桶出口的请求速率是均衡的,当入口的请求量比较大的时候,如果漏桶已经满了,请求将会溢出(被拒绝),这样我们就可以保证从漏桶出来的请求量永远是均衡的,不会因为入口的请求量突然增大,致使进入业务层的并发量过大而导致系统崩溃。</p>
<p>令牌桶算法是指系统会以一个恒定的速度在一个桶中放入令牌一个请求如果要进来它需要拿到一个令牌才能进入到业务层当桶里没有令牌可以取时则请求会被拒绝。Google 的 Guava 包中的 RateLimiter 就是基于令牌桶算法实现的。</p>
<p>我们可以发现,漏桶算法可以通过限制容量池大小来控制流量,而令牌算法则可以通过限制发放令牌的速率来控制流量。</p>
<p>[第 30 讲]</p>
<p><strong>责任链模式、策略模式与装饰器模式有很多相似之处。在平时,这些设计模式除了在业务中被用到之外,在架构设计中也经常被用到,你是否在源码中见过这几种设计模式的使用场景呢?欢迎你与大家分享。</strong></p>
<p>责任链模式经常被用在一个处理需要经历多个事件处理的场景。为了避免一个处理跟多个事件耦合在一起,该模式会将多个事件连成一条链,通过这条链路将每个事件的处理结果传递给下一个处理事件。责任链模式由两个主要实现类组成:抽象处理类和具体处理类。</p>
<p>另外,很多开源框架也用到了责任链模式,例如 Dubbo 中的 Filter 就是基于该模式实现的。而 Dubbo 的许多功能都是通过 Filter 扩展实现的比如缓存、日志、监控、安全、telnet 以及 RPC 本身,责任链中的每个节点实现了 Filter 接口,然后由 ProtocolFilterWrapper 将所有的 Filter 串连起来。</p>
<p>策略模式与装饰器模式则更为相似,策略模式主要由一个策略基类、具体策略类以及一个工厂环境类组成,与装饰器模式不同的是,策略模式是指某个对象在不同的场景中,选择的实现策略不一样。例如,同样是价格策略,在一些场景中,我们就可以使用策略模式实现。基于红包的促销活动商品,只能使用红包策略,而基于折扣券的促销活动商品,也只能使用折扣券。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Java并发编程实战/30 装饰器模式:如何优化电商系统中复杂的商品价格策略?.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/Java并发编程实战/32 MySQL调优之SQL语句如何写出高性能SQL语句.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":"709971a1ae383d60","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>