线程安全

2 线程安全

核心就是在访问对象的可变状态时,进行正确的管理。

2.1 对象及状态

对象的状态(也可理解为属性)是指存储在状态变量(例如实例或静态域)中的数据。对象的状态可能包括其他依赖对象的域。例如,某个HashMap的状态不仅存储在HashMap对象本身,还包含集合里面存储的对象。

如何安全地访问可变状态的变量?

A: 不在多个线程之间共享(定义为私有)

B:定义为final

C: 使用同步

 

在线程安全的类当中封装了必要的同步机制,客户端无需重复采用同步措施。

 

2.2 原子性

在java.util.concurrent.atomic包中包含了一些原子变量类,用于实现在数值和对象引用上的原子状态转换。

类摘要

AtomicBoolean

可以用原子方式更新的 boolean 值。

AtomicInteger

可以用原子方式更新的 int 值。

AtomicIntegerArray

可以用原子方式更新其元素的 int 数组。

AtomicIntegerFieldUpdater<T>

基于反射的实用工具,可以对指定类的指定 volatile int 字段进行原子更新。

AtomicLong

可以用原子方式更新的 long 值。

AtomicLongArray

可以用原子方式更新其元素的 long 数组。

AtomicLongFieldUpdater<T>

基于反射的实用工具,可以对指定类的指定 volatile long 字段进行原子更新。

AtomicMarkableReference<V>

AtomicMarkableReference 维护带有标记位的对象引用,可以原子方式对其进行更新。

AtomicReference<V>

可以用原子方式更新的对象引用。

AtomicReferenceArray<E>

可以用原子方式更新其元素的对象引用数组。

AtomicReferenceFieldUpdater<T,V>

基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新。

AtomicStampedReference<V>

AtomicStampedReference 维护带有整数"标志"的对象引用,可以用原子方式对其进行更新。

 

2.3 竞态条件

2.3.1先检查后执行

当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞态条件。最常见的竞态条件类型就是"先检查后执行(Check-Then-Act )"操作,即通过一个可能失效的观测结果来决定下一步的动作。

线程B同时执行getInstanceo A看到instance为空,因而创建一个新的ExpensiveObject实

例。B同样需要判断instance是否为空。此时的instance是否为空,要取决于不可预测的时序,包括线程的调度方式,以及A需要花多长时间来初始化ExpensiveObject并设置instance。如果当B检查时,instance为空,那么在两次调用getInstance时可能会得到不同的结果,即使getInstance通常被认为是返回相同的实例。

 

2.3.2读取—修改—写入

 

2.4 数据竞争

如果在访问共享的非final类型的域时没有采用同步来进行协同,那么就会出现数据竞争。当一个线程写人一个变量而另一个线程接下来读取这个变量,或者读取一个之前由另一个线程写人的变量时,并且在这两个线程之间没有使用同步,那么就可能出现数据竞争。在Java内存模型中,如果在代码中存在数据竞争,那么这段代码就没有确定的语义。并非所有的竞态条件都是数据竞争,同样并非所有的数据竞争都是竟态条件,但二者都可能使并发程序失败。

 

2.5 加锁机制

A: 对于有多个状态(属性)的对象,每一个可变状态都要加锁。

B: 长时间的或者可能无法完成的操作,不要持有锁,一般单独封装在一个线程,提交到池。

2.5.1 内置锁Synchronized

Java提供了一种内置的锁机制来支持原子性。同步代码块(Synchronized Block)。

同步代码块包括两部分:一个作为锁的对象引用,一个作为由这个锁保护的代码块。

 

2.5.2 内置锁重入

重入:如果某个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。

意味着获取锁的操作的粒度是"线程",而不是"调用"。重入的一种实现方法是,为每个锁关联一个获取计数值和一个所有者线程。当计数值为0时,这个锁就被认为是没有被任何线程持有。每个锁对应一个标记(0/1)和持有者线程名。

 

 

posted @ 2018-03-10 10:58  老人与JAVA  阅读(440)  评论(0编辑  收藏  举报