JAVA学习——基于AQS的ReentrantLock公平锁和非公平锁的实现

之前笔者解析了AQS的源码,在JUC中有很多锁是基于AQS实现的,今天想写个简单的ReentrantLock实现,代码也基本是在看了ReentrantLock源码后写出来的,做个笔记。

总结一下AQS的原理,就是使用一个int类型来表示可申请的锁资源,提供了一系列的原子操作,以及用于放置申请锁的线程的等待队列。实际上定义了一整套完整的多线程访问共享资源的同步框架,具体的解析可以看我的另一篇文章

ReentrantLock非公平锁的实现

首先来看ReentrantLock的非公平锁实现,它的类定义:

public class UnfairReentrantLockImpl extends AbstractQueuedSynchronizer implements Lock, Serializable

非公平锁实现了Lock接口,因此需要实现以下的方法:
JAVA学习——基于AQS的ReentrantLock公平锁和非公平锁的实现_第1张图片
基本上只要了解接口的语义,基于AQS提供的接口,可以快速实现Lock的接口功能,代码如下,可以看到AQS提供的是线程同步部分的实现:

	public void lock() {
		//cas的方式修改state值,如果成功,即获得锁
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
        } else {
        	//否则加入等待队列,自旋方式申请锁
            acquire(1);
        }
    }
    @Override
    public void unlock() {
    	//释放锁
        release(1);
    }
	@Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException 			{
   		//阻塞方式申请锁,指定时间后无论是否申请成功都返回
        return tryAcquireNanos(1, unit.toNanos(time));
    }
    @Override
    public boolean tryLock() {
    	//非阻塞方式申请锁
        return nofairTryAcquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
    	//可中断式的申请锁
        acquireInterruptibly(1);
    }

    @Override
    public Condition newCondition() {
        return new ConditionObject();
    }
    //尝试获取锁
	private final boolean nofairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (current == getExclusiveOwnerThread()) {
        	//如果当前线程已经持有锁,只修改一下state的值即返回,
        	//体现了可重入锁的特点
            int nextC = c + acquires;
            if (nextC < 0) {
                throw new Error("Maximum lock lcount exceeded");
            }
            setState(nextC);
            return true;
        }
        return false;
    }

ReentrantLock锁继承AQS,必须要实现AQS的两个方法:tryAcquire和tryRelease,这两个方法在AQS只提供了一个抛出异常的实现。tryAcquire的语义是非阻塞式的申请锁,而tryRelease的语义是释放锁,并恢复state的值。在AQS内部,会使用这两个方法构造整个同步器的实现。

    //AbstractQueuedSynchronizer定义的方法,该方法在AbstractQueuedSynchronizer中只有一个抛出异常的默认实现
    @Override
    protected boolean tryAcquire(int arg) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (compareAndSetState(0, arg)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (current == getExclusiveOwnerThread()) {
            int nextC = c + arg;
            if (nextC < 0) {
                throw new Error("Maximum lock lcount exceeded");
            }
            setState(nextC);
            return true;
        }
        return false;
    }
 //AbstractQueuedSynchronizer定义的方法,该方法在AbstractQueuedSynchronizer中只有一个抛出异常的默认实现
    @Override
    protected boolean tryRelease(int arg) {
        int c = getState() - arg;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;

        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }

ReentrantLock公平锁的实现
ReentrantLock公平锁强调根据线程等待时间长短来分配锁,等待时间最长的获取锁,在实现上,会让等待队列头部的线程获得锁。
公平锁的代码实现与非公平锁基本一致,主要区别在以下两个方法上,公平锁的lock方法会将申请锁的线程直接加入等待队列,根据等待时间排序来决定获取锁的线程。公平锁的tryAcquire方法在判断一个线程能否获得锁时,会比非公平锁多加入一个请求线程是否为头线程的判断,只有头线程能获得线程。读者可以与非公平锁的这两个方法实现进行对比,即可清楚比较出两者的区别

	@Override
    public void lock() {
        //与非公平锁的区别:直接将当前申请锁请求加入队列
        acquire(1);
    }
     @Override
    protected boolean tryAcquire(int arg) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            //只有在当前线程为头结点时,才能去获得锁
            if (!hasQueuedPredecessors() && compareAndSetState(0, arg)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (current == getExclusiveOwnerThread()) {
            int nextC = c + arg;
            if (nextC < 0) {
                throw new Error("Maximum lock count exceeded");
            }
            setState(nextC);
            return true;
        }
        return false;
    }

ReentrantLock的测试

最后,给出一个简单的测试类,这个测试类创建了10个银行账户,初始金额都为1000元,然后有30个管理线程,会随机挑选两个账户,转账一个10以内的随机金额,每个线程独立转账10次。完成所有的转账操作后,计算所有账户总金额,如果总金额与原来一致,证明整个转账过程没有同步错误。


public class LockTest {
    private static Account[] accounts = new Account[10];
    private static AccountManager[] threads = new AccountManager[30];

    public static void main(String[] args) throws Exception {

        for (int i = 0; i < 10; i++) {
            accounts[i] = new Account(1000);
        }

        double sum = 0;
        for (int i = 0; i < 10; i++) {
            sum += accounts[i].getMoney();
        }

        System.out.println("src sum:" + sum);

        for (int i = 0; i < 30; i++) {
            threads[i] = new AccountManager();
            threads[i].start();
        }

        for (int i = 0; i < 30; i++) {
            threads[i].join();
        }

        sum = 0;
        for (int i = 0; i < 10; i++) {
            sum += accounts[i].getMoney();
        }

        System.out.println("after operation sum:" + sum);
        for (int i = 0; i < accounts.length; i++) {
            System.out.println("acount-" + i + " res money: " + accounts[i].getMoney());
        }

    }

    private static class AccountManager extends Thread {
        @Override
        public void run() {
            int index = 10;
            int fromAccount = 0, toAccount = 0;
            while (index > 0) {
                fromAccount = (int) (Math.random() * 10);
                toAccount = (int) (Math.random() * 10);
                if (toAccount == fromAccount) {
                    toAccount = (toAccount + 1) % 10;
                }
                try {
                    AccountMgr.transfer(accounts[fromAccount], accounts[toAccount], Math.random() * 10);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                index--;
                System.out.println(Thread.currentThread() + " complete " + (10 - index) + " transformation from " + fromAccount + " to " + toAccount);
            }
        }
    }
}

public class Account {
    private Lock lock = new FairReentrantLockImpl();
    private volatile double money;

    public Account(final double money) {
        this.money = money;
    }

    public void add(double money) {
        lock.lock();
        try {
            this.money += money;
        } finally {
            lock.unlock();
        }
    }

    public void reduce(double money) {
        lock.lock();
        try {
            this.money -= money;
        } finally {
            lock.unlock();
        }
    }

    public double getMoney() {
        return money;
    }

    void lock() {
        lock.lock();
    }

    void unLock() {
        lock.unlock();
    }

    boolean tryLock() {
        return lock.tryLock();
    }
}
public class AccountMgr {
    public static boolean tryTransfer(Account from, Account to, Double money) throws NoEnoughMoneyException {
        if (from.tryLock()) {
            try {
                if (to.tryLock()) {
                    try {
                        if (from.getMoney() >= money) {
                            from.reduce(money);
                            to.add(money);
                        } else {
                            System.out.println("operation failed ");
//                            throw new NoEnoughMoneyException();
                        }
                        return true;
                    } finally {
                        to.unLock();
                    }
                }
            } finally {
                from.unLock();
            }
        }
        return false;
    }

    public static void transfer(Account from, Account to, Double money) throws NoEnoughMoneyException {
        boolean success = false;
        do {
            success = tryTransfer(from, to, money);
            if (!success)
                Thread.yield();
        } while (!success);
    }

    public static class NoEnoughMoneyException extends Exception {
    }
}

你可能感兴趣的:(JAVA学习)