并发编程笔记九:AQS

九、锁的底层AQS

9.1、AQS的简介

AQS的全称是AbstractQueuedSynchronizer抽象队列同步器。它是一个抽象类,继承它可以自定义各种同步工具。ReentrantLock的底层使用的就是AQS。

9.2、独占模式和共享模式

在某些场景下,我们需要在一个线程执行一段代码时,其他线程就不可以进入这段代码执行,就是说在一个时间点只能有一个线程执行这段代码块,这种同步模式称为独占模式。ReentrantLock锁就是一种独占模式的同步工具。

在某些场景下,我们只能允许指定数量的线程执行执行一段代码,超过指定数量的线程就不能继续进入代码执行,必须等待有执行的线程退出同步代码块,后续线程才能继续进入代码块执行,这种模式称为共享模式。Semaphore就是一种共享模式的同步工具。

AQS对独占模式和共享模式的同步都做了支持,我们如果想要自己写同步工具,只要覆写对应的方法即可。

9.3、AQS中的变量和方法

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    protected AbstractQueuedSynchronizer() { }
    static final class Node {
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;
        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter;
    }
    private transient volatile Node head;
    private transient volatile Node tail;
    private volatile int state;
    protected final int getState() {
        return state;
    }
    protected final void setState(int newState) {
        state = newState;
    }
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
    protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }
    protected boolean tryReleaseShared(int arg) {
        throw new UnsupportedOperationException();
    }
    protected boolean isHeldExclusively() {
        throw new UnsupportedOperationException();
    }
    public class ConditionObject implements Condition, java.io.Serializable {
        private transient Node firstWaiter;
        private transient Node lastWaiter;
        public ConditionObject() { }
        public final void signal() {
            // ...
        }
        public final void signalAll() {
        	// ...
        }
        public final void awaitUninterruptibly() {
        	// ...
        }
        public final void await() throws InterruptedException {
        	// ...
        }
        public final long awaitNanos(long nanosTimeout)
                throws InterruptedException {
        	// ...
        }
        public final boolean awaitUntil(Date deadline)
                throws InterruptedException {
        	// ...
        }
        public final boolean await(long time, TimeUnit unit)
                throws InterruptedException {
        	// ...
        }
    }
}

 

9.3.1、同步状态state

AQS内部维护了一个私有的volatile修饰的int类型变量,他代表着AQS的同步状态。我们可以通过改变这个值来表明是否有线程进入了同步代码块。

例如:

1)在独占模式下,我们在初始化AQS子类对象的时候,state默认为0,我们规定当state为0时,表明没有线程获取同步状态,即没有线程进入同步代码块。当一个线程需要获取同步状态,进入同步代码块的时候,我们可以利用CAS操作,先比较state是否为0,如果为0,则将state更新为1,然后进入同步代码块。若比较的state不为0,则将该线程进行阻塞,加入到同步队列中,直到前一个线程将同步状态释放。

2)在共享模式下,我们在初始化AQS子类对象的时候,将state的值设置为我们规定的线程数量,表明现在还能有指定数量的线程获取同步状态。当一个线程需要获取同步状态时,可以先计算state值减小1后是否大于等于0,如果大于等于0,那么就利用CAS操作将state的值减小1个,如果小于0,说明已经达到线程最大数量了,那么就需要将该线程进行阻塞,加入到同步队列中,直到有一个线程将同步状态释放。

9.3.2、获取和更新同步状态state的方法

CAS中对其子类提供了3个获取和更新state的方法

    protected final int getState() {
        return state;
    }
    protected final void setState(int newState) {
        state = newState;
    }
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

 

1)getState()

获取同步状态state

2)setState(int newState)

更新同步状态state

3)compareAndSetState(int expect, int update)

以CAS的方式更新同步状态state

9.3.3、独占模式同步工具需要覆写的方法

    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
    protected boolean isHeldExclusively() {
        throw new UnsupportedOperationException();
    }

 

1)tryAcquire(int arg)

尝试获取同步状态,获取成功返回true,获取失败返回false

2)tryRelease(int arg)

尝试释放同步状态,释放成功返回true,释放失败返回false

3)isHeldExclusively()

当前线程是否获取了同步状态,是返回true,否返回false。这个方法在Condition中会使用到。

如果我们想写一个独占模式下的同步工具,那么只需要覆写这三个方法即可。至于获取同步状态失败后如何将当前线程阻塞,加入同步队列,前一个线程释放同步状态后,如何恢复这个线程继续执行,AQS都已经帮封装好了,我们无需关心。

9.3.4、共享模式同步工具需要覆写的方法

    protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }
    protected boolean tryReleaseShared(int arg) {
        throw new UnsupportedOperationException();
    }

 

1)tryAcquireShared(int arg)

尝试获取同步状态,获取成功返回大于等于0的值,获取失败返回小于0的值。注意这个返回值是int类型

2)tryReleaseShared(int arg)

尝试释放同步状态,释放成功返回true,释放失败返回false

同样,如果我们想写一个共享模式的同步工具,那么只需要覆写这两个方法即可。

9.4、自定义同步工具代码示例

9.4.1、独占模式同步工具

这是个非常简单的独占模式同步工具,没有考虑重入性,释放锁的时候也没考虑释放是持有锁的线程释放的。(如果考虑重入性和判断释放锁的线程可以使用AQS的父类AbstractOwnableSynchronizer来实现)。

import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class ExclusiveAQSDemo{
	private static final class Sync extends AbstractQueuedSynchronizer{
		@Override
		protected boolean tryAcquire(int arg) {
			return compareAndSetState(0, 1);
		}
		@Override
		protected boolean tryRelease(int arg) {
			setState(0);
			return true;
		}
		@Override
		protected boolean isHeldExclusively() {
			return getState() == 1;
		}
	}
	private final Sync sync;
	public ExclusiveAQSDemo() {
		super();
		this.sync = new Sync();
	}
	public void lock() {
		sync.acquire(1);
	}
	public void unlock() {
		sync.release(1);
	}
}

 

同步工具的测试:

public class ExclusiveAQSDemoTest {
	public static void main(String[] args) throws Exception{
		test(100, 1);
		test(100, 10);
		test(100, 100);
		test(100, 1000);
		test(100, 10000);
		test(100, 100000);
	}
	public static void test(int threadCount, int incrementCount) throws Exception{
		Increment increment = new Increment();
		Thread[] threads = new Thread[threadCount];
		for(int i = 0; i < threadCount; i++) {
			Thread thread = new Thread(() ->  {
				for(int j = 0; j < incrementCount; j++) {
					increment.increment();
				}
			});
			threads[i] = thread;
			thread.start();
		}
		for(int i = 0; i < threadCount; i++) {
			threads[i].join();
		}
		System.out.println(increment.getI());
	}
}
class Increment{
	private int i;
	private ExclusiveAQSDemo lock = new ExclusiveAQSDemo();
	public void increment() {
		lock.lock();
		try {
			i++;
		} finally {
			lock.unlock();
		}
	}
	public Integer getI() {
		return i;
	}
}

 

执行结果:

100
1000
10000
100000
1000000
10000000

 

9.4.2、共享模式同步工具

import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class SharedAQSDemo {
	private static final class Sync extends AbstractQueuedSynchronizer{
		public Sync(int limit) {
			setState(limit);
		}
		@Override
		protected int tryAcquireShared(int arg) {
			while(true) {
				int current = getState();
				int update = current - arg;
				if(update >= 0) {
					if(compareAndSetState(current, update)) {
						return update;
					}
				}else {
					return update;
				}
			}
		}
		@Override
		protected boolean tryReleaseShared(int arg) {
			while(true) {
				int current = getState();
				int update = current + arg;
				if(compareAndSetState(current, update)) {
					return true;
				}
			}
		}
	}
	private final Sync sync;
	public SharedAQSDemo(int limit) {
		super();
		this.sync = new Sync(limit);
	}
	public void lock() {
		sync.acquireShared(1);
	}
	public void unlock() {
		sync.releaseShared(1);
	}
}

 

同步工具的测试:

public class SharedAQSDemoTest {
	public static void main(String[] args) {
		SharedAQSDemo sharedAQSDemo = new SharedAQSDemo(5);
		for(int i = 0; i < 20; i++) {
			new Thread(() -> {
				sharedAQSDemo.lock();
				try {
					System.out.println(Thread.currentThread().getName() + "正在执行!");
					Thread.sleep(3000L);
				} catch (Exception e) {
					e.printStackTrace();
				}finally {
					sharedAQSDemo.unlock();
				}
			}, String.valueOf(i)).start();;
		}
	}
}

 

9.5、同步队列

在线程获取同步状态失败时,会将当前线程封装成Node节点,并放入同步队列中。在AQS的成员位置维护了队列链表的头节点和尾节点。

    static final class Node {
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;
        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter;
    }
    private transient volatile Node head;
    private transient volatile Node tail;

 

(1)head和tail

同步队列的头节点和尾节点

(2)prev和next

上一节点和下一节点,构成双向链表

(3)thread

封装的线程

(4)waitStatus

当前线程的状态字段,取值可以为0,1,-1,-2,-3。

Status field, taking on only the values: SIGNAL: The successor of this node is (or will soon be) blocked (via park), so the current node must unpark its successor when it releases or cancels. To avoid races, acquire methods must first indicate they need a signal, then retry the atomic acquire, and then, on failure, block. CANCELLED: This node is cancelled due to timeout or interrupt. Nodes never leave this state. In particular, a thread with cancelled node never again blocks. CONDITION: This node is currently on a condition queue. It will not be used as a sync queue node until transferred, at which time the status will be set to 0. (Use of this value here has nothing to do with the other uses of the field, but simplifies mechanics.) PROPAGATE: A releaseShared should be propagated to other nodes. This is set (for head node only) in doReleaseShared to ensure propagation continues, even if other operations have since intervened. 0: None of the above The values are arranged numerically to simplify use. Non-negative values mean that a node doesn't need to signal. So, most code doesn't need to check for particular values, just for sign. The field is initialized to 0 for normal sync nodes, and CONDITION for condition nodes. It is modified using CAS (or when possible, unconditional volatile writes).

(5)nextWaiter

等待队列中的下一个节点

9.6、等待队列

9.6.1、Condition接口

Lock接口中的方法newCondition(),返回值是一个Condition接口。

newCondition():返回该锁对象的一个等待队列Condition。

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

 

利用Condition接口就可以实现线程间的wait/notify机制通信,常见的应用就是生产者和消费者模型。

Condition接口代码:

public interface Condition {
    void await() throws InterruptedException;
    void awaitUninterruptibly();
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    boolean awaitUntil(Date deadline) throws InterruptedException;
    void signal();
    void signalAll();
}

 

(1)void await() throws InterruptedException;

当前线程进入等待状态,直到被唤醒(signal)返回或者被中断抛出异常。

(2)void awaitUninterruptibly();

当前线程进入等待状态,不能被中断,直到被唤醒(signal)返回。

(3)long awaitNanos(long nanosTimeout) throws InterruptedException;

当前线程进入等待状态,参数可以指定持续的时间,单位是纳秒,直到被唤醒(signal)返回或被中断抛出异常或者到达了指定的时间。

该方法的返回值表明等待的剩余时长。也就是说,如果这个方法是因为到达指定时间返回,那么返回值就是0,如果是被唤醒返回,那么返回值应该就是大于等于0的值。

(4)boolean await(long time, TimeUnit unit) throws InterruptedException;

当前线程进入等待状态,参数可以指定持续的时间和时间的单位,直到被唤醒(signal)返回或被中断抛出异常或者到达了指定的时间。

如果是达到指定时间返回的,那么返回值是true,如果是被唤醒返回,那么返回值是false。

(5)boolean awaitUntil(Date deadline) throws InterruptedException;

当前线程进入等待状态,直到指定的时间到达或者被唤醒(signal)返回或被中断抛出异常。

返回值同上。

(6)void signal();

唤醒该等待队列下的一个等待线程,线程被唤醒后,会加入同步队列的末端,重新获取同步状态。

(7)void signalAll();

唤醒该等待队列下的所有等待线程,线程被唤醒后,会加入同步队列的末端,重新获取同步状态。

9.6.2、ConditionObject类

ConditionObject是AQS的内部类,它实现了Condition接口。

ReentractLock类的newCondition()方法返回的就是ConditionObject类的对象。

因为ConditionObject类是AQS的内部类,所以ConditionObject对象中就持有AQS子类对象的引用,那么conditionObject对象就可以访问同步队列,将线程放置同步队列中。

    public class ConditionObject implements Condition, java.io.Serializable {
        private transient Node firstWaiter;
        private transient Node lastWaiter;
        public ConditionObject() { }
        public final void signal() { // ... }
        public final void signalAll() { // ... }
        public final void awaitUninterruptibly() { // ... }
        public final void await() throws InterruptedException { // ... }
        public final long awaitNanos(long nanosTimeout)
                throws InterruptedException { // ... }
        public final boolean awaitUntil(Date deadline)
                throws InterruptedException { // ... }
        public final boolean await(long time, TimeUnit unit)
                throws InterruptedException { // ... }
    }

 

9.6.3、等待队列

AQS中等待队列的节点和同步队列的节点是一个类,就是AQS中的内部类NODE

    static final class Node {
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;
        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter;
    }

 

(1)firstWaiter和lastWaiter

在ConditionObject类中维护了两个变量firstWaiter和lastWaiter,表示了这个等待队列的头节点和尾节点。

(2)nextWaiter;

Node节点中的nextWaiter字段表示了等待队列中当前节点的下一个等待节点,形成单向链表。(同步队列是双向链表)。

(3)waitStatus

当waitStatus为-2时,表明这个节点在等待队列中。

9.6.4、代码示例

(1)使用condition的生产者/消费者代码:

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 生产者/消费者模型
 * @author ZTP
 */
public class ProducerAndConsumer {
	// 锁对象
	private Lock lock = new ReentrantLock();
	// 线程等待队列
	private Condition waitQueue = lock.newCondition();
	// 商品队列最大长度
	private static final int GOODSQUEUE_MAX_SIZE = 10;
	// 商品队列
	private Queue<Goods> goodsQueue = new LinkedList<>();
	// 商品编号
	private volatile int goodsNumber = 1;
	/**
	 * 生产者
	 * @author ZTP
	 */
	public class Producer implements Runnable{
		@Override
		public void run() {
			while(true) {
				lock.lock();
				try {
					while (goodsQueue.size() >= GOODSQUEUE_MAX_SIZE) {
						waitQueue.await();
					}
					Goods goods = new Goods(goodsNumber++);
					goodsQueue.add(goods);
					Thread.sleep(1000L);
					System.out.println(Thread.currentThread().getName() + "生产了一个商品,编号为" + goods.getNumber());
					waitQueue.signalAll();
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					lock.unlock();
				}
			}
		}
	}
	/**
	 * 消费者
	 * @author ZTP
	 */
	public class Consumer implements Runnable {
		@Override
		public void run() {
			while(true) {
				lock.lock();
				try {
					while(goodsQueue.size() <= 0) {
						waitQueue.await();
					}
					Goods goods = goodsQueue.remove();
					Thread.sleep(200L);
					System.out.println(Thread.currentThread().getName() + "消费了一个商品,编号为" + goods.getNumber());
					waitQueue.signalAll();
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					lock.unlock();
				}
			}
		}
	}
	/**
	 * 商品
	 * @author ZTP
	 */
	public class Goods {
		// 编号
		private int number;
		public Goods(int number) {
			this.number = number;
		}
		public int getNumber() {
			return number;
		}
		public void setNumber(int number) {
			this.number = number;
		}
	}
}

 

(2)测试类

/**
 * 测试类
 * @author ZTP
 */
public class Test {
	public static void main(String[] args) {
		ProducerAndConsumer pac = new ProducerAndConsumer();
		for(int i = 0; i < 3; i++) {
			Thread producerThread = new Thread(pac.new Producer(), "生产者" + i);
			producerThread.start();
		}
		for(int j = 0; j < 2; j++) {
			Thread consumerThread = new Thread(pac.new Consumer(), "消费者" + j);
			consumerThread.start();
		}
	}
}

 

9.6.5、其他

(1)一个lock对象可以生成多个condition对象,每个对象对应一个等待队列。

(2)Condition的await/signal代码的典型模式

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//等待线程的典型模式
public void conditionAwait() throws InterruptedException {
    lock.lock();    //获取锁
    try {
        while (条件不满足) {
            condition.await();  //使线程处于等待状态
        }
        条件满足后执行的代码;
    } finally {
        lock.unlock();    //释放锁
    }
}
//通知线程的典型模式
public void conditionSignal() throws InterruptedException {
    lock.lock();    //获取锁
    try {
        完成条件;
        condition.signalAll();  //唤醒处于等待状态的线程
    } finally {
        lock.unlock();    //释放锁
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值