笔记:《JAVA并发编程实践》

《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高并发程序设计》

发表评论

邮箱地址不会被公开。 必填项已用*标注