java并发编程实战-15-16章

第十五章 原子变量与非阻塞同步机制

并发包里的工具(如信号量SemaphoreConcurrentLinkedQueue)
synchronized性能更好,伸缩性更好.
原因是使用了原子变量与非阻塞同步机制.

非阻塞的底层: CAS操作. (compare and swap)
没有锁,Lock free,因此更接近完美.
因此没有活跃性问题.

15.1 锁的劣势

性能不行.

15.2 硬件对并发的支持

独占锁: 基于悲观假设,不互斥的话会出事.(悲观锁)
乐观方法: 特殊指令: 包括, Test And Set , Fetch and Increment, CAS, 条件存储.

15.2 CAS 比较并交换

CAS包括三个操作数: 内存位置,旧值,新值.
//类似对应java中mapreplace操作的三个操作数: key,旧值,新值.

CAS操作失败的线程不会挂,会获得失败信息.(同步非阻塞)
可以反复重试. (类似于poll的轮询)

CAS的缺点:
需要调用者自己处理竞争问题.// 重试,回退,放弃.
CAS实现的非阻塞算法通常比用锁写复杂一些.

15.3 原子变量类

包括AtomicInteger等等.提供各种原子操作,如CAS,自增等.
它和一样保证写后读,但还保证内容是原子更新的.(比如两块内存看似一起更新)

  • tip:

    原子变量类不适合作为容器的Key.
    容器的Key一般要是Immutable对象.(如Integer,String)

15.4 具体案例

这一节用CAS实现了链表,栈,原子的域更新器.
需要看着源码学习.

15.4.4 ABA问题

CAS(V,A,B)需要判断V位置是否为A,然后替换为B.
但如果V位置发生变化: A->B->A.
这样其实和我们希望的条件已经不同了,本质上是变化了,只不过值没变,版本号变了.
实例来说,就是链表节点引用没变,引用的值已经变了.

解决方案:
使用AtomicStampedReference(以及AtomicMarkableReference)支持在两个变量上执行原子的条件更新.

  • AtomicStampedReference
    [对象,引用]二元组,在引用上加上版本号.

  • AtomicMarkableReference
    [对象引用,布尔值]二元组,可标记节点为已删除的节点.

第十六章 java内存模型

16.1 概念

为了性能,会进行指令重排,预测执行等等.
内存模型: JMM.
内存模型规定,优化(重排)时应该遵守哪些约束.

  • 串行一致性:
    如果约束够用,执行结果就会和串行一样.

16.1.1 平台的内存模型

平台给出内存模型,约束自己,向外界保证xx条件下会发生什么.
提供接口/指令: 如内存栅栏,提供协调存储的接口.

16.1.3 JAVA内存模型

  1. 程序顺序: 同一个线程里按源码顺序执行;
  2. 监视器锁: 解锁会在加锁之前;(A线程释放了,B线程才能获得锁)
  3. volatile: 写后读. (原子变量也是)
  4. 线程启动: Thread.start之后才会有run等其他操作发生.
  5. 中断规则: (1)A线程中断B线程;(2)B检测到中断. 保证(1)在(2)前面.
  6. 终结器: 构造函数在终结器之前执行完成.
  7. 传递性: 上述规则可以传递.

1是针对单一线程的,2-7都是针对多个线程之间的代码顺序.
口诀: 程锁原线中终传.

16.1.4 借助同步

也就是借助上述7个已有的约束设计代码,达到同步效果.
比如我们平时用的锁,就是利用用了第二条,监视器锁规则.

类库中提供的约束:

  1. 线程安全容器: 写后读.
  2. CountDownLatch: 倒数操作在await返回之前.
  3. Semaphore: PV操作,V释放在P获得之前. (别人释放了,你才能获得)
  4. Future: get返回之前,任务的代码会执行完.
  5. Executor: 提交任务操作,将在执行任务操作之前.
  6. CyclicBarrier: (1)线程A到达栅栏;(2)其他线程离开栅栏. (1)会在(2)前面.换句话说,大家伙会等线程A到了才离开.

16.2 发布

错误示例:

1
2
3
4
5
6
7
8
9
10
@NotThreadSafe
public class A{
private static Resource resource;
public static Resource getInstance(){
if(resource==null){// 显然多个线程会在这里冲突.
resource=new Resource();
}
return resource;
}
}

安全但是慢:

1
2
3
4
5
6
7
8
9
10
@ThreadSafe
public class B{
private static Resource resource;
public synchronized static Resource getInstance(){// 方法级同步
if(resource==null){
resource=new Resource();
}
return resource;
}
}

直接静态初始化:

1
2
3
4
5
6
7
@ThreadSafe
public class C{
private static Resource resource=new Resource();
public static Resource getInstance(){
return resource;
}
}

占位符技术: 延迟静态初始化:

1
2
3
4
5
6
7
8
9
@ThreadSafe
public class D{
private static class ResourceHolder{ // 懒汉,完美.
public static Resource resource=new Resource();
}
public static Resource getInstance(){
return ResourceHolder.resource;
}
}

双检(DCL): 不推荐. 慢,繁琐.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ThreadSafe
public class E{
private volatile static Resource resource; // volatile
public static Resource getInstance(){
if(resource==null){
synchronized(E.class){ // Class对象锁.
if(resource==null){
resource=new Resource();
}
}
}
return resource;
}
}
// 之所以需要volatile,是为了保证resource变量的写操作立即刷新到内存,起码在读之前. (StoreLoad屏障)

初始化安全域:

对象的初始引用不会被重排序到构造函数之前.

  • final:
    final域能够安全发布,通过final域可达的变量\容器的写入操作安全发布.

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
@ThreadSafe
public class SafeStates{
private final Map<String,String>States;
public SafeStates(){
states= new HashMap<>();
states.put("alaska","AK");
//...
}

public String getV(String s){
return states.get(s);
}
}

线程A创建的SafeStates对象,线程B能安全得访问getV方法.

推荐文章