jdk源码学习之JUC总结(1)

JUC是jdk源码中的java.util.concurrent包的简称,JUC包是java实现高并发的基础,里面包含了很多java高并发的解决方案,大概分为Excutor架构,并发容器,原子类,锁和其他并发工具。Excutor架构提供了多种线程池和阻塞队列,并发容器包括ConcurrentHashMap等线程安全的数据结构,原子类可以实现基本类型赋值的原子操作,锁的实现基础是AQS。

基于JUC的并发工具基本可以满足大部分的并发需求,线程的创建可以使用线程池,线程之间的同步可以使用锁,线程安全的并发容器提供了线程之间可以共享的数据结构,原子类为一些基本数据类型在多线程之间的共享提供了安全的赋值和更新操作,如果要实现自己的同步器,可以继承AQS类。

一、Java并发基础

1.AQS原理和分析

AQS的核心是Node和Queue,Node保存了线程的状态和其他信息,Queue是Node的队列。AQS中的大部分更新和赋值操作也是基于CAS的,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

private Node enq(final Node node) {

for (;;) {

Node t = tail;

if (t == null) { // Must initialize

if (compareAndSetHead(new Node()))

tail = head;

} else {

node.prev = t;

if (compareAndSetTail(t, node)) {

t.next = node;

return t;

}

}

}

}

AQS是典型的模板方法模式的运用,只提供了获取锁和释放锁的模板,如果要定义自己的同步器重写这些方法就可以了。ReentrantLock就是使用AQS实现的线程之间同步的功能。

2.CAS原理和分析

CAS是compare and swap的简称,是比较并交换的意思。是为了解决为一个变量赋值的原子性操作的问题,核心思想就是用预期值和旧值比较如果相等则把新值赋值给旧值,如果不相等就继续比较直到赋值成功为止。CAS的思想是基于一种乐观的并发策略,就是直接进行赋值操作,如果成功的话那效率是很高的,它省去了阻塞这种线程切换的开销,适用于竞争不是很激烈的并发场景。而像加锁操作则是悲观的并发策略,无论有没有竞争,先加锁再操作,这在竞争激烈的情况下是一种高效合理的策略。

二、Java内存模型

1.synchronize

synchronize是最常用的线程同步机制,被synchronize修饰的方法会在指令层面加入monitorenter和monitorout操作,使得其中的代码只能由一个线程执行。而synchronize(lock){}相当于锁住了lock对象,保证了只能有一个线程访问lock对象。

synchronize被认为是重量级锁,而在jdk1.6的优化之后,synchronize的效率提高了很多,主要优化有锁消除,锁粗化,锁自适应自旋,轻量级锁等。

2.volatile

被volatile修饰的变量具有内存可见性,java的内存模型有工作内存和主内存之分,工作内存是线程私有的内存,主内存所有线程都可以访问,volatile关键字保证了对其修饰的变量的写可以立刻刷到主内存,这样其他线程读到的值就是最新的了。同时,volatile修饰的变量还有禁止指令重排序的功能,在赋值操作之后会添加内存屏障的指令,使得其他CPU的内存值更新之后才能进行读操作。

3.线程通信

对于使用synchronize进行同步的线程,采用wait()和notifyAll()进行通信。
使用ReentrantLock同步的线程,采用condition对象的await()和signalAll()方法,而且一个ReentrantLock可以有多个condition对象对应多个信号量。

4.happens-before

happens-before规则保证了符合这个规则的代码的执行顺序,常用的规则有:

  1. 保证单线程中的顺序符合程序代码的顺序。
  2. lock()操作在unlock()之前。
  3. volatile写在读之前。
  4. 若A操作在B之前,B在C之前,则A在
  5. 还有其他的几个就不列举了,这几个规则的主要作用应该是用来判定指令重排序的,只要不符合这几个规则,那么就可以认为编译后的指令是没有顺序的。