《JAVA并发编程实践》
暂时先不看这个书,说的不太明晰,看《JAVA高并发程序设计》
第一章 介绍
1.2 线程的优点:
1.2.1 使用多处理器
1.2.2 模型的简化
1.2.3 对异步事件的简单处理
1.2.4 用户界面的更佳响应性
事件派发线程
SWING UI操作非线程安全
1.3 线程的风险
1.3.1 安全危险
@NotThreadSafe
这是本书的自定义Annotation之一,表示该类非线程安全
还有@ThreadSafe和Immutable
并发危险:竞争条件(并发竞争同一个资源)
1.3.2 活跃度的危险
活跃度失败(死锁)
1.3.3 性能危险
1.4 线程无处不在
TimerTasks运行在由Timer管理的线程中,不是由应用程序来管理(但是在同一个进程中)
包括:
Timer
JSPs(Servlets and JavaServer Pages)
远程方法调用(Remote Method Invocation)
第二章 线程安全
2.1 什么是线程安全性
线程安全性:一个类是线程安全的,是指在被多个线程访问时,类可以持续进行正确的行为。
2.2 原子性
自增操作是3个离散操作的简写形式:读-改-写
2.2.1 竞争条件
不是所有 数据竞争 都是 竞争条件
检查再运行(一种竞争条件):观察到一些事情为真,然后执行一些动作,事实上观察到执行操作过程中,观察结果可能已经无效,从而引发错误。
2.2.2 示例:惰性初始化中的竞争条件
检查再运行的常见用法是 惰性初始化 ,延迟对象的初始化,直到需要时再初始化,并且只初始化一次
2.2.3 复合操作
原子操作:该操作对于所有的操作,包括它自己,要么全部执行完成,要么一点都没有执行。
“检查再运行”和 读-改-写 操作的全部执行操作是 复合操作
解决方法一:
java.util.concurrent.atomic 包中包括了 原子变量(atomic variable) 类
如:把long替换成AtomicLong类
AtomicReference 是对象引用的线程安全holder类
2.3 解决方法二:锁
需要修改多个变量时,无法保证同时更新。
2.3.1 内部锁
强制原子性的内置锁机制:synchronized块
内部锁在JAVA中扮演互斥锁(mutex)的角色,至多有一个线程可以拥有锁。
该方法过于极端,会导致性能问题
2.3.2 重进入(Reentrancy)
内部锁 是可重进入的,线程获得它自己占有的锁时,请求会成功,请求计数会加1。
重进入意思是请求是基于“每线程”,而不是“每调用”
重进入方便了锁行为的封装,如:父子类同一方法都有锁,那若不可重入,则子类永远在等待。
2.4 用锁来保护状态
每个共享的可变变量都需要由唯一一个确定的锁保护。
很多线程安全类都是这个模式,通过对象的内部锁来保护,如:Vector和同步的容器(collection)类。
对于每一个设计多个变量的不变约束,需要同一个锁保护其所有的变量。
2.5 活跃度和性能
Synchronized代价昂贵,请求排队等候处理,这种web应用的运行方式是 弱并发 的一种表现。
BigInteger 类 不限制数值的长度
synchronized块的大小设计要求,包括安全性、简单性和性能。
通常简单性和性能之间是相互牵制的,实现一个同步策略时,不要过早地为了性能而牺牲简单性(存在潜在安全性问题)。
耗时操作执行期间,不要占有锁。
第三章 共享对象
3.1 可见性
早在对number赋值之前,主线程就已经写入ready并使之对读取线程可见,这是一种“重排序”现象。在单个线程中,只要重排序不会对结果产生影响,那么就不能保证其中的操作 -- 即时重排序对其他线程来说会产生明显的影响。
Thread.yield() 作用 只是告诉线程调度如果有人需要,可以先拿去,我过会再执行
http://blog.csdn.net/partner4java/article/details/7993420
清单3.1
(但是在 JAVA 8 中实际测试,结果永远等于42。。。)
有一个简单的方法来避免这些复杂的问题:只要数据需要被跨进程共享,就进行恰当的同步。
3.1.1 过期数据
NoVisibility 演示了一种没有恰当同步的程序,它能够引起以外的结果:过期数据。
一个线程可能会得到一个变量最新的值,但是也可能得到另一个变量先前写入的过期值。
解决过期数据,仅仅用synchronized同步是不够的。
@GuardedBy(lock)
本书的注解,只有持有了该锁,才能访问被标注的域或方法。
3.1.2 非原子的64位操作
当一个线程没有同步的情况下读取变量,它可能得到一个过期值,但是它至少不是凭空来的的值。这样的保证被称为最低限的安全性(out-of-thin-air safety)。
最低限的安全性应用于所有的变量,除了:没有声明为volatile的64位数值变量(double和long)。对于非volatile的long和double变量,JVM允许将64位的读或写划分为两个32位的操作。如果读和写发生在不同的线程,可能出现一个值的高32位和另一个值的低32位。
除非将它们声明为volatile类型,或者用锁保护起来。
3.1.3 锁和可见性
用内置锁同步不同线程,可以保证在解锁前发生的事情对其是可见的。
当访问一个共享的可变变量时,为什么要求所有线程由同一个锁进行同步 -- 为了保证一个线程对数值进行的写入,其他线程也都可见。
3.1.4 Volatile 变量
Java提供一种同步的弱形式:volatile变量。
volatile变量不会缓存在寄存器或者缓存在对其他处理器隐藏的地方。所以,读一个volatile类型的变量时,总会返回由某一线程所写入的最新值。
一种volatile变量的典型应用:检查状态标记
加锁可以保证可见性与原子性;volatile变量只能保证可见性。
3.2 发布和逸出
发布(publishing)一个对象的意思是使它能够被当前范围之外的代码使用。
一个对象在尚未准备好时就将它发布,这种情况称作逸出(escape)。
通过public String[] getStated() 发布states会出问题
任何一个调用者都能修改它的内容,数组states已经逸出了它所属的范围。
无论如何,被误用的风险还是存在的。一旦一个对象逸出,就要假设存在其他类或线程可能误用它。
内引类的实例包含了对封装实例隐含的引用。
3.2.1 安全构建的实践
如果this引用在构造过程中逸出,这样的对象任务是“没有正确构建的”。
不要让this引用在构造期间逸出。
(不太懂)
this引用几乎总是被新线程共享。
使用工厂方法防止this引用在构造期间逸出
3.3 线程封闭
线程封闭(Thread confinement)技术是实现线程安全的最简单的方式之一。当对象粉笔在一个线程中时,这种做法会自动成为线程安全的,即使被封闭的对象本身并不是。
例子:
Swing把可视化组件和数据模型对象限制到事件分发线程中,实现线程安全。
应用池化的JDBC(JAVA Database Connectivity)Connection对象。在Connection对象被归还前,池不会把它分配给其他线程。这种连接方式隐形地将Connection对象限制在处于请求处理期间的线程。
JAVA语言本身提供了一些机制(本地变量和 ThreadLocal 类)有助于维护线程限制。
3.3.1 Ad-hoc 线程限制
Ad-hoc 线程限制 是指维护线程限制性的任务全部落在实现上的这种情况。因为没有语言特性协助将对象限制目标线程上,所以这种方式是非常容易出错的。
Ad-hoc 指“非正式的”。
鉴于Ad-hoc 线程限制固有的易损性,因此应该有节制地使用它。可能的话,用一种线程限制的强形式(栈限制或者Thread Local)取代它。
(Ad-hoc 线程限制 我理解是指语言没有线程限制手段时,自己实现易出错)
3.3.2 栈限制
在栈限制中,只有通过本地变量才可以触及对象。它们存在于执行线程栈,其他线程无法访问这个栈。
与ad-hoc线程限制比,更易维护,更健壮。
(这就是保证局部变量,不外泄)
3.3.3 ThreadLocal
它允许你将每个线程与有数值的对象关联在一起。ThreadLocal提供了get与set访问器,为每个使用它的线程维护一份单独的拷贝。所以get总是返回由当前执行线程通过set设置的最新值。
每个线程拥有属于自己的Connection,在JDBC中避免Connection的安全风险。
3.4 不可变性
《高性能服务器编程》
《JAVA高并发程序设计》