可重入锁

同一个线程再次进入同步代码的时候.可以使用自己已经获取到的锁,这就是可重入锁

java里面内置锁(synchronized)和Lock(ReentrantLock)都是可重入的

比如一个方法是synchronized,递归调用自己,那么第一次已经获得了锁,第二次调用的时候还能进入吗? 直观上当然需要能进入.这就要求必须是可重入的.可重入锁又叫做递归锁

 

public class Widget {        

public synchronized void doSomething() {           

  ...       

 }

}

public class LoggingWidget extends Widget {        

public synchronized void doSomething() {            

System.out.println(toString() + ": calling doSomething");            super.doSomething();//若内置锁是不可重入的,则发生死锁        

}

}

 

当然,可重入锁,也是针对同一个线程而言的。同一线程在调用自己类中其他synchronized方法/块或调用父类的synchronized方法/块都不会阻碍该线程的执行。在多线程就不可以了。

土方法实现可重入锁

将获得锁的线程记录下来,如果进入方法的线程和获得锁的线程是同一个线程,那么我们就可以直接获得锁,不需要等待

public class MyLock implements Lock{

    private boolean isLocked = false;

    //记录获得锁的线程

    private Thread lockBy = null;

    //记录获得锁的线程的重入次数

    private int count = 0;

    @Override

    public synchronized void lock() {

        //获取当前线程

        Thread currentThread = Thread.currentThread();

        //已经有线程获得锁,并且获得锁的线程不是当前线程,那么不满足获得锁,线程需要等待

        while (isLocked && currentThread != lockBy)

            try {

                wait();

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

 

        //没有线程获得锁,或者获得锁的线程就是当前线程

        isLocked = true;

        lockBy = currentThread;

        //记录当前线程的重入次数

        count++;

    }

    @Override

    public synchronized void unlock() {

        //释放锁时,只有当获得锁的线程和当前线程是同一线程时才是正确的

        if (lockBy == Thread.currentThread()) {

            //线程重入次数减一

            count--;

            //只有当count变为0也就是所有获取锁的地方都已经释放了,才能够真正释放锁,修改标志位,唤醒其他线程

            if (count == 0) {

                notify();

                isLocked = false;

            }

        }

    }

@Override

public void lockInterruptibly() throws InterruptedException {

// TODO Auto-generated method stu

}

@Override

public boolean tryLock() {

// TODO Auto-generated method stub

return false;

}

@Override

public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {

// TODO Auto-generated method stub

return false;

}

@Override

public Condition newCondition() {

// TODO Auto-generated method stub

return null;

}

}

当我们使用上面介绍重入锁的测试方式来测验代码时,将会打印出a、b,因为锁是可以重入的,不会出现一直等待的情况。

使用AQS类实现可重入锁

AbstractQueuedSynchronizer类时JDK在1.5版本开始提供的一个可以用来实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)框架,在这里我们不关注它的其他功能,重点介绍一下如何利用AQS实现阻塞锁。

当我们要使用AQS实现一个锁时,我们需要在我们的锁类的内部声明一个非公共的内部帮助类,让这个类集成AbstractQueuedSynchronizer类,并实现其某些方法。
利用AQS类,我们可以实现两种模式的锁,一种是独占锁,一种是共享锁。这两种锁的帮助类需要实现的方法是不同,如果是独占锁,那么需要实现tryAcquire(int)和tryRelease(int)方法;如果是共享锁,那么需要实现tryAcquireShared(int)和tryReleaseShared(int)方法。

AQS类内部维护了一个FIFO的双向链表,用来保存所有争夺锁的线程,AQS源码中的Node类就是双向链表中节点的数据结构。

当使用AQS类加锁时,会调用方法acquire(int)方法中会调用,而acquire(int)方法中会调用tryAcquire(int)来尝试获得锁,如果获得锁成功,方法结束,如果获得锁失败,那么需要将线程维护到FIFO链表中,并且让新增加的线程进入等待状态,并且维护链表中线程的状态。(这部分代码比较复杂,可以参考网上对AQS的讲解,之后会再写一篇专门介绍AQS类的源码解析)

注:这个方法是忽略中断的,不忽略中断的方法,这里不做介绍

当使用AQS释放锁时,会调用方法release(int)方法中会调用,而release(int)方法中会调用tryRelease(int)来尝试释放锁,如果释放锁成功后,如果FIFO链表中有线程,那么,会唤醒所有等待状态的线程。

利用AQS实现可重入锁

实际上利用AQS实现一个可重入锁是非常容易的,首先给出代码。

实际上利用AQS实现锁和用土方法实现锁的思路大体上是相同的,只是,我们不需要关注线程的唤醒和等待,这些会有AQS帮助我们实现,我们只需要实现方法tryAcquire(int)和tryRelease(int)就可以了。

这里我们实际上是应用AQS中的int值保存当前线程的重入次数。

加锁思路:

如果第一个线程进入可以拿到锁,可以返回true,

如果第二个线程进入,拿不到锁,返回false,

有一种特例(实现可重入),如果当前进入线程和当前保存线程为同一个,允许拿到锁,但是有代价,更新状态值,也就是记录线程的重入次数

public class MyLock implements Lock{

 

    private Helper helper = new Helper();

    //实现一个私有的帮助类,继承AQS类

    private class Helper extends AbstractQueuedSynchronizer {

        @Override

        protected boolean tryAcquire(int arg) {

        

            //AQS中的int值,当没有线程获得锁时为0

            int state = getState();

            Thread t = Thread.currentThread();

            //第一个线程进入

            if (state == 0) {

                //由于可能有多个线程同时进入这里,所以需要使用CAS操作保证原子性,这里不会出现线程安全性问题

                if (compareAndSetState(0, 1)) {

                    //设置获得独占锁的线程

                    setExclusiveOwnerThread(Thread.currentThread());

                    return true;

                }

            } else if (getExclusiveOwnerThread() == t) {

                //已经获得锁的线程和当前线程是同一个,那么state加一,由于不会有多个线程同时进入这段代码块,所以没有线程安全性问题,可以直接使用setState方法

                setState(state + 1);

                return true;

            }

            //其他情况均无法获得锁

            return false;

        }

 

        @Override

        protected boolean tryRelease(int arg) {

 

            //锁的获取和释放使一一对应的,那么调用此方法的一定是当前线程,如果不是,抛出异常

            if (Thread.currentThread() != getExclusiveOwnerThread()) {

                throw new RuntimeException();

            }

            

            int state = getState() - arg;

 

            boolean flag = false;

            

            //如果state减一后的值为0了,那么表示线程重入次数已经降低为0,可以释放锁了。

            if (state == 0) {

                setExclusiveOwnerThread(null);

                flag = true;

            }

            

            //无论是否释放锁,都需要更改state的值

            setState(state);

            

            //只有state的值为0了,才真正释放了锁,返回true

            return flag;

        }

 

        Condition newCondition() {

            return new ConditionObject();

        }

    }

 

    @Override

    public void lock() {

        helper.acquire(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 void unlock() {

        helper.release(1);

    }

 

    @Override

    public Condition newCondition() {

        return helper.newCondition();

    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值