ReentrantLock详解


ReentrantLock

  • 提供了无条件的,可轮询的,定时的以及可中断的锁获取操作
  • 加锁和解锁都是显式的

ReentrantLock使用格式

  Lock lock = new ReentrantLock();
  try{
      lock.lock();//加锁操作
  }finally{
      lock.unlock();
  }

使用过后必须释放锁

底层实现

当我们new一个ReentrantLock对象时,底层会帮我们new一个NonfairSync对象,NonfairSync FairSync都是基于AQS队列实现,AbstractQueuedSynchronizer简称为AQS队列。
它是基于先进先出FIFO实现的等待队列,AQS队列是由Node节点组成的双向连标实现啊的,所有的操作都是在这个AQS队列当中,如果一个线程获取锁就直接成功,如果失败了就将其放入等待队列当中。

获取锁

1)CAS操作抢占锁,抢占成功则修改锁的状态为1,将线程信息记录到锁当中,返回state=1
2)抢占不成功,tryAcquire获取锁资源,获取成功直接返回,获取不成功,新建一个检点插入到
当前AQS队列的尾部,acquireQueued(node)表示唤醒AQS队列中的节点再次去获取锁

释放锁

1)获取锁的状态值,释放锁将状态值-1
2)判断当前释放锁的线程和锁中保存的线程信息是否一致,不一致会抛出异常
3)状态只-1直到为0,锁状态值为0表示不再占用,为空闲状态

公平锁与非公平锁

  • 公平锁的实现就是谁等待时间最长,谁就先获取锁
  • 非公平锁就是随机获取的过程,谁运气好,cpu时间片轮询到哪个线程,哪个线程就能获取锁

公平锁

class Singleton1{
    private static Lock lock = new ReentrantLock(true);
    public static void test(){
        for(int i=0; i<2; i++){
            try{
                lock.lock();
                System.out.println(Thread.currentThread().getName()+"获取了锁");
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
     public static void main(String[] args) {
             new Thread("线程A"){
            @Override
            public void run() {
                test();
            }
        }.start();
        new Thread("线程B"){
            @Override
            public void run() {
                test();
            }
        }.start();
        }
        }

运行结果为:
线程A获得了锁
线程B获得了锁
线程A获得了锁
线程B获得了锁

private static Lock lock = new ReentrantLock(true);

给定参数为(true)则为公平锁,哪个线程等待的时间越长,哪个线程获得锁

非公平锁

class Singleton1{
    private static Lock lock = new ReentrantLock();
    public static void test(){
        for(int i=0; i<2; i++){
            try{
                lock.lock();
                System.out.println(Thread.currentThread().getName()+"获取了锁");
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
     public static void main(String[] args) {
             new Thread("线程A"){
            @Override
            public void run() {
                test();
            }
        }.start();
        new Thread("线程B"){
            @Override
            public void run() {
                test();
            }
        }.start();
        }
        }
private static Lock lock = new ReentrantLock();

参数为默认参数,为非公平锁,线程随机获得锁

可重入锁与不可重入锁

重入锁实现可重入性原理或机制是:每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。

synchronized

public class TestDemo10 {
    public static synchronized void lock1(){
        System.out.println("lock1");
        lock2();
    }
    public static synchronized void lock2(){
        System.out.println("lock2");
    }
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                TestDemo10 lock = new TestDemo10();
                lock.lock1();
            }
        }.start();
        }
        }

运行结果:
在这里插入图片描述
所以synchronized为可重入锁

ReentrantLock

    private static Lock lock = new ReentrantLock();
    private static int i = 0;
        try{
            lock.lock();
            lock.lock();
            i++;
        } finally {
            lock.unlock();
            lock.unlock();
        }

运行不会被阻塞,所以R~~eentrantLock为可重入锁

可定时性

public class TestDemo10 {
   private  static ReentrantLock lock = new ReentrantLock();
       public static void main(String[] args) {
        Thread thread1 = new Thread("thread1"){
            @Override
            public void run() {
                try{
                    if(lock.tryLock(5, TimeUnit.SECONDS)){
                        TimeUnit.SECONDS.sleep(6);
                    }else{
                        System.out.println(Thread.currentThread().getName()+"获取锁失败");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        };
        Thread thread2 = new Thread("thread2"){
            @Override
            public void run() {
                try{
                    if(lock.tryLock(5, TimeUnit.SECONDS)){
                        TimeUnit.SECONDS.sleep(6);
                    }else{
                  System.out.println(Thread.currentThread().getName()+"获取锁失败");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    if(lock.isHeldByCurrentThread()){
                        lock.unlock();
                    }
                }
            }
        };
        thread1.start();
        thread2.start();
        }
        }

运行结果为:
线程2获取锁失败

因为线程1先获得锁,执行睡眠6秒的操作,睡眠中不会释放掉锁,因此线程2在5秒内没有获取到锁。

ReentrantLock实现单例

   class Singleton1{
        private Singleton1(){
        }
    private static volatile Singleton1 instance;
    private static Lock lock = new ReentrantLock();
    //返回当前唯一一个实例
    static Singleton1 getInstance(){
        if(instance == null){//判断是否初始化
            try{
                lock.lock();
                if(instance == null){
                    instance = new Singleton1();
                //    System.out.println("single has been initialized by "+Thread.currentThread().getName());
                }
            } finally {
                lock.unlock();
            }
        }
        return instance;
    }
}

public class TestDemo11{
    public static void main(String[] args) {
            new Thread(){
                @Override
                public void run() {
                    System.out.println(Singleton1.getInstance());
                }
            }.start();
    }
}

ReentrantLock与synchronized的比较

相似点

它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,等到释放掉锁或者唤醒后才能继续获得锁。

区别

对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成

便利性:Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,而ReenTrantLock需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。

锁的细粒度和灵活度:ReenTrantLock优于Synchronized

  • 21
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值