learn.lianglianglee.com/专栏/Java 并发编程 78 讲-完/78 一份独家的 Java 并发工具图谱.md.html
2022-08-14 03:40:33 +08:00

478 lines
42 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>78 一份独家的 Java 并发工具图谱.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 并发编程 78 讲-完/00 由点及面,搭建你的 Java 并发知识网.md.html">00 由点及面,搭建你的 Java 并发知识网</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/01 为何说只有 1 种实现线程的方法?.md.html">01 为何说只有 1 种实现线程的方法?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/02 如何正确停止线程?为什么 volatile 标记位的停止方法是错误的?.md.html">02 如何正确停止线程?为什么 volatile 标记位的停止方法是错误的?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/03 线程是如何在 6 种状态之间转换的?.md.html">03 线程是如何在 6 种状态之间转换的?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/04 waitnotifynotifyAll 方法的使用注意事项?.md.html">04 waitnotifynotifyAll 方法的使用注意事项?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/05 有哪几种实现生产者消费者模式的方法?.md.html">05 有哪几种实现生产者消费者模式的方法?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/06 一共有哪 3 类线程安全问题?.md.html">06 一共有哪 3 类线程安全问题?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/07 哪些场景需要额外注意线程安全问题?.md.html">07 哪些场景需要额外注意线程安全问题?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/08 为什么多线程会带来性能问题?.md.html">08 为什么多线程会带来性能问题?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/09 使用线程池比手动创建线程好在哪里?.md.html">09 使用线程池比手动创建线程好在哪里?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/10 线程池的各个参数的含义?.md.html">10 线程池的各个参数的含义?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/11 线程池有哪 4 种拒绝策略?.md.html">11 线程池有哪 4 种拒绝策略?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/12 有哪 6 种常见的线程池?什么是 Java8 的 ForkJoinPool.md.html">12 有哪 6 种常见的线程池?什么是 Java8 的 ForkJoinPool</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/13 线程池常用的阻塞队列有哪些?.md.html">13 线程池常用的阻塞队列有哪些?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/14 为什么不应该自动创建线程池?.md.html">14 为什么不应该自动创建线程池?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/15 合适的线程数量是多少CPU 核心数和线程数的关系?.md.html">15 合适的线程数量是多少CPU 核心数和线程数的关系?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/16 如何根据实际需要,定制自己的线程池?.md.html">16 如何根据实际需要,定制自己的线程池?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/17 如何正确关闭线程池shutdown 和 shutdownNow 的区别?.md.html">17 如何正确关闭线程池shutdown 和 shutdownNow 的区别?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/18 线程池实现“线程复用”的原理?.md.html">18 线程池实现“线程复用”的原理?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/19 你知道哪几种锁?分别有什么特点?.md.html">19 你知道哪几种锁?分别有什么特点?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/20 悲观锁和乐观锁的本质是什么?.md.html">20 悲观锁和乐观锁的本质是什么?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/21 如何看到 synchronized 背后的“monitor 锁”?.md.html">21 如何看到 synchronized 背后的“monitor 锁”?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/22 synchronized 和 Lock 孰优孰劣,如何选择?.md.html">22 synchronized 和 Lock 孰优孰劣,如何选择?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/23 Lock 有哪几个常用方法?分别有什么用?.md.html">23 Lock 有哪几个常用方法?分别有什么用?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/24 讲一讲公平锁和非公平锁,为什么要“非公平”?.md.html">24 讲一讲公平锁和非公平锁,为什么要“非公平”?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/25 读写锁 ReadWriteLock 获取锁有哪些规则?.md.html">25 读写锁 ReadWriteLock 获取锁有哪些规则?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/26 读锁应该插队吗?什么是读写锁的升降级?.md.html">26 读锁应该插队吗?什么是读写锁的升降级?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/27 什么是自旋锁?自旋的好处和后果是什么呢?.md.html">27 什么是自旋锁?自旋的好处和后果是什么呢?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/28 JVM 对锁进行了哪些优化?.md.html">28 JVM 对锁进行了哪些优化?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/29 HashMap 为什么是线程不安全的?.md.html">29 HashMap 为什么是线程不安全的?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/30 ConcurrentHashMap 在 Java7 和 8 有何不同?.md.html">30 ConcurrentHashMap 在 Java7 和 8 有何不同?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/31 为什么 Map 桶中超过 8 个才转为红黑树?.md.html">31 为什么 Map 桶中超过 8 个才转为红黑树?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/32 同样是线程安全ConcurrentHashMap 和 Hashtable 的区别.md.html">32 同样是线程安全ConcurrentHashMap 和 Hashtable 的区别</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/33 CopyOnWriteArrayList 有什么特点?.md.html">33 CopyOnWriteArrayList 有什么特点?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/34 什么是阻塞队列?.md.html">34 什么是阻塞队列?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/35 阻塞队列包含哪些常用的方法add、offer、put 等方法的区别?.md.html">35 阻塞队列包含哪些常用的方法add、offer、put 等方法的区别?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/36 有哪几种常见的阻塞队列?.md.html">36 有哪几种常见的阻塞队列?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/37 阻塞和非阻塞队列的并发安全原理是什么?.md.html">37 阻塞和非阻塞队列的并发安全原理是什么?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/38 如何选择适合自己的阻塞队列?.md.html">38 如何选择适合自己的阻塞队列?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/39 原子类是如何利用 CAS 保证线程安全的?.md.html">39 原子类是如何利用 CAS 保证线程安全的?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/40 AtomicInteger 在高并发下性能不好,如何解决?为什么?.md.html">40 AtomicInteger 在高并发下性能不好,如何解决?为什么?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/41 原子类和 volatile 有什么异同?.md.html">41 原子类和 volatile 有什么异同?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/42 AtomicInteger 和 synchronized 的异同点?.md.html">42 AtomicInteger 和 synchronized 的异同点?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/43 Java 8 中 Adder 和 Accumulator 有什么区别?.md.html">43 Java 8 中 Adder 和 Accumulator 有什么区别?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/44 ThreadLocal 适合用在哪些实际生产的场景中?.md.html">44 ThreadLocal 适合用在哪些实际生产的场景中?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/45 ThreadLocal 是用来解决共享资源的多线程访问的问题吗?.md.html">45 ThreadLocal 是用来解决共享资源的多线程访问的问题吗?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/46 多个 ThreadLocal 在 Thread 中的 threadlocals 里是怎么存储的?.md.html">46 多个 ThreadLocal 在 Thread 中的 threadlocals 里是怎么存储的?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/47 内存泄漏——为何每次用完 ThreadLocal 都要调用 remove().md.html">47 内存泄漏——为何每次用完 ThreadLocal 都要调用 remove()</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/48 Callable 和 Runnable 的不同?.md.html">48 Callable 和 Runnable 的不同?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/49 Future 的主要功能是什么?.md.html">49 Future 的主要功能是什么?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/50 使用 Future 有哪些注意点Future 产生新的线程了吗?.md.html">50 使用 Future 有哪些注意点Future 产生新的线程了吗?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/51 如何利用 CompletableFuture 实现“旅游平台”问题?.md.html">51 如何利用 CompletableFuture 实现“旅游平台”问题?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/52 信号量能被 FixedThreadPool 替代吗?.md.html">52 信号量能被 FixedThreadPool 替代吗?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/53 CountDownLatch 是如何安排线程执行顺序的?.md.html">53 CountDownLatch 是如何安排线程执行顺序的?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/54 CyclicBarrier 和 CountdownLatch 有什么异同?.md.html">54 CyclicBarrier 和 CountdownLatch 有什么异同?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/55 Condition、object.wait() 和 notify() 的关系?.md.html">55 Condition、object.wait() 和 notify() 的关系?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/56 讲一讲什么是 Java 内存模型?.md.html">56 讲一讲什么是 Java 内存模型?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/57 什么是指令重排序?为什么要重排序?.md.html">57 什么是指令重排序?为什么要重排序?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/58 Java 中的原子操作有哪些注意事项?.md.html">58 Java 中的原子操作有哪些注意事项?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/59 什么是“内存可见性”问题?.md.html">59 什么是“内存可见性”问题?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/60 主内存和工作内存的关系?.md.html">60 主内存和工作内存的关系?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/61 什么是 happens-before 规则?.md.html">61 什么是 happens-before 规则?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/62 volatile 的作用是什么?与 synchronized 有什么异同?.md.html">62 volatile 的作用是什么?与 synchronized 有什么异同?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/63 单例模式的双重检查锁模式为什么必须加 volatile.md.html">63 单例模式的双重检查锁模式为什么必须加 volatile</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/64 你知道什么是 CAS 吗?.md.html">64 你知道什么是 CAS 吗?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/65 CAS 和乐观锁的关系,什么时候会用到 CAS.md.html">65 CAS 和乐观锁的关系,什么时候会用到 CAS</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/66 CAS 有什么缺点?.md.html">66 CAS 有什么缺点?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/67 如何写一个必然死锁的例子?.md.html">67 如何写一个必然死锁的例子?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/68 发生死锁必须满足哪 4 个条件?.md.html">68 发生死锁必须满足哪 4 个条件?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/69 如何用命令行和代码定位死锁?.md.html">69 如何用命令行和代码定位死锁?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/70 有哪些解决死锁问题的策略?.md.html">70 有哪些解决死锁问题的策略?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/71 讲一讲经典的哲学家就餐问题.md.html">71 讲一讲经典的哲学家就餐问题</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/72 final 的三种用法是什么?.md.html">72 final 的三种用法是什么?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/73 为什么加了 final 却依然无法拥有“不变性”?.md.html">73 为什么加了 final 却依然无法拥有“不变性”?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/74 为什么 String 被设计为是不可变的?.md.html">74 为什么 String 被设计为是不可变的?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/75 为什么需要 AQSAQS 的作用和重要性是什么?.md.html">75 为什么需要 AQSAQS 的作用和重要性是什么?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/76 AQS 的内部原理是什么样的?.md.html">76 AQS 的内部原理是什么样的?</a>
</li>
<li>
<a href="/专栏/Java 并发编程 78 讲-完/77 AQS 在 CountDownLatch 等类中的应用原理是什么?.md.html">77 AQS 在 CountDownLatch 等类中的应用原理是什么?</a>
</li>
<li>
<a class="current-tab" href="/专栏/Java 并发编程 78 讲-完/78 一份独家的 Java 并发工具图谱.md.html">78 一份独家的 Java 并发工具图谱</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>78 一份独家的 Java 并发工具图谱</h1>
<p>本课时将提纲挈领的对本专栏的重点进行提炼,对前面 77 个课时的内容进行了整理和梳理,方便你复习前面的内容。如果你正准备面试,没有时间看前面的内容,可以通过本课时把 Java 并发知识体系快速建立起来,发现哪一块知识有薄弱的话,可以有针对性的去回顾那一课时的具体内容。</p>
<p>本专栏总共分为 3 个大模块,分别是模块一:夯实并发基础,模块二:玩转 JUC 并发工具,模块三:深入浅出底层原理,知其所以然。我们就从模块一:夯实并发基础部分开始讲起。</p>
<h3>模块一:夯实并发基础</h3>
<h4>线程基础升华</h4>
<p>首先对线程基础进行讲解和升华,在实现多线程上,讲解了为何本质只有 1 种<strong>实现线程</strong>的方法,并对于传统的 2 种或 3 种的说法进行了辨析;同时讲解了应该如何正确的<strong>停止线程</strong>,用 volatile 标记位的停止方法是不够全面的。</p>
<p>然后介绍了线程的 <strong>6 种状态</strong>,即 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED还介绍了转换路径。之后就把目光聚焦到了 <strong>wait、notify/notifyAll、sleep</strong> 相关的方法上,这也是面试中常考的内容,我们讲解了它们的注意事项,包括:</p>
<ul>
<li>为什么 wait 方法必须在 synchronized 保护的同步代码中使用?</li>
<li>为什么 wait / notify / notifyAll 被定义在 Object 类中,而 sleep 定义在 Thread 类中?</li>
</ul>
<p>我们还把 wait / notify 和 sleep 进行了比较,并分析它们的异同。之后我们用三种方式实现了<strong>生产者和消费者模式</strong>,分别是 wait / notify、Condition、BlockingQueue 的方式,并对它们进行了对比。</p>
<h4>线程安全</h4>
<p>在线程安全的相关课时中,首先讲解了<strong>什么是线程安全</strong>,线程<strong>不安全的场景</strong>包括运行结果错误、发布或初始化错误以及活跃性问题,而活跃性问题又包括死锁、活锁和饥饿。</p>
<p>然后总结了 4 种特别需要<strong>注意线程安全的情况</strong>,分别是:</p>
<ul>
<li>有操作共享资源或变量的时候;</li>
<li>依赖时序的操作;</li>
<li>不同数据之间存在绑定关系;</li>
<li>使用的类没有声明自己是线程安全的。</li>
</ul>
<p>之后,讲解了多线程所带来的<strong>性能问题</strong>,包括线程调度所产生的上下文切换和缓存失效,以及线程协作带来的开销。</p>
<h3>模块二:玩转 JUC 并发工具</h3>
<h4>线程池</h4>
<p>下面进入模块二:玩转 JUC 并发工具的部分,在线程池部分中我们首先给出了 3 点使用<strong>线程池</strong>的原因,也就是说,使用线程池比手动创建线程好的地方在于:</p>
<ul>
<li>可以解决线程生命周期的系统开销问题,同时还可以加快响应速度;</li>
<li>可以统筹内存和 CPU 的使用,避免资源使用不当;</li>
<li>可以统一管理资源。</li>
</ul>
<p>在了解了线程池的好处之后,就需要掌握线程池的<strong>各个参数</strong>的含义,即 corePoolSize、maxPoolSize、keepAliveTime、workQueue、ThreadFactory、Handler并且这也是<strong>面试中非常常见的考点</strong>,我们需要知道每个参数代表什么含义。</p>
<p>而线程池也可能会<strong>拒绝</strong>我们提交的任务,我们讲解了 2 种拒绝的时机以及 4 种拒绝的策略,分别是 AbortPolicy、DiscardPolicy、DiscardOldestPolicy、CallerRunsPolicy我们可以根据自己的业务需求去选择合适的拒绝策略。</p>
<p>之后介绍了 <strong>6 种常见的线程池</strong>,即 FixedThreadPool、CachedThreadPool、ScheduledThreadPool、SingleThreadExecutor、SingleThreadScheduledExecutor 和 ForkJoinPool这 6 种线程池各有各的特点,它们所采用的的参数也各不相同。</p>
<p>接下来介绍了<strong>阻塞队列</strong>,在线程池中比较常用的是 3 种阻塞队列,即 LinkedBlockingQueue、SynchronousQueue、DelayedWorkQueue。然后讲解了为什么不应该自动创建线程池主要原因是考虑到自动创建的线程池可能会发生 OOM 等风险,我们<strong>手动创建线程池</strong>,就可以更加明确其运行规则,也可以在必要的时候拒绝新的任务提交,所以是更加安全的。</p>
<p>既然说到要手动去创建线程,那怎么设置线程池的参数呢?这里就需要考虑到<strong>合适的线程数量</strong>是多少,我们给出了一个通用的建议:</p>
<ul>
<li>线程的平均工作时间所占比例越高,则需要越少的线程;</li>
<li>线程的平均等待时间所占比例越高,则需要越多的线程;</li>
<li>针对不同的程序,进行对应的压力测试就可以得到最合适的线程数。</li>
</ul>
<p>最后讲解了如何<strong>关闭线程池</strong>,讲解了和关闭线程池相关的 5 个方法,即 shutdown()、isShutdown()、isTerminated()、awaitTermination()、shutdownNow() 。其中的重点是 <strong>shutdown() 和 shutdownNow()</strong> 这两个方法的区别,前一个是优雅关闭,后一个则是立刻关闭。接着还对线程池实现“线程复用”的原理进行了讲解,同时分析了 <strong>execute 方法的源码,这是线程池中一个非常重要的方法</strong></p>
<h4>各种各样的“锁”</h4>
<p>在 Java 中,锁有很多种类,比如<strong>悲观锁和乐观锁、共享锁和独占锁、公平锁和非公平锁、可重入锁和非可重入锁、可中断锁和不可中断锁、自旋锁和非自旋锁、偏斜锁/轻量级锁/重量级锁</strong>等。关于悲观锁和乐观锁,我们分析了它们各自的使用场景,还对 synchronized 这种悲观锁分析了原理,看到了其背后的 “monitor ” 锁,然后对 synchronized 和 Lock 进行了比较,并且给出了选择建议:</p>
<p>如果可以,最好既不使用 Lock 也不使用 synchronized而是优先使用 JUC 包中其他的成熟工具,因为它们通常会帮我们自动处理所有的加锁和解锁操作;如果必须使用锁,则优先使用 synchronized因为它可以减少代码编写的数量以及降低出错的概率因为一旦使用 Lock就必须在 finally 中写上 unlock不然代码可能会出很大的问题而使用 synchronized 就不必考虑这些问题,因为它会自动解锁。当然如果 synchronized 不能满足我们的需求,就得考虑使用 Lock。</p>
<p>所以接下来就是 Lock 相关的内容,它有很多强大的功能,比如尝试获取锁、有超时的获取等。我们介绍了 lock() 、tryLock()、tryLock(long time, TimeUnit unit)、lockInterruptibly()、unlock() 这几个常用的方法,并且讲解了它们的作用。然后讲解了<strong>公平锁和非公平锁</strong>,其中公平锁会按照线程申请锁的顺序来依次获取锁,而非公平锁存在插队的情况,这在一定情况下可以提高整体的效率,通常默认也是非公平的。</p>
<p>接着是读写锁内容。ReadWriteLock 适用于读多写少的情况,合理使用可以进一步提高并发效率,它的规则是:<strong>要么是一个或多个线程同时持有读锁,要么是一个线程持有写锁</strong>,但两者不会同时出现。也可以总结为读读共享、其他都互斥(包括写写互斥、读写互斥、写读互斥)。之后还讲解了读写锁的升降级和插队策略。</p>
<p>对于自旋锁而言,首先介绍了什么是自旋锁,然后对比了自旋和非自旋锁的获取锁的过程,讲解了自旋锁的好处,然后自己实现了一个可重入的自旋锁,最后还分析了自旋锁的缺点和适用场景。</p>
<p>在锁的内容中,最后还讲解了 <strong>JVM 对锁进行的优化</strong>包括自适应的自旋锁、锁消除、锁粗化、偏向锁、轻量级锁、重量级锁等。有了这些优化点之后synchronized 的性能并不比其他的锁差,所以我们使用 synchronized 来满足业务条件在性能方面是完全 OK 的。</p>
<h4>并发容器面面观</h4>
<p>并发容器是一个重点。在并发容器的章节中,首先讲解了 HashMap 为什么是线程不安全的,然后对比了 <strong>ConcurrentHashMap</strong> 在 Java 7 和 8 中的区别,包括数据结构、并发度、保证并发安全的原理、遇到 Hash 碰撞、查询时间复杂度方面的区别。然后还分析了在 Map 桶中为什么超过 8 个才转为红黑树? 这是一种时间和空间上的平衡,以及对比了 ConcurrentHashMap 和 Hashtable虽然它们都是线程安全的但在出现版本上、实现线程安全的方式上、性能上、迭代时修改上都是不同的。</p>
<p>接着介绍了 CopyOnWriteArrayList它的适用场景是读操作可以尽可能的快而写即使慢一些也没关系以及读多写少的场景。CopyOnWriteArrayList 的读写规则是读取完全不用加锁,而写入也不会阻塞读取操作,也就是可以在写入的同时进行读取,只有写入和写入之间需要进行同步,不允许多个写入同时发生。之后还介绍了它的允许迭代时修改集合内容的特点以及 3 个缺点,分别是内存占用问题、在元素较多或者复杂的情况下,复制开销大的问题以及数据一致性问题,最后我们还对它的源码进行了分析。</p>
<h4>阻塞队列</h4>
<p>在并发容器里还有一个重点,那就是<strong>阻塞队列</strong>,首先介绍了什么是阻塞队列以及对于阻塞队列中的 3 组方法进行了辨析,同时还给出了代码演示。然后分别介绍了常见的 5 种阻塞队列,以及它们的特点,分别是 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue 和 DelayQueue。</p>
<p>之后对比了阻塞和非阻塞队列的并发安全原理,其中阻塞队列主要利用了 ReentrantLock 以及它的 Condition 来实现的,而非阻塞队列则是利用了 CAS 保证线程安全。</p>
<p>最后,我们讲解了如何选择适合自己的阻塞队列,需要从功能、容量、能否扩容、内存结构及性能这些方面去考虑综合选择适合自己的阻塞队列。</p>
<h4>原子类</h4>
<p>原子类是 JUC 包中的一个重量级的人物。首先介绍了 6 种原子类型即基本类型原子类、数组类型原子类、引用类型原子类、升级类型原子类、Adder 和 Accumulator。</p>
<p>接下来分析了 <strong>AtomicInteger</strong> 在高并发下性能不好以及如何解决的问题。性能不好的主要原因是在高并发下碰撞和冲突会比较多,我们可以使用 LongAdder 来解决这个问题;同时分析了 <strong>LongAdder</strong> 内部的原理。然后对比了原子类和 volatile ,如果只是有可见性问题的话,那么可以使用 volatile 来解决,但如果需要保证原子性的话,就需要使用原子类或其他工具来解决,而不应使用 volatile。</p>
<p>之后,我们把原子类和 synchronized 进行了对比,它们在功能上相似,但是在原理上、适用范围上、粒度上、性能上都有区别。最后还介绍了 Java 8 加入的 Accumulator它是一个更通用版本的 Adder。</p>
<h4>ThreadLocal</h4>
<p>首先讲解了两种场景是适合于 ThreadLocal 的:</p>
<ul>
<li>第一种是用作每个线程保存独享的对象,比如日期工具类;</li>
<li>第二种是 ThreadLocal 给每个线程去保存场景、上下文信息,以便后续的方法更方便的获取其信息,避免了传参。</li>
</ul>
<p>当然 ThreadLocal 并不是用来解决共享资源的多线程访问的问题的,因为它设计的本意是,资源并不是共享的,只是在每个线程内有个资源的副本而已,而每个副本都是各线程独享的。</p>
<p>接下来还分析了 ThreadLocal 的内部结构,需要掌握 <strong>Thread、ThreadLocal 及 ThreadLocalMap 三者之间的关系</strong>,同时还介绍了使用 ThreadLocal 之后要使用 remove 方法来防止内存泄漏。</p>
<h4>Future</h4>
<p>接下来是 Future 相关的内容。首先对比了 Callable 和 Runnable 的不同,它们在方法名、返回值、抛出异常上,以及和 Future 类的关系上都有所不同。然后介绍了 Future 类的主要功能,即把运算的过程放到子线程去执行,再通过 Future 去控制执行过程,最后获取到计算结果。这样一来就可以把整个程序的运行效率提高,是一种<strong>异步</strong>的思想。</p>
<p>我们还对 Future 的 get、get(long timeout, TimeUnit unit)、isDone()、cancel()、isCancelled() 这 5 种方法进行了详细讲解。在使用 Future 的时候要注意,比如我们用 for 循环批量获取 Future 的结果时容易阻塞,应该使用超时限制,并且 Future 的生命周期不能后退,而且 Future 本身并不能产生新的线程,它需要借助 Thread 类或者线程池才能用子线程执行任务。</p>
<p>之后讲解了一个“旅游平台”的问题,它希望高效获取各航空公司的机票信息,我们对代码进行了演进:从最开始的串行,到并行,然后到有超时的并行,最后我们发现,而且如果航空公司的响应速度都很快的话,也不需要一直等到超时的时间到了,而是可以提前结束等待的。我们就这样进行了一步一步的迭代,升级了我们的代码,该“旅游平台”问题也是平时工作中经常会遇到的问题,因为我们经常需要并行获取和处理数据。</p>
<h4>线程协作</h4>
<p>在线程配合相关的类中,我们讲解了 <strong>Semaphore 信号量、CountDownLatch、CyclicBarriar 和 Condition</strong></p>
<p>在信号量的课程中,首先介绍了它的使用场景、用法及注意点,其中注意点包括获取和释放的许可证数量尽量保持一致,在初始化的时候可以设置公平性以及信号量是支持跨线程、跨线程池的。</p>
<p>对于 CountDownLatch 而言,我们在创建类的时候,需要在构造函数中传入“倒数”次数,然后由需要等待的线程去调用 await 方法来等待,而每一次其他线程调用了 countDown 方法之后,计数便会减 1直到减为 0 时,之前等待的线程便会继续运行。</p>
<p>接下来介绍了 CyclicBarria它和 CountDownLatch 在用法上是有些相似的,即都能阻塞一个或一组线程,直到某个预设的条件达成发生,再统一出发,但它们也有很多的不同点,它们的作用对象不同、可重用性不同及执行动作的能力不同。</p>
<p>最后介绍了 Condition 和 wait / notify / notifyAll 的关系。如果说 Lock 是用来代替 synchronized 的,那么 Condition 就是用来代替相对应的 Object 的 wait / notify / notifyAll 的,所以它们在用法和性质上都是非常相似的。</p>
<h3>模块三:深入浅出底层原理,知其所以然</h3>
<h4>Java 内存模型</h4>
<p>然后就进入到了我们第 3 个模块:深入浅出底层原理,知其所以然。第一个重点是 <strong>Java 内存模型</strong>。首先介绍了为什么需要 Java 内存模型,然后介绍了什么是 Java 内存模型,重点包括重排序、原子性、可见性。</p>
<p>接着首先介绍了<strong>重排序</strong>的相关内容,其好处是可以提高处理速度。</p>
<p>接着介绍了<strong>原子性</strong>包括什么是原子性、Java 中的原子操作有哪些、long 和 double 原子性的特殊性以及简单地把原子操作组合在一起,并不能保证整体依然具备原子性。</p>
<p>之后讲解了<strong>可见性</strong>,我们需要知道主内存和工作内存之间的关系,还需要知道 <strong>happens-before</strong> 关系:如果第一个操作 happens-before 第二个操作(也可以描述为第一个操作和第二个操作之间满足 happens-before 关系),那么我们就说第一个操作对于第二个操作一定是可见的,也就是第二个操作在执行时就一定能保证看见第一个操作执行的结果。<strong>这个关系非常重要,也是可见性内容的一个重点。</strong></p>
<p>最后介绍了 volatile 的两个作用,分别是保证可见性以及一定程度上禁止重排序,还分析了在单例模式的双重检查锁模式为什么必须加 volatile ?主要是为了保证线程安全。</p>
<h4>CAS 原理</h4>
<p>在 CAS Compare-And-Swap相关课时中首先介绍了 CAS 的核心思想,是通过将内存中的值与指定数据进行比较,当这两个数值一样时,才将内存中的数据替换为新的值,整个过程具备原子性。</p>
<p>然后介绍了 CAS 的应用,包括在并发容器、数据库以及原子类中都有很多对 CAS 的应用;之后介绍了 CAS 的三个缺点,即 ABA 问题、自旋时间过长问题,以及线程安全的范围不能灵活控制问题。</p>
<h4>死锁问题</h4>
<p>在死锁的相关课时中,首先介绍了什么是死锁:两个或多个线程(或进程)被无限期地阻塞,相互等待对方手中资源的状态就是死锁。我们写了必然死锁的例子,介绍了发生死锁必须满足的互斥条件、请求与保持条件、不剥夺条件和循环等待条件这 4 个必要条件,还分别用命令行和代码定位死锁并且给出了 3 种解决死锁问题的策略,分别是避免策略、检测与恢复策略、鸵鸟策略。最后还分析了经典的哲学家就餐问题。</p>
<h4>final 关键字和“不变性”</h4>
<p>首先介绍了 final 分别作用在<strong>变量上、方法上和类上</strong>的不同作用,以及分析了为什么加了 final 却依然无法拥有“不变性”?主要原因是 final 修饰的对象,内容依然可以变。然后分析了为什么 String 被设计为是不可变的?主要分析了这样设计的好处分别是可以利用字符串常量池、用作 HashMap 的 key、缓存 HashCode 以及保证线程安全。</p>
<h4>AQS 框架</h4>
<p>最后是 AQS 的内容,我们介绍了为什么需要 AQS 以及它内部的原理;还对 AQS 在 CountDownLatch 类中的应用进行了源码分析。</p>
<h3>总结</h3>
<p>以上就是本专栏的重点内容了,也涵盖到了 Java 并发编程的大部分重点知识。我也非常高兴能和你一起来学习和探讨关于 Java 并发的知识,在写作的过程中难免会有遗漏的知识点,可通过留言,或联系拉勾客服人员加入本课程的读者群一起讨论。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/Java 并发编程 78 讲-完/77 AQS 在 CountDownLatch 等类中的应用原理是什么?.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":"7099711b4ed83d60","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>