Java AQS(AbstractQueuedSynchronizer)

本文详细解析了Java并发编程中的AbstractQueuedSynchronizer(AQS)机制,包括其核心思想、自定义AQS的步骤以及AQS在ReentrantLock、Condition等并发工具类中的实现。通过分析AQS的源码,展示了锁的获取、释放、线程阻塞与唤醒的过程,同时也探讨了AQS与synchronized的区别和联系。此外,文章还介绍了基于AQS实现的ReentrantReadWriteLock、Semaphore、CountDownLatch和CyclicBarrier等并发工具类的使用场景和原理。通过对AQS的理解,读者可以更好地掌握Java并发编程的底层机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是 AQS?

AQS(AbstractQueuedSynchronizer),位于 java.util.concurrent.locks 包下的一个类,它是 jdk 1.5 提供的一套 基于 CAS 理论 + 阻塞队列(链表)+ LockSupport.park()/LockSupport.unpark() 开发的锁机制框架。是除了 Java 自带的 synchronized 关键字之外的锁机制。

我们常用的 ReentrantLock、CountDownLatch、CyclicBarrier 等并发类均是基于 AQS 来实现的,具体用法是通过继承 AQS 并实现其模版方法,来达到同步状态的管理。

AQS 的功能在使用中可以分为两种:独占锁和共享锁。

  • 独占锁:每次只能有一个线程持有锁(ReentrantLock 就是独占锁)

  • 共享锁:允许多个线程同时获得锁,并发访问共享资源(ReentrantReadWriteLock 的读操作)

AQS 核心思想

AQS 的核心思想是,如果被请求的共享资源 state 空闲(state 是一个标志位),则将当前请求资源的线程设置为有效的工作线程(可以认为线程持锁了),并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用阻塞队列(链表)实现的,即将暂时获取不到锁的线程加入到队列中。

简单理解就是,AQS 内部会使用 CAS 判断 state 是否锁定(假设 state = 0 是没锁,state = 1 是有锁),如果没锁就将当前线程设置为持锁的工作线程,并将 state 修改为 1;如果有锁,就将拿不到锁的线程加入阻塞等待队列,等待锁分配。
在这里插入图片描述

自定义 AQS

AQS 设计是基于模版方法模式,一般的使用方法是:

  • 继承 AbstractQueuedSynchronizer 并重写指定的方法(这些重写方法很简单,无非是对于共享资源 state 的获取和释放)

  • 将 AQS 组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法

public class MyLock implements Lock {
	private final MySync sync = new MySync();
	
	// 加锁(不成功会进入等待队列)
	@Override
	public void lock() {
		// 独占式获取同步状态,如果获取失败则插入同步队列进行等待
		sync.acquire(1); 
	}
	
	// 加锁(可打断)
	@Override
	public void lockInterruptibly() throws InterruptedException {
		// 与 acquire() 相同,但在同步队列中进行等待的时候可以检测中断
		sync.acquireInterruptibly(1);
	}
	
	// 尝试加锁(一次)
	@Override
	public boolean tryLock() {
		// 独占式获取同步状态,试着获取如果成功返回 true,否则返回 false
		return sync.tryAcquire(1);
	}

	// 尝试加锁(带超时)
	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		// 在 acquireInterruptibly() 基础上增加了超时等待功能,在超时时间内没有获得同步状态返回 false
		return sync.tryAcquireNanos(1, unit.toNanos(time));
	}

	// 解锁
	@Override
	public void unlock() {
		// 释放同步状态,该方法会唤醒在同步队列中的下一个节点
		sync.release(1);
	}
	
	// 创建条件变量
	@Override
	public Condition newCondition() {
		return sync.newCondition();
	}
	
	private static class MySync extends AbstractQueuedSynchronizer {
		// 独占式获取同步状态,试着获取如果成功返回 true,否则返回 false
		@Override
		protected boolean tryAcquire(int arg) {
			// 设定 state = 0 时没锁,state = 1 时有锁
			if (compareAndSetState(0, 1)) {
				// 通过 CAS 能修改预期值从 0 到 1,将当前线程设置为工作线程持有锁
				setExclusiveOwnerThread(Thread.currentThread());
				return true;
			}
			return false;
		}
			
		// 独占式释放同步状态,等待中的其他线程此时将有机会获取同步状态 	
		@Override
		protected boolean tryRelease(int arg) {
			setState(0); // state = 0 修改为没锁
			setExclusiveOwnerThread(null); // 持有锁的线程重置
			return true;
		}
	
		// 是否在独占模式下被线程占用
		@Override
		protected boolean isHeldExclusively() {
			return getState() == 1;
		}

		public Condition newCondition() {
			return new ConditionObject();
		}
	}
}

AQS 可以重写的方法:

方法声明方法描述
boolean tryAcquire(int arg)独占式获取同步状态,试着获取如果成功返回 true,否则返回 false
boolean tryRelease(int arg)独占式释放同步状态,等待中的其他线程此时将有机会获取同步状态
int tryAcquireShared(int arg)共享式获取同步状态,返回值 >= 0 代表获取成功,否则失败
boolean tryReleaseShared(int arg)共享式释放同步状态,成功为 true,否则为 false
boolean isHeldExclusively()是否在独占模式下被线程占用

独占锁:

方法声明方法描述
void acquire(int arg)独占式获取同步状态,如果获取失败则插入同步队列进行等待
void acquireInterruptibly(int arg)与 acquire() 相同,但在同步队列中进行等待的时候可以检测中断
boolean tryAcquireNanos(int arg, long nanosTimeout)在 acquireInterruptibly() 基础上增加了超时等待功能,在超时时间内没有获得同步状态返回 false
boolean release(int arg)释放同步状态,该方法会唤醒在同步队列中的下一个节点

共享锁:

方法声明方法描述
void acquireShared(int arg)共享式获取同步状态,与独占式的区别在于同一时刻有多个线程获取同步状态
void acquireSharedInterruptibly(int arg)在 acquireShared() 基础上增加了能响应中断的功能
boolean tryAcquireSharedNanos(int arg, long nanosTimeout)在 acquireSharedInterruptibly() 基础上增加了超时等待功能
boolean releaseShared(int arg)共享式释放同步状态

总结下自定义 AQS 的步骤:

  • 继承 AbstractQueuedSynchronizer,根据我们的需求重写相应的方法,比如要实现一个独占锁就重写 tryAcquire()、tryRelease(),要实现共享锁就重写 tryAcquireShared()、tryReleaseShared()

  • 在我们的组件中调用 AQS 中的模板方法就可以了,这些模板方法会调用我们之前重写的那些方法

我们只需要很小的工作量就可以实现自己的同步组件,重写的那些方法,仅仅是一些简单的对于共享资源 state 的获取和释放操作,至于像获取资源失败、线程需要阻塞之类的操作,AQS 已经帮我们完成了。

编写上面的自定义 AQS 只是为了从框架使用层面上更好的理解它,但一般我们不需要自己自定义(除非有特定业务场景需要),因为 jdk 已经提供了 ReentrantLock 等针对业务场景的 AQS 实现,我们可以直接使用它

ReentrantLock 是怎么实现锁机制的?或者说 AQS 是怎么运行的?

AQS 源码分析

ReentrantLock 是基于 AQS 实现的,所以会通过它具体分析 AQS 的运行机制。先简单了解下 ReentrantLock 的整体框架,方便后面讲解原理时知道它们是干什么的:

在这里插入图片描述
ReentrantLock 内部提供了一个 Sync 继承了 AQS,并且提供了两个子类 NoFairSync 和 FairSync 分别是非公平锁和公平锁的实现类。

AQS 中 ConditionObject 是条件变量,Node 是队列。

CAS 尝试获得锁及锁重入处理

ReentrantLock.java

public class ReentrantLock implements Lock, Serializable {
	private final Sync sync;
	
	public ReentrantLock() {
		sync = new NofairSync(); // 默认非公平锁,非公平锁其实就是让线程抢锁
	}
	
	public ReentrantLock(boolean fair) {
		sync = fair ? new NoFairSync() : new NoFairSync();
	}

	public void lock() {
		// NonfairSync.lock()
		sync.lock();
	}

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        final void lock() {
        	// CAS,预期值为 0,新值为 1,如果能修改为 1,设置当前线程持锁
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1); // 调用 AbstractQueuedSynchronizer.acquire()
        }

		// AbstractQueuedSynchronizer.acquire(1) 调用时会调用该方法
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

	abstract static class Sync extends AbstractQueuedSynchronizer {
		private static final long serialVersionUID = -5179523762034025860L;
	
		abstract void lock();

        final boolean nonfairTryAcquire(int acquires) { // acquires = 1
            final Thread current = Thread.currentThread(); // 当前线程
            int c = getState(); // 获取锁状态
            if (c == 0) {
            	// 在 NonfairSync.lock() 已经尝试过 CAS 失败,所以这里是重试操作
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 锁重入操作,本来抢到锁的线程又来抢占
            // 相当于 lock.lock() 调用了两遍
            // lock.lock();
            // lock.lock();
            // 该操作主要规避死锁问题
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc); // 修改 state 避免死锁
                return true;
            }
            return false;
        }
        ...
	}
}

AbstractQueuedSynchronizer.java

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
	...
	
	// 调用 tryAcquire(),实际调用 NofairSync.tryAcquire(1)
	public final void acquire(int arg) {
		if (!tryAcquire(arg) &&
			acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
			selfInterrupt();
	}
	...
}

上面的代码是在尝试持锁及锁重入的处理,可以用下图理解上面的逻辑:

在这里插入图片描述
调用 lock() 时,此时 Thread-0 持有锁 state = 1,Thread-1 尝试 CAS 获取锁失败(尝试两次获取锁,第一次是调用 lock() 时,第二次是调用 acquire() 间接调用了 tryAcquire() 又尝试了一次)。

线程入队处理

AbstractQueuedSynchronizer.java

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
	...
	
	public final void acquire(int arg) {
		if (!tryAcquire(arg) &&
			acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
			selfInterrupt();
	}

    private Node addWaiter(Node mode) {
    	// 将当前线程转换为节点
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
			node.prev = pred;
			if (compareAndSetTail(pred, node)) {
				pred.next = node;
				return node;
			}
		}
		enq(node);
		return node;
    }

	// 自旋操作,模拟线程锁抢占,用的数据结构是链表
	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;
				}
			}
		}
	}
}

我们上面说到 ReentrantLock 默认是非公平锁,也就是线程是会抢锁的,那怎么抢?enq() 就是模拟抢锁的操作。

enq() 通过自旋的方式(链表节点不断的轮换变动实现随机的方式)让线程在无限循环中不断的轮换位置,最终当持有锁的线程 Thread-0 释放锁时,拿到锁的线程就是随机的了。

在这里插入图片描述

调用 addWaiter(),将 Thread-1 转换为链表节点构造 Node 队列。图中黄色三角表示该 Node 的 waitStatus 状态,waitStatus = 0 表示默认正常状态,可以去抢占锁 state 修改为 1;其中第一个 Node 称为 Dummy 哨兵(链表中也有称为哑节点),用来占位,并不关联线程。

LockSupport.park() 阻塞

AbstractQueuedSynchronizer.java

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
	...
	
	public final void acquire(int arg) {
		if (!tryAcquire(arg) &&
			acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
			selfInterrupt();
	}

    final boolean acquireQueued(final Node node, int arg) {
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                // 两次拿锁都失败后,还在尝试拿锁调用 tryAcquire(1)
                if (p == head && tryAcquire(arg)) { 
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt()) // 最主要看这句代码
                    interrupted = true;
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    	// Node.SIGNAL = -1
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev; // 置换
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            pred.compareAndSetWaitStatus(ws, Node.SIGNAL); // waitStatus = -1
        }
        return false;
    }

	private final boolean parkAndCheckInterrupt() {
		LockSupport.park(this); // 实际做阻塞的操作
		return Thread.interrupted();
	}
}

在这里插入图片描述

  • acquireQueued() 会在一个死循环中不断尝试获得锁,还是失败后调用 LockSupport.park() 阻塞

  • 如果自己是紧邻着 head(排第二位,图中的 Thread-1),那么再次 tryAcquire() 取锁,当然这时 state 仍为 1 因为 Thread-0 还持锁,Thread-1 获取锁失败

  • 进入 shouldParkAfterFailedAcquire() 逻辑,将前驱 node (Dummy)的 waitStatus 改为 -1,这次返回 false(-1标识:有责任去唤醒后继节点,即 Dummy 有责任去唤醒 Thread-1)

在这里插入图片描述

  • shouldParkAfterFailedAcquire() 执行完毕回到 acquireQueued() 再次 tryAcquire() 尝试获取锁,当然这时 state 仍为 1 失败

  • 当再次进入 shouldParkAfterFailedAcquire() 时,这时因为其 node 的 waitStatus 已经是 -1,这次返回 true

  • 进入 parkAndCheckInterrupt(),Thread-1 进入阻塞状态(灰色表示阻塞)

多个线程竞争失败如下:

在这里插入图片描述

LockSupport.unpark() 唤醒

ReentrantLock.java

public class ReentrantLock implements Lock, Serializable {
	private final Sync sync;
	
	public void unlock() {
		sync.release(1);
	}	

	abstract static class Sync extends AbstractQueuedSynchronizer {
       protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null); // 持有锁线程重置 
            }
            setState(c); // 重置共享资源 state
            return free;
        }		
	}
}

AbstractQueuedSynchronizer.java

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
	...

   public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h); 
            return true;
        }
        return false;
    }

    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            node.compareAndSetWaitStatus(ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next; // 取下一个节点(自旋操作后,队列拿下一个线程是随机的)
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node p = tail; p != node && p != null; p = p.prev)
                if (p.waitStatus <= 0)
                    s = p;
        }
        if (s != null)
            LockSupport.unpark(s.thread); // 从队列中唤醒下一个节点的线程可以开始抢锁了
    }
}

上面的代码最主要的就是调用了 LockSupport.unpark() 唤醒队列中的线程可以抢锁了。

AQS 运行机制总结

ReentrantLock 的实现主要的几个方法:

加锁 lock():

  • tryAcquire():尝试加锁与锁重入处理

  • addWaiter():用来入队,往最后一个节点插入

  • acquireQueued():将 addWaiter() 的节点进行 park 等待唤醒 (park 等同于 synchronized 锁机制的 wait() 等待)

解锁 unlock():

  • release():unpark 唤醒队列中的线程(unpark 等同于 synchronized 锁机制的 notify() 唤醒)

根据上面分析的流程简单分析下 AQS 的基本实现:

AQS 维护一个共享资源 state,通过内置的队列来完成获取资源线程的排队工作(内置的同步队列称为 CLH 阻塞队列,实际上它是一个链表)。该队列由一个一个的 Node 节点组成,每个 Node 节点维护一个 prev 引用和 next 引用,分别指向自己的前驱和后继节点。AQS 维护两个指针,分别指向队列头部 head 和尾部 tail。

当线程取锁失败(比如 tryAcquire() 试图设置 state 状态失败),会被构造成一个节点加入 CLH 队列中,同时当前线程会被阻塞在队列中(通过 LockSupport.park() 实现阻塞),当持有同步状态的线程释放同步状态时,会唤醒后继节点,然后此节点线程继续加入到对同步状态的争夺中。

整体流程如下图(若图片无法打开,可以点击该链接 AQS 运行机制):
在这里插入图片描述

Condition 条件变量

Condition 是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件,只有满足条件时,线程才会被唤醒。

当我们使用 ReentrantLock 时,一般也会创建条件变量:

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

try {
	lock.lock();
	condition.await(); // 阻塞
} finally {
	lock.unlock();
}

try {
	lock.lock();
	condition.signal(); // 唤醒
} finally {
	lock.unlock();
}

Condition 作为条件,主要的操作就是条件不满足时进入阻塞等待。那它是怎么阻塞的?又是什么时候知道要释放?

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
	...

    static final class Node {
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter;                        	
	}

	public class ConditionObject implements Condition, java.io.Serializable {
        private transient Node firstWaiter;
        private transient Node lastWaiter;		
        		
	    public final void await() throws InterruptedException {
	        if (Thread.interrupted())
	            throw new InterruptedException();
	        Node node = addConditionWaiter();
	        int savedState = fullyRelease(node);
	        int interruptMode = 0;
	        while (!isOnSyncQueue(node)) {
	            LockSupport.park(this);
	            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
	                break;
	        }
	        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
	            interruptMode = REINTERRUPT;
	        if (node.nextWaiter != null) // clean up if cancelled
	            unlinkCancelledWaiters();
	        if (interruptMode != 0)
	            reportInterruptAfterWait(interruptMode);
	    }
	
	    private Node addConditionWaiter() {
	        Node t = lastWaiter;
	        // If lastWaiter is cancelled, clean out.
	        if (t != null && t.waitStatus != Node.CONDITION) {
	            unlinkCancelledWaiters();
	            t = lastWaiter;
	        }
	
	        Node node = new Node(Node.CONDITION); // 修改 watiStatus = -2
	
	        if (t == null)
	            firstWaiter = node;
	        else
	            t.nextWaiter = node; // 只操作 nextWaiter 
	        lastWaiter = node;
	        return node;
	    }
	}

    final int fullyRelease(Node node) {
        try {
            int savedState = getState();
            if (release(savedState))
                return savedState;
            throw new IllegalMonitorStateException();
        } catch (Throwable t) {
            node.waitStatus = Node.CANCELLED;
            throw t;
        }
    }

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h); // LockSupport.unpark()
            return true;
        }
        return false;
    }
}

可以看到,ConditionObject 继承自 Condition,内部是在操作 Node。每个 ConditionObject 其实就对应着一个等待队列。

Node 从物理结构上是双链表(持有 prev 和 next 变量),但从在 Condition 的应用角度来讲它是单链表,因为 Node 只操作 nextWaiter 变量专门用于做等待处理,prev 和 next 变量是用于做类似 synchronized 机制的 entryList 队列容器。

Condition 条件变量内部还是用的 park/unpark 实际处理阻塞和唤醒。

await() 的流程可以用下图说明:

在这里插入图片描述

图中一开始 Thread-0 持有锁,调用 await() 时会进入 ConditionObject 的 addConditionWaiter() 流程创建新的 Node 并且状态为 -2(Node.CONDITION),关联 Thread-0,加入等待队列尾部。

wait()/notify()、await()/signal()、park/unpark 对比

  • wait()/notify():依托于 synchronized,基于 JVM 底层对于阻塞的实现,Monitor 使用 waitSet 作为等待机制,线程随机被唤醒其实就是用的 Set 提取算法,notifyAll() 会唤醒所有

  • await()/signal():依赖于 ReentrantLock 条件变量,已经用条件变量与 AQS 体系作为唤醒机制,本质上底层实现是 park/unpark 实现阻塞

  • park/unpark:以 thread 为操作对象,操作更精准,可以准确地唤醒某一个线程(notify() 随机唤醒一个线程,notifyAll() 唤醒所有),增加了灵活性

其实 park/unpark 的设计原理核心是 “许可”,park 是等待一个许可,unpark 是为某线程提供一个许可。但这个许可是不能叠加的,许可是一次性的。

比如线程 B 连续调用了三次 unpark,当线程 A 调用 park 就使用掉这个许可,如果线程 A 再次调用 park,则进入等待状态。

AQS 和 synchronized 锁机制类比

虽然 synchronized 和 AQS 是两种不同的锁机制实现方案,不过为了方便记忆,是可以将 AQS 和 synchronized 的实现方式做类比的。关于 synchronized 锁机制详细可以查看另一篇文章:Java synchronized 与 CAS

在这里插入图片描述

上图是重量级锁 Monitor 的实现原理图,Monitor 分别由 Owner、entryList 和 waitSet 组成,Object 是锁,Owner 就是持有锁的工作线程,entryList 是等待队列,waitSet 是等待唤醒的线程。

在 AQS 中同样也有可以对应的地方,compareAnSetState() 自己控制锁状态,setExclusiveOwnerThread() 就是 Monitor 的 Owner,等待队列(其实是一个链表,Node 节点持有 prev 和 next 引用)就是 Monitor 的 entryList,Condition 就是 Monitor 的 waitSet。

AQS 的其他业务场景

ReentrantReadWriteLock

ReentrantReadWriteLock 读写锁指一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程。

读写锁有两个静态内部类:ReadLock 读锁和 WriteLock 写锁,这两个锁实现了 Lock 接口。

  • 写锁的获取和释放:写锁是支持重入的排他锁,如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取读锁时,读锁已经被获取或者该线程不是已获取写锁的线程,则当前线程进入等待状态。读写锁确保写锁的操作对读锁可见。写锁释放每次减少写状态,当写状态为 0 时表示写锁已经释放

  • 读锁的获取和释放:读锁时支持重入的共享锁(共享锁为 shared 节点,对于 shared 节点会进行一连串的唤醒,直到遇到一个读节点),它能够被多个线程同时获取,在没有其他写线程访问(写状态为 0)时,读锁总是能够被成功地获取,而所做的也只是增加读状态(线程安全)。如果当前线程已经获取了读锁,则增加读状态。如果当前线程在获取读锁时,写锁已经被获取,则进入等待状态。

public static void main(String[] args) {
	DataContainer container = new DataContainer();
	new Thread(() -> {
		container.read();
	}, "t1").start();
	
	new Thread(() -> {
		// container.read();
		container.write();
	}).start();
}

static class DataContainer {
	private Object data;
	private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
	private ReentrantReadWriteLock.ReadLock r = rw.readLock();
	private ReentrantReadWriteLock.WriteLock w = rw.writeLock();

	public Object read() {
		System.out.println("获取读锁...");
		r.lock();
		try {
			System.out.println("读取");
			Thread.sleep(1000);
			return data;
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			System.out.println("释放读锁...");
			r.unlock();
		}
		return null;
	}

	public void write() {
		System.out.println("获取写锁...");
		w.lock();
		try {
			System.out.println("写入");
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			System.out.println("释放写锁...");
			w.unlock();
		}
	}
}

// 两个线程都在读
执行结果:
获取读锁... // 线程 1 拿到锁
读取
获取读锁... // 线程 2 也拿到锁
读取
释放写锁...
释放写锁...

// 一个线程读一个线程写
执行结果:
获取读锁...
读取
获取写锁...
释放读锁... // 拿到写锁时,写入前会先释放读锁
写入
释放写锁...

读写锁适合缓存的场景:

public class CacheDemo {
	Object data;
	// 是否有效,如果失效需要重新计算 data
	volatile boolean cacheValid;
	final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

	void processCacheData() {
		rwl.readLock().lock();
		if (!cacheValid) {
			// 获取写锁前必须先释放读锁
			rwl.readLock().unlock();
			rwl.writeLock().lock();
			
			try {
				// 判断是否有其他线程已经获取了写锁、更新了缓存,避免重复更新
				if (!cacheValid) {
					data = null;
					cacheValid = true;
				}
				// 降级为读锁,释放写锁,这样能够让其他线程读取缓存
				rwl.readLock().lock();
			} finally {
				// 自己用完数据,释放写锁
				rwl.writeLock().unlock();
			}
		}
	}
}

Semaphore

Semaphore 也就是我们常说的信号灯,Semaphore 可以控制同时访问的线程个数,通过 acquire 获取一个许可,如果没有就等待,通过 release 释放一个许可。它的作用有点类似于限流的作用。

public static void main(String[] args) {
	// 创建一个无界线程池
	ExecutorService exec = Executors.newCachedThreadPool();
	// 配置只能3个线程同时访问
	final Semaphore semaphore = new Semaphore(3);
	// 模拟5个客户端访问
	for (int i = 0; i < 5; i++) {
		int num = i;
		Runnable task = (() -> {
			try {
				// 获得许可
				semaphore.acquire();
				System.out.println("获得许可:" + num);
				// 随机休眠
				TimeUnit.SECONDS.sleep((int)(Math.random() * 10 + 1));
				// 访问完后,释放许可
				semaphore.release();
				System.out.println("----当前还有多少个许可:" + semaphore.availablePermits());
			} catch (InterruptedException e) {
				e.printStackTrace();	
			}
		});
		exec.execute(task);
	}
	// 退出线程池
	exec.shutdown();
}

执行结果:
获得许可:1
获得许可:2
获得许可:0
----当前还有多少个许可:1
获得许可:3
----当前还有多少个许可:1
获得许可:4
----当前还有多少个许可:1
----当前还有多少个许可:2
----当前还有多少个许可:3

CountDownLatch

CoutDownLatch 是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完毕再执行。

从命名可以解读到是一个递减的计数器,类似于我们倒计时的概念。

public static void main(String[] args) {
	CountDownLatch latch = new CountDownLatch(3); // 设定计数器数量
    ExecutorService service = Executors.newFixedThreadPool(4);
      service.submit(() -> {
          System.out.println("t1 begin...");
          try {
              Thread.sleep(1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          latch.countDown(); // 计数器-1
          System.out.println("t1 end..." + latch.getCount());
      }, "t1");
      service.submit(() -> {
          System.out.println("t2 begin...");
          try {
              Thread.sleep(2000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          latch.countDown(); // 计数器-1
          System.out.println("t2 end..." + latch.getCount());
      }, "t2");
      service.submit(() -> {
          System.out.println("t3 begin...");
          try {
              Thread.sleep(3000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          latch.countDown(); // 计数器-1
          System.out.println("t3 end..." + latch.getCount());
      }, "t3");
      service.submit(() -> {
          try {
              System.out.println("t4 waiting...");
              latch.await(); // 等待其他线程执行完后再执行
              System.out.println("t4 wait end...");
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      }, "t4");
}

执行结果:
t1 begin...
t2 begin...
t3 begin...
t4 waiting...
t1 end...2
t2 end...1
t3 end...0
t4 wait end...

在这里插入图片描述

CyclicBarrier

CyclicBarrier 字面意思是可循环的屏障。它主要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会打开,所有被屏障拦截的线程才会继续工作。

public static void main(String[] args) {
	ExecutorService service = Executors.newFixedThreadPool(5);
    CyclicBarrier barrier = new CyclicBarrier(4, () -> {
        System.out.println("task1, task2, task3, task4 finish...");
    });
    service.submit(() -> {
        System.out.println("task1 begin...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            barrier.await();
            System.out.println("task1 execute...");
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    });
    service.submit(() -> {
        System.out.println("task2 begin...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            barrier.await();
            System.out.println("task2 execute...");
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    });
    service.submit(() -> {
        System.out.println("task3 begin...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            barrier.await();
            System.out.println("task3 execute...");
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    });
    service.submit(() -> {
        System.out.println("task4 begin...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            barrier.await();
            System.out.println("task4 execute...");
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    });
}

执行结果:
task1 begin...
task2 begin...
task3 begin...
task4 begin...
task1, task2, task3, task4 finish...
task2 execute...
task4 execute...
task3 execute...
task1 execute...
...

在这里插入图片描述

AQS 和 synchronized 的选择

或许你会有疑问:AQS 和 synchronized 用哪种好?

其实 AQS 和 synchronized 没有可比性

AQS 的底层原理应用的是 CAS 原子变量 + 队列 + park/unpark 的支持来添加业务去具体实现锁的定义,它是并发工具。而 synchronized 是基于 JVM 的锁机制实现。

所以应该根据具体的业务场景具体选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值