在AQS中,维护着一个表示共享资源加锁情况的变量volatile int state,以及FIFO的线程阻塞队列(称为CLH队列)。

当多个线程并发访问共享资源时,如果共享资源已经被某个线程加入了锁,那么其他线程在访问此共享资源时就会加入CLH队列中。

state表示共享资源被线程加锁的次数。例如:当state的值为1,就表示共享资源被某个线程加了一次锁,当state的值为0时,就表示共享资源没有被加锁,随时可以访问。

AQS类中提供了3种方式state的方法,如

int getState(); 获取state值

void setState(int newState);  直接设置state的值

compareAndSetState(int expect,int update); 使用CAS算法,设置state值

除了加锁次数外,并发的线程在访问共享资源时都会使用以下一种或两种加锁方式。

1.Exclusive:独占方式,同一时间内只能有一个线程访问资源,如ReentrantLock采用的是独占方式。

2.Share:共享式,同一时间内允许多个线程并发访问资源,如CountDownLatch采用的共享方式。

其中Exclusive方式的加锁与解锁,在AQS源码中对应的实现方式,

boolean tryAcquire(int args);  尝试获取资源,如果成功,就给资源加args个锁,并独占资源

boolean tryRelease(int args); 尝试释放资源,如果成功,就释放该资源的args个锁。

boolean isHeldExclusively(); 判断当前线程,是否正在独占共享资源

例如,ReentrantLock的state初始时为0(即共享资源没有被加锁)。当某个线程A调用lock()方法时,lock()会在底层触发tryAcquire(1),把该资源的state修改为1,表示给资源加了一把锁,之后就可以独占使用该资源。之后,其他线程如果再调用lock()方法,就会失败并进入阻塞状态(因为ReentrantLock是独占方式,同一个时间只能被一个线程加锁)。只有在线程A调用unlock()(unlock()会触发tryRelease(1),表示将state的值减1,即把state的值设置为0),也就是说在把资源释放后,其他线程才能访问该资源。简而言之,state为0,表示资源未被加锁,任何线程都可以访问;当state>0时,表示资源已被加了锁,其他线程不能访问。

共享方式的加锁和解锁,对应的方法

int tryAcquireShared(int args); 尝试给资源加args个共享锁,并访问资源;返回值是一个int类型;返回负数:加共享类型失败,当前线程进入阻塞队列。

返回0:当前线程加共享锁成功,但后续其他线程无法再加共享锁。返回正数:当前线程加入共享锁成功,并且后续其他线程也可以再加共享锁。

boolean tryReleaseShared(int args); 尝试释放该资源的arg个锁。

例如:CountDownLatch的构造方法CountDownLatch(int cuont)可以将state的初始值设置为count,并交给count个子线程去并发执行,与此同时主程序会进入阻塞状态。当每个子线程执行完毕后,都会调用countDown(),countDown()会在底层调用tryReleaseShared(1),即state减1.因此,当所有子线程全部执行完毕后,state的值就会变为0。而当state=0时,唤醒主线程,从而实现闭锁功能。

综上所述,在JUC提供的同步类(或者自定义的同步类)中,如果要提供独占的访问方式,就只需要实现AQS的tryAcquire()方法和tryRelease()方法;

如果提供共享的访问方式,就需要实现AQS的tryAcquireShared()和tryReleaseShared()方法;如果既要提供独占方式,又要提供共享方式,只需要将以上4个方法全部实现即可,如JUC中ReentrantReadWriteLock类就同时实现了共享和独占两种方式。