Java Concurrency in Practice——读书笔记
Thread Safety线程安全
线程安全编码的核心,就是管理对状态(state)的访问,尤其是对(共享shared、可变mutable)状态的访问。
- shared:指可以被多个线程访问的变量
- mutable:指在其生命周期内,它的值可被改变
通常,一个对象Object的状态state就是他的数据data,存储于状态变量(state variables)如实例对象或者静态变量,以及他所依赖的其他对象。
Java中最常用的同步机制是使用Synchronized关键字,其他还有volatile变量, explicit locks(显式锁), 和atomic variables(原子变量)。
概念
- state:状态,怎么理解好呢,就是(在某一给定时刻,它所存储的信息,这里理解为数据data)
- invariant:不变性,就是用来限制state的constrains the state stored in the object.例如:
  1 public class Date {
  2     int /*@spec_public@*/ day;
  3     int /*@spec_public@*/ hour;
  4
  5     /*@invariant 1 <= day && day <= 31; @*/ //class invariant
  6     /*@invariant 0 <= hour && hour < 24; @*/ //class invariant
  7
  8     /*@
  9     @requires 1 <= d && d <= 31;
 10     @requires 0 <= h && h < 24;
 11     @*/
 12     public Date(int d, int h) { // constructor
 13         day = d;
 14         hour = h;
 15     }
 16
 17     /*@
 18     @requires 1 <= d && d <= 31;
 19     @ensures day == d;
 20     @*/
 21     public void setDay(int d) {
 22         day = d;
 23     }
 24
 25     /*@
 26     @requires 0 <= h && h < 24;
 27     @ensures hour == h;
 28     @*/
 29     public void setHour(int h) {
 30         hour = h;
 31     }
 32 }
如何做到线程安全?
- 不在线程间共享状态变量(state variable)—无状态的对象总是线程安全的。
- 在线程间共享不可变的状态变量(immutable state variable)
- 在访问状态变量时,使用同步机制
什么是线程安全?
线程安全的核心概念是:正确性。一个类是否正确,取决于它是否遵守他的规范(specification),一个好的规范,定义了如下两点内容:
- invariants不变性,或者叫约束条件,约束了他的状态state
- postconditions后置条件,描述了操作后的影响
atomic原子性
一个无状态的Servlet必然是线程安全的,如下:
1 @ThreadSafe
2 public class StatelessFactorizer implements Servlet {
3 public void service(ServletRequest req, ServletResponse resp) {
4 BigInteger i = extractFromRequest(req);
5 BigInteger[] factors = factor(i);
6 encodeIntoResponse(resp, factors);
7 }
8 }
加入一个状态后,就不再线程安全了。
1 @NotThreadSafe
2 public class UnsafeCountingFactorizer implements Servlet {
3 private long count = 0;
4
5 public long getCount() {
6 return count;
7 }
8
9 public void service(ServletRequest req, ServletResponse resp) {
10 BigInteger i = extractFromRequest(req);
11 BigInteger[] factors = factor(i);
12 ++count;// 非原子操作
13 encodeIntoResponse(resp, factors);
14 }
15 }
++ 操作符并非原子操作,它包含三步:读值,加一,写入(read-modify-write)

Race condition竞态条件
多线程中,有可能出现由于不恰当的执行时序而造成不正确结果的情况,称为竞态条件。
竞态条件一:read-modify-write(先读取再修改写入)
最后的结果依赖于它之前的状态值,如上++操作
竞态条件二:check-then-act(先检查后执行)
示例:lazy initialization
1 @NotThreadSafe
2 public class LazyInitRace {
3 private ExpensiveObject instance = null;
4
5 public ExpensiveObject getInstance() {
6 if (instance == null)// check then act
7 instance = new ExpensiveObject();
8 return instance;
9 }
10 }
Compound actions复合操作
避免竞态条件的问题,就需要以“原子”方式执行上述操作,称之为“复合操作”。
解决read-modify-write这一类竞态条件问题时,通常使用已有的线程安全对象来管理类的状态,如下:
1 @ThreadSafe
2 public class CountingFactorizer implements Servlet {
3 private final AtomicLong count = new AtomicLong(0);
4 //使用线程安全类AtomicLong来管理count这个状态
5
6 public long getCount() {
7 return count.get();
8 }
9
10 public void service(ServletRequest req, ServletResponse resp) {
11 BigInteger i = extractFromRequest(req);
12 BigInteger[] factors = factor(i);
13 count.incrementAndGet();
14 encodeIntoResponse(resp, factors);
15 }
16 }
但这种方式无法满足check-then-act这一类竞态条件问题,如下:
1 @NotThreadSafe
2 public class UnsafeCachingFactorizer implements Servlet {
3 private final AtomicReference<BigInteger> lastNumber = new AtomicReference<BigInteger>();
4 private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<BigInteger[]>();
5
6 public void service(ServletRequest req, ServletResponse resp) {
7 BigInteger i = extractFromRequest(req);
8 if (i.equals(lastNumber.get()))
9 encodeIntoResponse(resp, lastFactors.get());
10 else {
11 BigInteger[] factors = factor(i);
12 lastNumber.set(i);
13 lastFactors.set(factors);
14 encodeIntoResponse(resp, factors);
15 }
16 }
17 }
锁Locking可以更完美的解决复合操作的原子性问题。当然锁也可以解决变量的可见性问题。
Intrinsic locks内置锁
也称为monitor locks监视器锁,每一个Java对象都可以被当成一个锁,自动完成锁的获取和释放,使用方式如下:
  1 synchronized (lock) {
  2  // Access or modify shared state guarded by lock
  3 }
内置锁是一种“互斥排它锁”,因此最多只有一个线程可以拥有这个锁。
同时,内置锁也是可重入的(Reentrancy),每个锁含有两个状态,一是获取计数器(acquisition count),一个是所有者线程(owning thread),当count=0,锁是可获取状态,当一个thread t1 获取了一个count=0的锁时,jvm设置这个锁的count=1,owning thread=t1,当t1再次要获取这个锁时,是被允许的(即可重入),此时count++,当t1退出该同步代码块时,count--,直到count=0后,即锁被t1彻底释放。
如何使用lock来保护state?
- 只是在复合操作(compound action)的整个执行过程中(entire duration)持有一把锁来维持state的原子性操作,是远远不够的;而是应该在所有这个状态可被获取的地方(everywhere that variable is accessed)都用同一把锁来协调对状态的获取(包括读、写)——可见性
- 所有(包含变量多于一个)的不定性,它所涉及的所有变量必须被同一把锁保护。(For every invariant that involves more than one variable, all the variables
 involved in that invariant must be guarded by the same lock.)
活跃性与性能
- 避免在较长时间的操作中持有锁,例如网络IO,控制台IO等。
- 在实现同步操作时,避免为了性能而复杂化,可能会带来安全性问题。
可见性
可见性比较难发现问题,是因为总是与我们的直觉相违背。
重排序(reordering)的存在,易造成失效数据(Stale data),但这些数据多数都是之前某一个线程留下来的数据,而非随机值,我们称这种情况为最低安全性(out-of-thin-air safety);但非原子的64位操作(如long,double),涉及到高位和低位分解为2个32位操作的情况,而无法满足最低安全性,线程读到的数据,可能是线程A留下的高位和线程B留下的低位组合。除非用volatile关键字或锁保护起来。
volatile关键字修饰的变量会避免与其他内存操作重排序。慎用!
发布Publishing与逸出escaped
发布:使对象能够在当前作用域外被使用。
逸出:不应该发布的对象被发布时。
隐式this指针逸出问题:
  1 public class ThisEscape {
  2     private String name = null;
  3
  4     public ThisEscape(EventSource source) {
  5         source.registerListener(new EventListener() {
  6             public void onEvent(Event event) {
  7                 doSomething(event);
  8             }
  9         });
 10         name = "TEST";
 11     }
 12
 13     protected void doSomething(Event event) {
 14         System.out.println(name.toString());
 15     }
 16 }
 17 // Interface
 18 import java.awt.Event;
 19
 20 public interface EventListener {
 21     public void onEvent(Event event);
 22 }
 23 // class
 24 public class EventSource {
 25     public void registerListener(EventListener listener) {
 26         listener.onEvent(null);
 27     }
 28 }
 29 // Main
 30 public class Client {
 31     public static void main(String[] args) throws InterruptedException {
 32         EventSource es = new EventSource();
 33         new ThisEscape(es);
 34     }
 35 }
运行上述代码会报空指针错误,是因为在name 初始化之前,就使用了ThisEscape实例(this指针逸出),而此时实例尚未完成初始化。
修改如下,避免This逸出:
  1 public class SafePublish {
  2
  3     private final EventListener listener;
  4     private String name = null;
  5
  6     private SafePublish() {
  7         listener = new EventListener() {
  8             public void onEvent(Event event) {
  9                 doSomething();
 10             }
 11         };
 12         name = "TEST";
 13     }
 14
 15     public static SafePublish newInstance(EventSource eventSource) {
 16         SafePublish safePublish = new SafePublish ();
 17         eventSource.registerListener(safeListener.listener);
 18         return safePublish;
 19     }
 20
 21     protected void doSomething() {
 22         System.out.println(name.toString());
 23     }
 24 }
造成this指针逸出的情况:
- 在构造函数中启动了一个线程或注册事件监听;—私有构造器和共有工厂方法
- 在构造函数中调用一个可以被override的方法(非private或final方法)
Thread confinement线程封闭
如Swing 和 JDBC的实现,使用局部变量(local variables )和 ThreadLocal 类
ad-hoc线程封闭:不太懂,就是开发者自己去维护封闭性?
Stack confinement栈封闭
不可变immutable
并不是被final修饰的就是绝对的不可变!!
使用Volatile来发布不可变对象
1 @Immutable
2 class OneValueCache {
3 private final BigInteger lastNumber;
4 private final BigInteger[] lastFactors;
5
6 public OneValueCache(BigInteger i, BigInteger[] factors) {
7 lastNumber = i;
8 lastFactors = Arrays.copyOf(factors, factors.length);
9 }
10
11 public BigInteger[] getFactors(BigInteger i) {
12 if (lastNumber == null || !lastNumber.equals(i))
13 return null;
14 else
15 return Arrays.copyOf(lastFactors, lastFactors.length);
16 }
17 }
18
19 // @ThreadSafe
20 public class VolatileCachedFactorizer implements Servlet {
21 private volatile OneValueCache cache = new OneValueCache(null, null);
22
23 public void service(ServletRequest req, ServletResponse resp) {
24 BigInteger i = extractFromRequest(req);
25 BigInteger[] factors = cache.getFactors(i);
26 if (factors == null) {
27 factors = factor(i);
28 cache = new OneValueCache(i, factors);
29 }
30 encodeIntoResponse(resp, factors);
31 }
32 }
33
Java Concurrency in Practice——读书笔记的更多相关文章
- Java Concurrency in Practice 读书笔记 第十章
		粗略看完<Java Concurrency in Practice>这部书,确实是多线程/并发编程的一本好书.里面对各种并发的技术解释得比较透彻,虽然是面向Java的,但很多概念在其他语言 ... 
- Java Concurrency in Practice 读书笔记 第二章
		第二章的思维导图(代码迟点补上): 
- java concurrency in practice读书笔记---ThreadLocal原理
		ThreadLocal这个类很强大,用处十分广泛,可以解决多线程之间共享变量问题,那么ThreadLocal的原理是什么样呢?源代码最能说明问题! public class ThreadLocal&l ... 
- 《Java编程思想》读书笔记(五)
		前言:本文是<Java编程思想>读书笔记系列的最后一章,本章的内容很多,需要细读慢慢去理解,文中的示例最好在自己电脑上多运行几次,相关示例完整代码放在码云上了,码云地址:https://g ... 
- 《Java 8实战》读书笔记系列——第三部分:高效Java 8编程(四):使用新的日期时间API
		https://www.lilu.org.cn/https://www.lilu.org.cn/ 第十二章:新的日期时间API 在Java 8之前,我们常用的日期时间API是java.util.Dat ... 
- 《Java编程思想》读书笔记(二)
		三年之前就买了<Java编程思想>这本书,但是到现在为止都还没有好好看过这本书,这次希望能够坚持通读完整本书并整理好自己的读书笔记,上一篇文章是记录的第一章到第十章的内容,这一次记录的是第 ... 
- 《Java编程思想》读书笔记(四)
		前言:三年之前就买了<Java编程思想>这本书,但是到现在为止都还没有好好看过这本书,这次希望能够坚持通读完整本书并整理好自己的读书笔记,上一篇文章是记录的第十七章到第十八章的内容,这一次 ... 
- 《Java编程思想》读书笔记
		前言 这个月一直没更新,就是一直在读这本<Java编程思想>,这本书可以在Java业界被传神的一本书,无论谁谈起这本书都说好,不管这个人是否真的读过这本书,都说啊,这本书很好.然后再看这边 ... 
- 《神经网络算法与实现-基于Java语言》的读书笔记
		文章提纲 全书总评 读书笔记 C1.初识神经网络 C2.神经网络是如何学习的 C3.有监督学习(运用感知机) C4.无监督学习(自组织映射) Rreferences(参考文献) 全书总评 书本印刷质量 ... 
随机推荐
- 【FF14】工匠配方爬取
			目标:爬取最终幻想14工匠配方到excel表格.(一个装修仔的尊严) 代码: from bs4 import BeautifulSoup import urllib.request import xl ... 
- javaFX的控制台实现
			最近做了个javaFX的工具,想弄个控制台输出信息,准备用TextArea来模拟console,但直接操纵console对象的话不依赖这个项目的地方就无法输出信息到控制台了,至于log,以前弄过一个输 ... 
- Python 中写一个装饰器实现限制频率访问
			1.思路: 首先要在装饰器中确定访问的方法名, 第一次可以访问成功,之后要在规定的时间(变量)之后才可以访问. 初始应该有一个变量为0;访问成功之后把当前的时间赋值给这个变零. 这样再次访问时把当前的 ... 
- CF1153D Serval and Rooted Tree
			题目地址:CF1153D Serval and Rooted Tree 挺好玩儿也挺考思维的一道题 思路:树形DP+贪心 数组 \(d\) 维护这样一个值: 对于一个节点 \(x\) ,它的值最大可以 ... 
- osgearth介绍
			osgEarth为开发osg应用提供了一个地理空间SDK和地形引擎. osgEarth的目标: l 提供基于osg开发3D地理空间应用的支持; l 直接从数据源可视化地形模型和影像变得更加简单: l ... 
- 前端笔记知识点整合之JavaScript(三)关于条件判断语句、循环语句那点事
			一.条件分支语句 条件分支语句,也叫作条件判断语句,就是根据某种条件执行某些语句,不执行某些语句. JS中有三种语法是可以表示条件分支的 1.1 if……else…… 条件分支的主力语法,这个主力 ... 
- NOI2004郁闷的出纳员
			传送门 题目看起来玄乎,但其实只需要一点点小 trick 就可以了. 我们可以用一个全局的 delta 来维护工资的调整记录 对于每一个新加入的员工,先判断是否低于最低工资下限,如果是,直接踢出,不做 ... 
- Ipa 脱壳工具   Clutch dumpdecrypted  使用
			1 Clutch 使用 下载地址 : https://github.com/kjcracks/clutch/releases 下载之后去掉去掉版本号 拖入手机的 /usr/bin/ 目录下 执行 c ... 
- javascript中new Date()存在的兼容性问题
			问题:通过new Date()创建的时间对象在Chrome能正常工作,但在IE浏览器却显示NaN 代码: var time = new Date(date + ' 00:00:00'); //NaN ... 
- Docker动态给容器Container暴露端口
			查看Container的IP地址 docker inspect <container name or id>| grep IPAddress 查看Container的映射的端口 docke ... 
