Lock接口与实现(1)—重入锁与读写锁

什么是Lock接口

Lock: Lock不是Java中的关键字,而是J.U.C包( java.util.concurrent)下locks下的一个接口。在使用的过程中需要显式的获取(lock.lock())和释放锁(lock.unlock)。

Lock接口与synchronized的优劣对比

synchronized的优劣

在Java多线程编程中,开发者经常使用synchronized关键字来实现同步,控制多线程对变量的访问,来避免并发问题。但是在有些时候,synchronized关键字会比较沉重,不灵活。synchronized 方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放且必须在与所有锁被获取时相同的词法范围内释放所有锁

Lock接口的优劣

lock接口提供了与synchronized关键字类似的同步功能,需要在使用的过程中需要显示的获取和释放锁,这个条件也是lock接口的缺点。lock接口拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。缺点则是缺少隐式获取释放锁的便捷性。

Lock接口的基本API

在这里插入图片描述
以下代码时常用的四个API方法实例Demo,帮助各位理解上面API表格的描述,Condition后文继续解读

package lock;

import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author 潇兮
 * @date 2019/9/21 21:41
 **/
public class Demo4 {

    //公平锁
//    static Lock lock=new ReentrantLock(true);

    //非公平锁
    static Lock lock=new ReentrantLock();

    public static  void main(String[] args) throws  InterruptedException{
        //主线程获取锁
        lock.lock();

        Thread th=new Thread(new Runnable() {
            @Override
            public void run() {
                //子线程获取

                // 尝试获取,浅尝
                boolean result=lock.tryLock();
                System.out.println("尝试获取: 是否获得锁:" + result);

                //过时不候
                boolean result2= false;
                try {
                    System.out.println("过时不候:开始尝试获得锁2:");
                    result2 = lock.tryLock(3, TimeUnit.SECONDS);
                    System.out.println("过时不候: 3s是否获得锁2:" + result2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //听话,有人通知就不获取并抛出异常 InterruptedException
                try {
                    System.out.println("听话: 是否获得锁3:");
                   lock.lockInterruptibly();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("有人告诉我不要去获得锁3");
                }

                //不死不休 只有当15秒后锁释放,此时才能获取到锁打印出来,锁不释放,则一直处于等待队列中
                System.out.println("不死不休: 是否获得锁4:");
                lock.lock();
                System.out.println("不死不休: 获得锁4:");



            }
        });

        th.start();

        Thread.sleep(6000L);
        //中断
        th.interrupt();
        System.out.println("线程中断");

        Thread.sleep(12000L);
        lock.unlock();
    }
}

Condition

Object中的wait()、notify()、notifyAll()只能和synchronized配合使用,可以唤醒一个线程或者全部(单个等待集);Condition需要与Lock配合使用的,提供了多个等待集合,更加精确的控制 。
在这里插入图片描述

Condition的使用—代码示例

可以从打印的结果看到直接运行时结果成功输出,当打开注释掉的代码,会出现死锁。

package lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author 潇兮
 * @date 2019/9/21 22:26
 **/
public class Demo4_Condition {

    private static Lock lock=new ReentrantLock();
    private static Condition condition=lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
      Thread th=new Thread(){
          @Override
          public void  run(){
             //              try {
//                  Thread.sleep(4000L);
//                  System.out.println("3s后");
//              } catch (InterruptedException e) {
//                  e.printStackTrace();
//              }
              lock.lock();

            try {
               System.out.println("condition的挂起...");
               //释放锁
               condition.await();
               System.out.println("condition的使用成功");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
               lock.unlock();
            }
          }
      };

      th.start();
      Thread.sleep(3000L);
      //System.out.println("3s后");
      //condition的方法都要搭配lock方法,不加锁会报错
      lock.lock();
      //唤醒
      condition.signal();
      System.out.println("2s后condition的唤醒");
      lock.unlock();
    }
}


成功输出打印结果
在这里插入图片描述

打开注释死锁的打印结果:
在这里插入图片描述

可重入锁—ReentrantLock

ReentrantLock的可重入性表现在同一个线程可以多次获得锁,而不同线程依然不可多次获得锁,加锁lock.lock几次需要对应释放几次的锁lock.unlock。ReentrantLock中的等待池是Condition,可以有多个Condition,waites为ReentrantLock的等待队列,当有线程T2抢锁的时候,若锁被占用,则进入等待队列中。

ReentrantLock的加锁逻辑

假设有 t1, t2, t3, t4这四个线程(图一),若t1,t2对ReentrantLock lock的执行CASJMM内存模型—手撕CAS)操作抢锁,发现owner值为null,并且count为0,此时只有一个线程(CAS机制)能成功,若T2抢锁成功,将 count 值改为 1 并将owner的引用改为 t2,t1 进入 waiters,状态变为 waiting;若此时 t3 过来抢锁,会对比 owner 值,发现不指向 t3,则进入等待队列 waiters, t4 也是如此(图二)。假如 t2 在此时没有执行 释放锁的前提下又执行 lock( ) 操作抢锁,会对比owner值,发现相等,通过CAS操作将owner值加一变为 2。当 t2 具体如下脑图

图一
在这里插入图片描述
图二
在这里插入图片描述

手写ReentrantLock实现

package lock;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;

/**
 * @author 潇兮
 * @date 2019/9/22 0:03
 * 类说明: 手写实现ReentrantLock 逻辑
 **/
public class Demo4_ReentrantLock implements Lock {

    //创建一个锁拥有者
    AtomicReference<Thread> owner=new AtomicReference<>();
    //记录重入次数
    AtomicInteger count=new AtomicInteger(0);
    //等待队列,实际上的ReentrantLock采用AbstractQueuedSynchronizer(AQS)
    private LinkedBlockingQueue<Thread> waiters=new LinkedBlockingQueue<>();


    @Override
    public boolean tryLock() {
        //获取AtomicInteger值
        int ct=count.get();
        //判断count值是否为0,不为0,锁被占用
        if (ct!=0){
            //判断是否当前线程占用

            if (Thread.currentThread()!= owner.get()){
                //当前线程占用,重入
                count.set(ct+1);
                return true;
            }
//            else {
//                //不是当前线程进入等待队列
//                return  false;
//            }
        }else {
            // count值为0,锁未占用,CAS抢锁
            if (count.compareAndSet(ct,ct+1)){
                owner.set(Thread.currentThread());
                return true;
            }
//            else {
//                //CAS失败,进入等待队列
//                return  false;
//            }
        }
        return  false;
    }

    @Override
    public void lock() {
        //判断锁是否被占用
        if (!tryLock()){
            //加入等待队列
            waiters.offer(Thread.currentThread());
             for (;;){
                 //若线程是队列头部尝试加锁
                 Thread head = waiters.peek();
                 if (head == Thread.currentThread()){
                     if (!tryLock()){
                         //失败,挂起线程
                         LockSupport.park();
                     }else {
                         //移出队列
                         waiters.poll();
                         return;
                     }
                 }else {
                     //不是线程头部
                     LockSupport.park();
                 }
             }

        }
    }

    public boolean tryUnlock() {
        //判断当前线程是否占有锁
        if (owner.get() !=Thread.currentThread()){
            throw new  IllegalMonitorStateException();
        }else {
            //unlock
            int ct=count.get();
            int nextCt=ct-1;
            count.set(nextCt);

            //判断count是否为0,为0则释放锁成功,返回true,不为0则释放锁失败返回false
            if (nextCt==0){
                owner.compareAndSet(Thread.currentThread(),null);
                return true;
            }else {
                return false;
            }
        }
    }

    @Override
    public void unlock() {
      if (tryUnlock()){
         Thread head=waiters.peek();
         if (head!=null){
             //唤醒
             LockSupport.unpark(head);
         }
      }
    }


    @Override
    public void lockInterruptibly() throws InterruptedException {

    }


    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }


    @Override
    public Condition newCondition() {
        return null;
    }
}

读写锁—ReentrantReadWriteLock

什么是ReentrantReadWriteLock

维护一对关联锁,一个只用于读操作,一个只用于写操作;读锁可以有多个线程共同持有,是一种共享锁;写锁是排他的,是一种互斥锁。同一时间内,两把锁不能被不同线程持有。

ReentrantReadWriteLock基本原理—丧心病狂版

假设有两个线程 t1 , t2。(实际上源码中使用state单个字段存储读写数量,所以不需要采用cas操作保证原子性,这里是为了方便理解)

  1. 现在假如 t1 要去抢写锁,会先判断 readCount 是否为 0,如果 readCount 为0,则读锁未被占用,才能去抢写锁,如图一;
  2. t1 开始抢写锁,判断 writeCount 是否为 0,若果 writeCount 为 0,则对它进行 CAS 操作,从 0 变为 1,owner 由 null 变为 t1,如图二;
  3. 此时若 t1 线程又去抢写锁,会先判断 readCount 是否为 0, readCount 不为 0,则抢写锁 ,判断写锁 writeCount 是否为 0,writeCount 不为 0 则继续判断 owner 的值是否为当前线程。如果是做重入操作若不是则进入waiters,当下指向 t1 线程,则 writeCount 的值 +1 变为 2 ,如图三;
  4. 此时 t2线程进入抢锁,判断 readCount 为 0,去争抢写锁,判断写锁 writeCount 是否为 0,writeCount 等于2不为 0,则继续判断 owner 的值是否为当前线程,不是指向 t2,进入等待队列,状态waiting;如图四;
  5. 此时 t1 释放锁,writeCount 由2变为1,再释放一次变为0,owner值变为null,如图五;
  6. 此时waiters队列的头部线程唤醒即 t2 ,假设t2抢锁成功,writeCount变为1,owner变为 t2(难受不画图了,脑补吧);
  7. 假设此时有 t3 线程进入抢读锁,会先判断 writerCount 是否为0,writerCount 不为0进入等待队列,类推,t4,t5 都进入等待队列,状态waiting,如图六;
  8. 此时t2释放掉锁,writerCount变为0,owner变为null;唤醒了头部队列 t3,t3进行抢锁,会先判断 writerCount 是否为0,writerCount为0,说明写锁未被占用,才能去抢读锁,采用CAS操作将 0 改为 1,t4 ,t5类推,readCount为3,如图七;
  9. 此时 t1 抢写锁,先判断readCount是否为0,不为0,进入等待队列,此时t5,t4,t3释放锁(采用CAS方式),readCount变为0,唤醒头部队列 t1重新抢锁。
    完美,这就是ReentrantReadWriteLock的基本原理,,,,,,,,,,,,

图一
在这里插入图片描述
图二
在这里插入图片描述
图三
在这里插入图片描述
图四
在这里插入图片描述
图五
在这里插入图片描述
图六
在这里插入图片描述
图七
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值