详细说明:http://tool.oschina.net/apidocs/apidoc?api=jdk-zh
为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。此类的设计使用的是模板方法模式。应该将子类定义为非公共内部帮助器类,可用它们来实现其封闭类的同步属性。类 AbstractQueuedSynchronizer 没有实现任何同步接口。而是定义了诸如 acquireInterruptibly(int)
之类的一些方法,在适当的时候可以通过具体的锁和相关同步器来调用它们,以实现其公共方法。
注:conditionObject今后放到条件锁condition里面说;另外Node这内部类使用的链表结构,需要有一定的数据结构只是,才能看懂源码。node这个类在很多地方都有使用到,我觉得这个是理解透AQS模板的重要切入点之一。sync是AQS所使用的同步器类。
AQS类支持默认的独占 模式和共享 模式之一,或者二者都支持。处于独占模式下时,其他线程试图获取该锁将无法取得成功。在共享模式下,多个线程获取某个锁可能(但不是一定)会获得成功。此类并不“了解”这些不同,除了机械地意识到当在共享模式下成功获取某一锁时,下一个等待线程(如果存在)也必须确定自己是否可以成功获取该锁。处于不同模式下的等待线程可以共享相同的 FIFO 队列。通常,实现子类只支持其中一种模式,但两种模式都可以在(例如)ReadWriteLock
中发挥作用。只支持独占模式或者只支持共享模式的子类不必定义支持未使用模式的方法。
子类可以维护其他状态字段,但只是为了获得同步而只追踪使用 getState()
、setState(int)
和 compareAndSetState(int, int)
方法来操作以原子方式更新的 int 值。为了将此类用作同步器的基础,需要适当地重新定义以下方法,这是通过使用 getState()
、setState(int)
和/或 compareAndSetState(int, int)
方法来检查和/或修改同步状态来实现的:
这里提供一个使用AQS模板,实现一个类似ReentrantLock重入锁的自定义锁,并测试其安全性、重入性。结合实例理解起来,比看jdk的API可能要容易的多。更深层次的原理以及架构设计这些,本节暂不讨论。
一、自定义重入锁
package com.steve.AQS;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* @Author Loujitao
* @Date 2018/7/3
* @Time 14:54
* @Description: 同步锁类得实现lock接口,实现对应的方法
*/
public class MyLock implements Lock{
//这个是内部类的非公有同步器,辅助锁方法的实现
private MyAQS myAQS=new MyAQS();
//如果该锁没有被另一个线程保持,则获取该锁并立即返回,将锁的保持计数设置为 1。
@Override
public void lock() {
//调用的是AQS的方法
myAQS.acquire(1);
}
// 如果当前线程未被中断,则获取锁。
@Override
public void lockInterruptibly() throws InterruptedException {
myAQS.acquireInterruptibly(1);
}
//如果锁是自由的并且被当前线程获取,或者当前线程已经保持该锁,则返回 true;否则返回 false
@Override
public boolean tryLock() {
return myAQS.tryAcquire(1);
}
//如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被 中断,则获取该锁。
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return myAQS.tryAcquireNanos(1,unit.toNanos(time));
}
//如果当前线程是此锁所有者,则将保持计数减 1。如果保持计数现在为 0,则释放该锁
@Override
public void unlock() {
myAQS.release(1);
}
@Override
public Condition newCondition() {
return newCondition();
}
/**
* @Author Loujitao
* @Date 2018/7/3
* @Time 14:25
* @Description: 同步器实现方式非公平的方式
*为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。
* ReentrantLock的内部实现了两套AQS,公平的、非公平。当然你还可以自己在加几套,自己发挥
*/
private class MyAQS extends AbstractQueuedSynchronizer{
//试图在独占模式下获取对象状态。如果成功,则返回 true。
@Override
protected boolean tryAcquire(int arg) {
//同步计数器 此类的设计目标是成为依靠单个原子int值来表示状态的大多数同步器的一个有用基础。
int state=getState();
Thread t=Thread.currentThread();
//当前获取锁的对象为0时
if(state==0){
//使用CAS操作设置state的值,不能直接setState()
if(compareAndSetState(0,arg)){
//这里讲t设置进去,是为了解重入锁的时候做判定依据
setExclusiveOwnerThread(t);
return true;
};
//这里是做重入锁的判定
}else if(getExclusiveOwnerThread()==t){
if(state<-1) throw new RuntimeException("state超出最小值0");
//这里没做CAS操作,而是用的普通的set
setState(state+1);
return true;
}
return false;
}
//试图设置状态来反映独占模式下的一个释放。此方法总是由正在执行释放的线程调用。
@Override
protected boolean tryRelease(int arg) {
//如果不是锁持线程来调用,需跑出异常
if(Thread.currentThread()!=getExclusiveOwnerThread()){
throw new RuntimeException();
}
//因为只有锁持线程能进来,所以下面的设置不需要CAS,普通的set即可
int state=getState()-arg;
boolean flag=false;
//当锁层次减为0时,说明对象释放了。讲状态清空
if(getState()==0){
setState(0);
setExclusiveOwnerThread(null);
flag=true;
}
setState(state);
return flag;
}
// 这两个是共享模式下的实现方法
// @Override
// protected int tryAcquireShared(int arg) {
// return super.tryAcquireShared(arg);
// }
// @Override
// protected boolean tryReleaseShared(int arg) {
// return super.tryReleaseShared(arg);
// }
//因为实现lock接口,需要重写newCondition(),所以才写的,其实没用到
Condition newCodition(){
return new ConditionObject();
}
}
二、测试代码
public class MyReentantLockTest {
private int number=0;
private MyLock myLock=new MyLock();
public static void main(String[] args) {
final MyReentantLockTest mtest=new MyReentantLockTest();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
mtest.getnext();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
mtest.getnext();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
mtest.getnext();
}
}
}).start();
}
//----------------一、多线程下非安全的方法---------------
public void getnext(){
try {
System.out.println(Thread.currentThread().getId()+" "+ number++);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//-------------二、测试安全性问题-------------------
public void saftNext(){
myLock.lock();
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getId()+" "+ number++);
}catch (Exception e){
throw new RuntimeException();
}finally {
myLock.unlock();
}
}
//-------------三、测试重入性问题-------------------
public void printA(){
myLock.lock();
System.out.println("A");
printB();
myLock.unlock();
}
public void printB(){
myLock.lock();
System.out.println("B");
myLock.unlock();
}
}
第一次得到的非安全的结果:
测试安全性问题
public static void main(String[] args) {
final MyReentantLockTest mtest=new MyReentantLockTest();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
mtest.saftNext();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
mtest.saftNext();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
mtest.saftNext();
}
}
}).start();
}
结果显示只有一个线程能进来,所以数据是安全的
测试重入性问题
public static void main(String[] args) {
final MyReentantLockTest mtest=new MyReentantLockTest();
mtest.printA();
}
如果将自定义锁中的重入校验代码去除,重入效果就没有了,如下
可以将本实例对比参照着java.util.concurrent.locks.ReentrantLock这个类来看,这样方便理解。AQS的使用在读写锁、信号量、线程池、CountDownLatch
等中都有使用到。是并发编程中比较重要的一部分,所以一定要好好学习才行。