(7)AbstractQueuedSynchronizer和ReentrantLock—— 可重入锁的实现

目录

一、 可重入锁与非可重入锁

二、 公平锁与非公平锁

 

三、独占锁与共享锁

四、AbstractQueuedSynchronizer(AQS)

五、ReentrantLock

1. lock方法

2. tryAcquire方法

3. 可重入的实现

六、用AQS实现一个自己写的重入锁


一、 可重入锁与非可重入锁

    可重入锁,就是当一个占有锁的线程再次进入到这个锁的代码块时,可以直接获得锁并且执行,而无需等待。

    与之相对的,就是非可重入锁,当一个占有锁的线程再次进入到这个锁块时仍然需要等待获取锁。

    Java中的synchronized和ReentrantLock都是可重入锁。

    可重入锁的优点是:同一个线程获取同一个锁时不会造成死锁,且加快了执行的速度。

    我们举一个例子来看下可重入锁:

public class Main {
    public static void funcA(){
        synchronized (Main.class){  //加锁
            System.out.println("a");
            funcB();
            System.out.println("done");
        }
    }

    public static void funcB(){
        synchronized (Main.class){ //加锁,同一把锁
            System.out.println("b");
        }
    }

    public static void main(String[] args) {
        funcA();
    }
}

       上面的代码,funcA与funcB中加的是同一把锁,funcA先获得了锁开始执行。之后funcA中调用了funcB,funcB同样也加了锁,如果锁不可重入,就会造成死锁,致使后面的信息打印不出来。而synchronized是可重入锁,避免了死锁。

执行结果:

 

二、 公平锁与非公平锁

    公平锁:获取锁的机会按照严格的请求时间先后顺序,有个先来后到。

    非公平锁:不按照先后顺序,谁来了都可以tryAcquire试一下,谁能成功全靠运气。

    公平锁与非公平锁的实现原理

    将需要获取锁的线程加入到一个FIFO的队列中,按照先进先出的原则,每次都是排在第一个的线程拥有获得锁的机会。

 

    公平锁与非公平锁的比较: 哪一个更好一点呢?

    公平锁比非公平锁多用了一个队列,耗费了一点点内存,但是它可以使所有需要获取锁的线程在排队的时候处于等待状态(除了排在队头的可以处于running),CPU轮询的时候就无需对这些处于等待状态的线程进行调度,节省了CPU资源。因此,公平锁更好一些。

 

三、独占锁与共享锁

    独占锁,就是一个线程获取锁之后,在其释放锁前,其他线程就不能获取这个锁;

    共享锁,就是一个线程获取锁之后,其他的线程也可以获取锁并执行同步代码块。

    ReentrantReadWriteLock ,它的写锁就是一个独占锁,它的读锁就是一个共享锁。

  •     当上了写锁之后,就只允许这一个线程写,其他线程就不能读不能写了;
  •     而当只有读锁的时候,多个线程可以共同读取数据,因为只读的时候不会产生线程安全性问题。

 

四、AbstractQueuedSynchronizer(AQS)

    AbstractQueuedSynchronizer内部维护了一个FIFO的队列和一个 int 状态变量,队列用来实现排队,状态变量用来控制是否可以获得锁。 它为实现依赖于FIFO队列的阻塞锁和相关同步器提供了一个框架。

    AQS实现阻塞锁通常是通过子类extends AQS来实现的

    AQS定义了很多定义为final的方法,用来提供服务。

    当子类继承AQS之后,还有几个方法需要自己实现,用来完成锁的获取和释放。

tryAcquire(int arg) 尝试获取锁(独占)
tryRelease(int arg) 尝试释放锁(独占)
tryAcquireShared(int arg) 尝试获取锁(共享)
tryReleaseShared(int arg) 尝试释放锁(共享)
isHeldExclusively 返回boolean, 是否被独占

如果是实现独占锁,就实现前两个方法;

如果是实现共享锁,就实现后两个方法。

这些个方法的原始写法只是抛出一个异常,需要继承者自己实现。

 

   AQS中的其他方法及其作用下面以ReentrantLock的实现为例来讲解。 

五、ReentrantLock

    ReentrantLock是通过继承AQS来实现的,它的类结构图是这样

    ReentrantLock中包含了一个Sync类,Sync类继承了AQS的诸多方法。

    NonFairSync 和 FairSync继承了Sync类,它们两个是真正用来干活的,它们各有两个方法 lock 和 tryAcquire用来实现获取锁,FairSync实现公平锁,NonFairSync实现非公平锁。

    看一下ReentrantLock的源码实现

    

1. lock方法

    非公平锁先尝试去独占,如果不成功再和公平锁一样调用AQS的acquire()方法。在acquire()方法中,调用了addWaiter,正是addWaiter方法中将想要获取锁的线程加入了FIFO的Queue中。

        

2. tryAcquire方法

    非公平锁直接调用Sync类的nonfairTryAcquire方法,其实nonfairTryAcquire的实现与公平锁的tryAcquire差不多,只是多加了一个判断线程队列是否为空的步骤而已。

    如果线程队列不为空又不是同一个线程重入,则直接return false,会使处于Queue的头节点的线程优先获取锁,从而实现获取时间上的公平。

3. 可重入的实现

  在tryAcquire方法中,else if 判断如果当前线程是已经独占了锁的线程,那么只是将锁的层数+1,然后返回true,就相当于又获取了锁,从而实现了可重入。

 

六、用AQS实现一个自己写的重入锁

  

import javax.management.RuntimeErrorException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class MyLock implements Lock{  //implements Lock,声明MyLock为锁
    private static Helper helper = new Helper();
    private static class Helper extends AbstractQueuedSynchronizer{  //同Sync一样extends AQS实现功能
        @Override
        protected boolean tryAcquire(int arg) {
            int state = getState();
            if(0 == state){
                if(compareAndSetState(arg)){
                    setExclusiveOwnerThread(Thread.currentThread());
                    return true;
                }
            }
            //实现可重入
            else if(getExclusiveOwnerThread() == Thread.currentThread()){
                setState(state + arg);
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            //容错处理
            if (getExclusiveOwnerThread() != Thread.currentThread()){
                throw new RuntimeException();
            }

            int state = getState() - arg;
            if (state == 0){
                setExclusiveOwnerThread(null);
                setState(state);      //这里还有下面都有setState(state)但不能合并提前到前面,否则先setState(state)就会造成释放另一个线程先运行,造成原子性失败
                return true;
            }
            setState(state);     //setState(state)不能合并提前到前面
            return false;
        }

        @Override
        protected boolean isHeldExclusively() {
            return 0 != getState();
        }

        protected Condition getCondition(){
            return new ConditionObject();
        }
    }
    @Override
    public void lock() {
        helper.acquire(1);
    }

    @Override
    public void unlock() {
        helper.release(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        helper.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return helper.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return helper.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public Condition newCondition() {
        return helper.getCondition();
    }
}

  测试代码

public class Main {
    private static volatile int num = 0;
    private static MyLock lock = new MyLock();

    public static void main(String[] args) {
        //起两个线程交替打印确定不会出现线程安全性问题
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    if(lock.tryLock()){
                        ++num;
                        System.out.println(Thread.currentThread().getName() + " " + num);
                        lock.unlock();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    if(lock.tryLock()){
                        System.out.println(Thread.currentThread().getName() + " " + num);
                        ++num;
                        lock.unlock();
                    }
                }
        }
        }).start();
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章