参考书籍:《Java并发编程的艺术》
设计目的
设计一个同步工具,该工具在同一时刻,只允许至多两个线程同时访问,超过两个线程访问将被阻塞,将这个同步工具命名为TwinsLock。
设计方案
首先,确定访问模式。分为独占式访问和共享式访问,由于TwinsLock需要同一时刻至多两个线程能访问,因此属于共享式访问。因此需要使用同步器提供的acquireShared(int args)方法和Shared相关的方法,这就要求重写AQS的tryAcquireShared(int args)方法和tryReleaseShared(int args)方法,这样才能保证同步器的共享式同步状态的获取与释放方法得以执行。
其次,定义资源数。TwinsLock同一时刻最多允许两个线程的同时访问,表名同步资源数为2,这样可以设置status为2,当一个线程获取成功,status减一,一个线程释放同步状态,status加一,且status合法取值为0、1、2。在同步状态变更时,需要使用compareAndSet(int except, int update)方法做原子性保证。
最后,定义自定义同步器。自定义同步组件通过组合自定义同步器来完成同步功能,一般情况下自定义同步器会被定义为自定义同步组件的内部类。
代码实现
运行下面的测试类,可以看看到线程名称成对输出,也就是在同一时刻只有两个线程能够获取到锁,这表明TwinsLock可以按照预期正常工作。可能有同学发现只有线程0和线程1抢到锁,这主要是因为持有锁的线程释放锁后,立即又进入循环并获取到锁,其他线程还来不及获取,要想看到其他线程获取到锁可以在释放锁后面sleep一小段时间。而且应该是非公平锁。
TwinsLock类
public class TwinsLock implements Lock {
private final Sync sync = new Sync(2);
private static final class Sync extends AbstractQueuedSynchronizer{
Sync(int count){
if(count <= 0){
throw new IllegalArgumentException("count must large than zero.");
}
setState(count);
}
@Override
protected int tryAcquireShared(int reducuCount) {
for(;;){
int current = getState();
int newCount = current - reducuCount;
if(newCount < 0 || compareAndSetState(current,newCount)){
return newCount;
}
}
}
@Override
protected boolean tryReleaseShared(int returnCount) {
for(;;){
int current = getState();
int newCount = current + returnCount;
if(compareAndSetState(current,newCount)){
return true;
}
}
}
}
public void lock(){
sync.acquireShared(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
public void unlock(){
sync.releaseShared(1);
}
@Override
public Condition newCondition() {
return null;
}
}
测试类
public class TwinsLockTest {
@Test
public void test() {
final Lock lock = new TwinsLock();
class Worker extends Thread {
@Override
public void run() {
while (true) {
lock.lock();
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
for(int i = 0; i < 100; i++){
Worker w = new Worker();
w.setDaemon(true);
w.start();
}
for(int i = 0; i < 10; i++){
try {
Thread.sleep(1000);
System.out.println();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}