Java并发框架——AQS阻塞队列管理(二)——自旋锁优化

看Craig, Landin, and Hagersten发明的CLH锁如何优化同步带来的花销,其核心思想是:通过一定手段将所有线程对某一共享变量轮询竞争转化为一个线程队列且队列中的线程各自轮询自己的本地变量。这个转化过程由两个要点,一是构建怎样的队列&如何构建队列,为了保证公平性,构建的将是一个FIFO队列,构建的时候主要通过移动尾部节点tail实现队列的排队,每个想获取锁的线程创建一个新节点并通过CAS原子操作将新节点赋予tail,然后让当前线程轮询前一节点的某个状态位,如图2-5-9-3,如此就成功构建线程排队队列;二是如何释放队列,执行完线程后只需将当前线程对应的节点状态位置为解锁状态,由于下一节点一直在轮询,可获取到锁。
 
图2-5-9-3 CLH锁
CLH锁的核心思想貌似是将众多线程长时间对某资源的竞争,通过有序化这些线程转化为只需对本地变量检测。唯一存在竞争的地方就是在入队列之前对尾节点tail的竞争,但竞争的线程的数量已经少了很多,且比起所有线程直接对某资源竞争的轮询次数也减少了很多,节省了很多CPU缓存同步操作,大大提升系统性能,利用空间换取性能。下面提供一个简单的CLH锁实现代码,lock与unlock两方法提供加锁解锁操作,每次加锁解锁必须将一个CLHNode对象作为参数传入,lock方法的for循环是通过CAS操作将新节点插入队列,而while循环则是检测前驱节点的锁状态位,一旦前驱节点锁状态位允许则结束检测让线程往下执行。解锁操作先判断当前节点是否为尾节点,如是则直接将尾节点置为空,此时说名仅仅只有一条线程在执行,否则将当前节点的锁状态位置为解锁状态。

public class CLHLock {

private static Unsafe unsafe = null;
private static final long valueOffset;
private volatile CLHNode tail;
public class CLHNode {
private boolean isLocked = true;
}

static {
try {
unsafe = getUnsafeInstance();
valueOffset = unsafe.objectFieldOffset(CLHLock.class
.getDeclaredField("tail"));
} catch (Exception ex) {
throw new Error(ex);
}
}

public void lock(CLHNode currentThreadNode) {
CLHNode preNode = null;
for (;;) {
preNode = tail;
if (unsafe.compareAndSwapObject(this, valueOffset, tail,
currentThreadNode))
break;
}
if (preNode != null)
while (preNode.isLocked) {
}
}

public void unlock(CLHNode currentThreadNode) {
  if (!unsafe.compareAndSwapObject(this, valueOffset, currentThreadNode,null))
   currentThreadNode.isLocked = false;
}

private static Unsafe getUnsafeInstance() throws SecurityException,
NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeInstance.setAccessible(true);
return (Unsafe) theUnsafeInstance.get(Unsafe.class);
}

}

点击订购作者书籍《Tomcat内核设计剖析》

时间: 2024-09-18 14:02:34

Java并发框架——AQS阻塞队列管理(二)——自旋锁优化的相关文章

Java并发框架——AQS阻塞队列管理(三)——CLH锁改造

在CLH锁核心思想的影响下,Java并发包的基础框架AQS以CLH锁作为基础而设计,其中主要是考虑到CLH锁更容易实现取消与超时功能.比起原来的CLH锁已经做了很大的改造,主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头结点和尾节点,他们分别指向队列的头和尾,尝试获取锁.入队列.释放锁等实现都与头尾节点相关,并且每个节点都引入前驱节点和后后续节点的引用:在等待机制上由原来的自旋改成阻塞唤醒.如图2-5-9-4,通过前驱后续节点的引用一节节连接起来形成一个链表队列,对于头尾节点的

Java并发框架——AQS阻塞队列管理(一)——自旋锁

我们知道一个线程在尝试获取锁失败后将被阻塞并加入等待队列中,它是一个怎样的队列?又是如何管理此队列?这节聊聊CHL Node FIFO队列. 在谈到CHL Node FIFO队列之前,我们先分析这种队列的几个要素.首先要了解的是自旋锁,所谓自旋锁即是某一线程去尝试获取某个锁时,如果该锁已经被其他线程占用的话,此线程将不断循环检查该锁是否被释放,而不是让此线程挂起或睡眠.它属于为了保证共享资源而提出的一种锁机制,与互斥锁类似,保证了公共资源在任意时刻最多只能由一条线程获取使用,不同的是互斥锁在获取

Java并发框架——AQS超时机制

AQS框架提供的另外一个优秀机制是锁获取超时的支持,当大量线程对某一锁竞争时可能导致某些线程在很长一段时间都获取不了锁,在某些场景下可能希望如果线程在一段时间内不能成功获取锁就取消对该锁的等待以提高性能,这时就需要用到超时机制.在JDK1.5之前还没有juc工具,当时的并发控制职能通过JVM内置的synchronized关键词实现锁,但对一些特殊要求却力不从心,例如超时取消控制.JDK1.5开始引入juc工具完美解决了此问题,而这正得益于并发基础框架AQS提供了超时的支持.为了更精确地保证时间间

Java并发框架——AQS之如何使用AQS构建同步器

AQS的设计思想是通过继承的方式提供一个模板让大家可以很容易根据不同场景实现一个富有个性化的同步器.同步器的核心是要管理一个共享状态,通过对状态的控制即可以实现不同的锁机制.AQS的设计必须考虑把复杂重复且容易出错的队列管理工作统一抽象出来管理,并且要统一控制好流程,而暴露给子类调用的方法主要就是操作共享状态的方法,以此提供对状态的原子性操作.一般子类的同步器中使用AQS提供的getState.setState.compareAndSetState三个方法,前两个为普通的get和set方法,要使

Java并发编程:阻塞队列

我们讨论了同步容器(Hashtable.Vector),也讨论了并发容器(ConcurrentHashMap.CopyOnWriteArrayList),这些工具都为我们编写多线程程序提供了很大的方便.今天我们来讨论另外一类容器:阻塞队列. 在前面我们接触的队列都是非阻塞队列,比如PriorityQueue.LinkedList(LinkedList是双向链表,它实现了Dequeue接口). 使用非阻塞队列的时候有一个很大问题就是:它不会对当前线程产生阻塞,那么在面对类似消费者-生产者的模型时,

Java并发编程之阻塞队列详解_java

1.什么是阻塞队列? 队列是一种数据结构,它有两个基本操作:在队列尾部加入一个元素,从队列头部移除一个元素.阻塞队里与普通的队列的区别在于,普通队列不会对当前线程产生阻塞,在面对类似消费者-生产者模型时,就必须额外的实现同步策略以及线程间唤醒策略.使用阻塞队列,就会对当前线程产生阻塞,当队列是空时,从队列中获取元素的操作将会被阻塞,当队列是满时,往队列里添加元素的操作也会被阻塞. 2.主要的阻塞队列及其方法 java.util.concurrent包下提供主要的几种阻塞队列,主要有以下几个: 1

Java并发框架——同步状态的管理

整个AQS框架核心功能都是围绕着其32位整型属性state进行,一般可以说它表示锁的数量,对同步状态的控制可以实现不同的同步工具,例如闭锁.信号量.栅栏等等.为了保证可见性此变量被声明为volatile,保证每次的原子更新都将及时反映到每条线程上.而对于同步状态的管理可以大体分为两块,一是独占模式的管理,另外是共享模式的管理.通过对这两种模式的灵活变换可以实现多种不同的同步器,如下图,对state的控制可以看成一个管道,管道的大小决定了同时通过的线程,独占模式好比宽度只容许一个线程通过的管道,在

Java并发框架——AQS中断的支持

线程的定义给我们提供了并发执行多个任务的方式,大多数情况下我们会让每个任务都自行执行结束,这样能保证事务的一致性,但是有时我们希望在任务执行中取消任务,使线程停止.在java中要让线程安全.快速.可靠地停下来并不是一件容易的事,java也没有提供任何可靠的方法终止线程的执行.回到第六小节,线程调度策略中有抢占式和协作式两个概念,与之类似的是中断机制也有协作式和抢占式. 历史上Java曾经使用stop()方法终止线程的运行,他们属于抢占式中断.但它引来了很多问题,早已被JDK弃用.调用stop()

Java并发框架——AQS之原子性如何保证?

在研究AQS框架时,会发现这个类很多地方都使用了CAS操作,在并发实现中CAS操作必须具备原子性,而且是硬件级别的原子性,java被隔离在硬件之上,明显力不从心,这时为了能直接操作操作系统层面,肯定要通过用C++编写的native本地方法来扩展实现.JDK提供了一个类来满足CAS的要求,sun.misc.Unsafe,从名字上可以大概知道它用于执行低级别.不安全的操作,AQS就是使用此类完成硬件级别的原子操作. Unsafe是一个很强大的类,它可以分配内存.释放内存.可以定位对象某字段的位置.可