《码出高效 Java开发手册》第七章 并发与多线程
并发(Concurrency) 与并行(Parallelism)
以KTV唱歌为例, Parallelism 是指有多少人可以使用话筒同时唱歌,
Concurrency是指同一个话筒被多少个人轮流使用;
一个科室两个专家同时出诊, 就是两个并行任务; 其中一个医生时而问诊, 时而看化验单,
然后继续问诊, 就是并发;
- 并发特点:
- 并发程序之间相互制约;
- 并发程序的执行过程断断续续;
- 当并发数量设置合理并且CPU 拥有足够能力时, 并发可以提高程序效率;
7.1 线程安全
一、线程状态
NEW , 线程被创建且未启动的状态.
创建线程的三种方式:
- extends Thread , @Override run()
- implements Runnable , @Override run()
- implements Callable , V @Override call(), call() 可以抛出异常
RUNNABLE , 就绪状态, 是调用start() 之后运行之前的状态;
多次调用start() 会抛出IllegalStateException;
RUNNING , 允许状态,
BLOCKED , 阻塞状态
几个原因:
- 同步阻塞: 锁被其他线程占用;
- 主动阻塞: 调用Thread某些方法, 主动让出CPU执行权, 如sleep() 、join()
- 等待阻塞: 执行了wait()
DEAD , run() 结束或异常退出, 不可逆;
二、保证高并发场景下线程安全的几个度量
数据单线程内可见:
如ThreadLoca采用此机制
只读对象:
允许复制, 拒绝写入; 如String Integer
一个对象拒绝写入的条件:
- final修饰, 避免被继承;
- private final 修饰, 避免属性别中途修改;
- 没有任何更新方法;
- 返回值不能可变对象的应用;
线程安全类:
如StringBuffer采用了synchronized修饰;
同步与锁机制:
三、Java并发包(java.util.concurrent, JUC)
@auther Doug Lea
- 主要类族:
线程同步类
逐步淘汰了Object wait()和 notify();
如: CountDownLatch Semaphore CyclicBarrier并发集合类
执行速度快, 提取数据准; 如ConcurrentHashMap
线程管理类
使用线程池;如 Executors的静态工厂 和 ThreadPoolExecutor等, 通过ScheduledExecutorService来执行定时任务;
锁
Lock接口; ReentrantLock
7.2 什么是锁
一、锁的两种特性: 互斥性和不可见性
二、Java锁的常见两种实现方式:
- JUC包中的锁类 : volatile
- 利用同步代码块 : synchronized
- 同步对象, 同步方法
- 原则: 锁的范围尽可能小, 时间尽可能短; 能锁对象就不锁类, 能锁代码块就不要锁方法;
- synchronized 通过JVM实现
- 监视锁monitor是每个对象与生俱来的隐藏字段, 通过monitor状态来加锁解锁
- 字节码文件中通过monitorente, monitorexit来加锁解锁;
7.3 线程同步
7.3.1 同步是什么
* 原子性 (i++不具备1)
7.3.2 volatile
- 线程的可见性: 某线程修改共享变量的指令对其他线程来说都是可见的, 反映的是指令执行的实时透明度;
解决双重检查锁定( Double-checked Locking )问题
如:
VolatileNotAtomic.java
, 可以事项count++原子操作的其他类有AtomicLong和LongAdder;
jdk1.8推荐LongAdder, 它减少了乐观锁的重试次数;volatile是轻量级的线程操作可见方式, 并非同步方式, 如果用于多写环境, 会造成线程安全问题;
如果是一写多读的并发场景, 则修饰变量非常合适, 如 CopyOnWriteArrayList接口中
它修改数据时候会把整个数据集合复制, 对写加锁, 修改后再用setArray() 指向新的集合// 源码 package java.util.concurrent; public class CopyOnWriteArrayList<E> { // 集合真正存储元素的数组 private transient volatile Object[] array ; final void setArray (Object [] a ) { array = a; } }
volatile会使线程的执行速度变慢
7.3.3 信号量同步
信号量同步是指不同线程之间通过传递同步信号量来协调线程执行的先后次序;
基于时间维度和信号维度的两个类
- CountDownLatch
CountDownLatchTest.java
- (倒数); countDown() 用于使计数器减一, await()方法用于调用该方法的线程处于等待状态;
- Semaphore
* acquire() (获取) 调用成功后往下一步执行;
* release() (释放) 释放持有的信号量, 下一线程可以获取空闲信号量来进入执行;
* CyclicBarrier (循环使用的屏障式)
通过reset() 释放线程资源;
* **结论:** 无论是从性能还是安全性, 我们应该尽量使用JUC并发包中的小号量同步类, 而避免使用对象的wait()和notify();
**体具可以参考[并发编程网](http://ifeve.com/)**
## 7.4 线程池
### 7.4.1 ThreadPool的好处
* 利用线程池管理并复用线程, 控制最大并发数等;
* 实现任务线程队列缓存策略和拒绝机制;
* 实现某些与时间相关的功能, 如定时执行, 周期执行等;
* 隔离线程环境
如何创建线程
#### 1. ThreadPoolExecutor 构造方法:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
常驻核心线程数;设置过大造成资源浪费, 过小造成频繁创建销毁;
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
表示能够容纳同时执行的最大运行的线程数
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
线程空闲时间, 空闲时间达到时会被销毁; 默认线程数大于corePoolSize时生效
* @param unit the time unit for the {@code keepAliveTime} argument
时间单位
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
缓存队列; 线程大于maximmPoolSize时, 进入BlockingQueue阻塞队列, LinkedBlockingQueue是单向链表, 用于控制入队出队的原子性,
两个锁分别控制元素的添加和获取, 是生产消费模型队列
* @param threadFactory the factory to use when the executor
* creates a new thread
线程工厂; 生产一组相同任务的线程
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
执行拒绝策略的对象; 当超过workQueue缓存区上限是, 来处理请求;一种简单的限流保护;
友好的拒绝策略:
1. 保存到数据库削峰填谷. 空闲时再取出来执行;
2. 转向提示页面;
3. 打印日志;
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
// 队列, 线程工厂, 拒绝策略都必须实例化
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
// ...
}
#### 2. Executor和ThreadPoolExecutor关系
![图7-4 线程池相关类图](https://img2018.cnblogs.com/blog/1084504/201904/1084504-20190410212621790-561282211.jpg)
* Executors 的5个核心方法 :
* newFixedThreadPool : jdk1.8引入
* newCachedThreadPool
* newScheduledThreadPool
* newSingleThreadExecutor: 创建单线程的线程池
* newFixedThreadPool
* LinkedBlockingQueue
// 无边界队列, 如果请求瞬间非常大, 会造成OOM风险
// 除了newWorkStealingPool其他四个方法都有资源耗尽风险
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
```
Executes默认的线程工厂和拒绝策略过于简单, 对用户不友好;
UserThreadFactory.java
RejectedExecutionHandler
- ThreadPoolExecutorde 的四个公开内部类
- AbortPolicy 丢弃任务并抛出RejectedExecutionException异常
- DiscardPolicy 丢弃任务, 不抛异常(不推荐)
- DiscardOldestPolicy 抛弃任务中等待最久的任务, 把当前任务加入队列
- CallerRunsPolicy 直接调用任务的run(), 如果线程池
根据之前实现的结程工厂和拒绝策略,线程池的相关代码实现 :
UserThreadPool.java
7.4.2 线程源码讲解
- ThreadPoolExecutor 属性定义中频繁使用位移来表示线程状态
- 分析 ThreadPoolExecutor 关于 execute 方法的实现,
总结: 线程池使用注意点
- 合理配置各类参数, 应根据实际业务场景来设置合理的工作线程数;
- 线程资源必须通过线程池提供, 不允许应用中自行显示创建线程;
- 创建线程或线程池时请指定有意义的名称, 便于出错时的回溯;
7.5 ThreadLocal
《码出高效 Java开发手册》第七章 并发与多线程的更多相关文章
- 《码出高效 Java开发手册》第二章 面向对象
码云地址: https://gitee.com/forxiaoming/JavaBaseCode/tree/master/EasyCoding 第2章 面向对象 Object-Oriented Pro ...
- 《码出高效 Java开发手册》第一章计算机基础(未整理)
码云地址: https://gitee.com/forxiaoming/JavaBaseCode/tree/master/EasyCoding
- 《码出高效 Java开发手册》第六章 数据结构与集合
码云: https://gitee.com/forxiaoming/JavaBaseCode/blob/master/EasyCoding/src/collection/index.md 6.1 数据 ...
- 《码出高效 Java开发手册》第五章 异常与日志
码云: https://gitee.com/forxiaoming/JavaBaseCode/blob/master/EasyCoding/src/exception/index.md 5.2 try ...
- 《码出高效 Java开发手册》第四章 走进JVM(未整理)
码云地址: https://gitee.com/forxiaoming/JavaBaseCode/tree/master/EasyCoding
- 《码出高效 Java开发手册》第三章 代码风格
第3章 代码风格 3.1 命名 符合语言特性 体现代码元素特征: Abstract xxx. Basexxxx.xxException.xxxTest等; 包名统一使用小写, 完整单词+点分隔符; 枚 ...
- 码出高效,阿里巴巴JAVA开发手册1.4.0
码出高效,阿里巴巴JAVA开发手册1.4.0阅读笔记 一.编程规约(三) 代码格式// 关键词if与括号之间必须有一个空格,括号内的f与左括号,0与右括号不需要空格 if (flag == 0) { ...
- 《码出高效:Java开发手册》第四章学习记录,内容想当的多,前后花了几天的时间才整理好。
<码出高效:Java开发手册>第四章学习记录,内容想当的多,前后花了几天的时间才整理好. https://naotu.baidu.com/file/e667435a4638cbaa15eb ...
- 《阿里巴巴Java开发手册》码出高效详解(一)- 为什么要学习阿里编码手册
<Java 开发手册>(以下简称<手册>)是每个 Java 工程师人手必备的一本参考指南.该手册包括 编程规约.异常日志.单元测试.安全规约.MySQL 数据库.工程结构.设计 ...
随机推荐
- Docker容器的原理与实践(上)
本文来自网易云社区. 虚拟化 是一种资源管理技术,将计算机的各种资源予以抽象.转换后呈现出来, 打破实体结构间的不可切割的障碍,使用户可以比原本更好的方式来应用这些资源. Hypervisor 一种运 ...
- day70 csrf简单用法 &Django ContentType
一. 什么是跨站请求伪造 CSRF def transfer(request): if request.method =='POST': from_ =request.POST.get('from') ...
- Python中进程和线程的总体区别
Num01–>线程 线程是操作系统中能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位. 一个线程指的是进程中一个单一顺序的控制流. 一个进程中可以并发多条线程,每条线程并行 ...
- 【文文殿下】【BZOJ4804】欧拉心算
题解 显然有 \(ans=\sum _{i=1} ^{n} \lfloor \frac{n}{i} \rfloor \sum _{d|i} \mu(d) \phi (\frac{i}{d})\) 前半 ...
- Django(wsgiref、jinja2模块使用介绍)
day60 wsgiref比较稳定 """ 根据URL中不同的路径返回不同的内容--函数进阶版 返回HTML页面 让网页动态起来 wsgiref模块版 "&qu ...
- (转)rootvg镜像
步骤1:查看当前还未加入到其它vg的可用PV # lspv hdisk0 00027c6a0507fe17 rootvg ...
- 监督学习——决策树理论与实践(上):分类决策树
1. 介绍 决策树是一种依托决策而建立起来的一种树.在机器学习中,决策树是一种预测模型,代表的是一种对象属性与对象值之间的一种映射关系,每一个节点代表某个对象/分类,树中的每一个分叉路 ...
- litespeed 下配置 伪静态,反向代理
<IfModule mod_rewrite.c>RewriteEngine onRewriteBase / RewriteRule ^(.*).html$ index.php?static ...
- UTF8最好不要带BOM
摘自:http://www.cnblogs.com/findumars/p/3620078.html 几周前还在为BOM的问题苦恼着...正如@梁海所说,“不含 BOM 的 UTF-8 才是标准形 ...
- JAVA跨域资源访问CORSFilter
当一个资源从与该资源本身所在的服务器不同的域或端口不同的域或不同的端口请求一个资源时,资源会发起一个跨域 HTTP 请求. 出于安全考虑,浏览器会限制从脚本内发起的跨域HTTP请求.跨域资源共享机制允 ...