JUC AbstractQueuedSynchronizer

队列同步器 AbstractQueuedSynchronizer(AQS),是用来构建锁或者其他同步组件的基础框架。它使用一个int变量来表示同步状态,通过CAS操作对同步状态进行修改,确保状态的改变是安全的。通过内置的FIFO(First In First Out)队列来完成资源获取线程的排队工作。

AQS同步与synchronized同步是采用的两种不同的机制:

synchronized在编译后,会在同步块的前后分别添加monitorenter和monitorexit两个字节码指令,这两个指令需要关联一个监视对象,当线程执行monitorenter指令时,需要首先获得获得监视对象的锁,即进入同步块的凭证,才能进入同步块,当线程离开同步块时,执行monitorexit指令,释放对象锁。

AQS同步中,使用一个int类型的变量state来表示当前同步块的状态。以独占式同步为例,state的有效值为0和1,其中0表示当前同步块中没有线程,1表示同步块中已经有线程在执行。当线程要进入同步块时,需要首先判断state的值是否为0,假设为0,会尝试将state修改为1,只有修改成功了之后,线程才可以进入同步块,并通过CAS操作确保同一时刻只有一个线程操作成功。当线程离开同步块时,会修改state的值为0,并唤醒等待的线程。所以在AQS同步中,线程获得锁实际上是指线程成功修改了状态变量state,而释放锁就是是指线程将状态变量置为了可修改的状态,以便其他线程可以再次尝试修改状态变量

JUC FutureTask

前面整理线程池时说过Executor是执行任务的抽象,而Runnable是任务的抽象。不过,线程池在实际提交任务时,会先将任务包装成一个FutureTask,以实现Future的语义,即异步获取任务结果的能力FutureTask内部实际依赖的是一个Callable,并将任务的执行过程和结果获取委托给它,如果提交的是Runnable,那么也可以通过适配器RunnableAdapter<V>将其适配成一个Callable

java.util.concurrent.Executors
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}

static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}

相关类图可以如下所示:

JUC ThreadPoolExecutor

Executor是java中任务执行的主要抽象,虽然它是一个简单的接口,但却为灵活而强大的异步任务执行框架提供了基础,它提供了一种标准的方法将任务的提交过程与执行过程解耦开来。

java.util.concurrent.Executor
1
2
3
public interface Executor{
void excute(Runnable command);
}

JUC ThreadLocal

ThreadLocal从其命名上就可以知道其意图是创建线程本地变量,就是希望同一个变量在不同的线程中拥有各自的值并且互不影响,其非常适合用来作为线程上下文变量,比如在一些连接池或者事务的场景中。

其思路是让每个Thread都持有一个私有的ThreadLocalMap,然后使用共享的key来保存值,而这个key就是共享的ThreadLocal实例,因此每个ThreadLocal也就对应一个本地变量。。但是,如果这个本地变量本身就是一个线程共享的对象,那么就算使用ThreadLocal也不是线程安全的。另外,设计者将具体的map操作都封装在了ThreadLocal中,然后提供统一的get/set接口,好让开发更加简洁方便。

Java Map

java容器中一般最常用的就是List、Set、Map,List的实现前面已经介绍过,本文主要讨论下Map的几个实现,至于Set则比较简单,它们仅仅是依赖Map做了层封装而已。

在讨论之前,还是先看下Map的继承结构

Java List

根据实现的接口可以将java容器分为Collection和Map两部分,其中还可以按照是否线程安全来继续划分,大部分线程安全的实现在JDK1.5之后新增的concurrent包中。

本文先讨论Collection中比较基础的两个List实现ArrayListLinkedList,前者通过数组,后者通过链表。数组的好处在于有位置索引,可以按下标任意访问,并且由于数组在内存上的连续性,因此访问会更快,但前提是元素必须排列紧凑, 这样在进行插入删除时就免不了要进行移动,而且由于数组长度不可变,在添加元素时可能需要扩容和复制处理。链表可以弥补数组在插入删除上的缺陷,但代价是牺牲查询的效率,尤其是按位置索引访问时,它只能选择从头或者从尾开始依次遍历。

在讨论之前先整体看下Collection的体系结构,可以看到其实有很多的实现类,它们分别用来应付各种不同的场景