并发系列之Lock解析

1、Lock简介

      Lock接口是对锁操作的基本定义,它提供了synchronized关键字所具备的全部功能方法,另外我们可以借助Lock创建不同的Condtion对象进行多线程间的通信操作。

  1. lock():尝试获取锁。如果锁已经被另一个线程持有,那么该线程会进入阻塞状态,直到获取到锁。
  2. lockInterruptibly():尝试获取锁,进入阻塞的线程是可以被中断的,该方法可以获取中断信号。
  3. tryLock():尝试获取锁,调用该方法获取锁无论是否成功都会立即返回,线程不会进入阻塞状态。
  4. boolean tryLock(long time, TimeUnit unit):该方法与tryLock()方法类似,只是多了获取锁时间的限制,如果在限制的时间内没有获取到锁,则结果返回false。
  5. unlock():释放锁,在持有锁的线程运行结束后,应该确保对锁资源的释放。
  6. newCondition():创建一个与Lock相关的Condition对象。
public class WtyicyLock implements Lock {
    /**
     * 尝试获取锁:如果锁已经被另一个线程持有,那么该线程会进入阻塞状态,直到获取到锁。
     */
    @Override
    public void lock() {

    }

    /**
     * 尝试获取锁:进入阻塞的线程是可以被中断的,该方法可以获取中断信号。
     * @throws InterruptedException
     */
    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    /**
     * 尝试获取锁 调用该方法获取锁无论是否成功都会立即返回,线程不会进入阻塞状态。
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock() {
        return false;
    }

    /**
     * 尝试获取锁:该方法与tryLock()方法类似,只是多了获取锁时间的限制,如果在限制的时间内没有获取到锁,则结果返回false。
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    /**
     * 释放锁:在持有锁的线程运行结束后,应该确保对锁资源的释放。
     */
    @Override
    public void unlock() {

    }

    /**
     *
     * 创建一个与Lock相关的Condition对象
     * @return
     */
    @Override
    public Condition newCondition() {
        return null;
    }
}

      显示锁Lock有很多实现:ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock。开发中最常用的实现类是ReentrantLock。


2、ReentrantLock介绍

      ReentrantLock是基于AQS同步器构建的锁,只支持独占方式的获取操作,因此它实现了tryAcquire、tryRelease、isHeldExclusively。
      ReentrantLock将同步状态state用于保存锁获取操作的次数,并且还维护了一个owner变量用来保存当前所有者线程的标识符。

ReentrantLock内部类有3个:

  1. 抽象静态内部类Sync,其继承了AbstractQueuedSynchronizer。
  2. 静态内部类NonfairSync,其继承了Sync。
  3. 静态内部类FairSync,其继承了Sync。

      ReentrantLockReentrantLock提供了两种锁机制:公平锁和非公平锁。公平锁通过类FairSync提供的方法实现,非公平锁通过NonfairSync提供的方法实现。

2.1、内部类Sync:

abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * 抽象方法,获取锁的方法lock()
         */
        abstract void lock();

        /**
         * 非公平锁获取方法
         */
        final boolean nonfairTryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();
            //获取state同步器状态的值
            int c = getState();
            //如果state的值为0,说明没有其他线程持有锁
            if (c == 0) {
                //通过CAS的方式修改state的值,多线程环境下state的值可以提前被其他线程抢占
                if (compareAndSetState(0, acquires)) {
                    //将线程持有标识设置为当前线程对象
                    setExclusiveOwnerThread(current);
                    //当前线程获取锁成功返回true
                    return true;
                }
            }
            //如果当前线程是持有锁的线程
            else if (current == getExclusiveOwnerThread()) {
                //将获取的state的值+本次获取锁的个数
                int nextc = c + acquires;
                //如果修改后的数值小于0,则抛出异常
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //如果不小于0,则修改sate的值
                setState(nextc);
                //当前线程重入成功,返回true
                return true;
            }
            //其他情况均认为当前线程获取锁失败,返回false
            return false;
        }
        /**
         * 释放锁
         */
        protected final boolean tryRelease(int releases) {
            //获取state状态值,减去需要释放的锁的个数
            int c = getState() - releases;
            //获取当前线程,如果当前线程不是锁的持有者,则抛出异常,此步骤保证只有锁的持有者才可以释放锁
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            //定义free变量初始化为false
            boolean free = false;
            //如果c为0,表示锁已经全部释放
            if (c == 0) {
                //修改free为true,表示锁已经全部释放成功
                free = true;
                //设置锁的持有标识为null
                setExclusiveOwnerThread(null);
            }
            //将释放锁之后的值赋值给state
            setState(c);
            //如果c为0,则返回true,其他情况锁资源没有完全释放,均返回false
            return free;
        }

        /**
         * 判断当前线程是否持有某个Lock
         * @return
         */
        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        /**
         * 创建Condition对象
         * @return
         */
        final AbstractQueuedSynchronizer.ConditionObject newCondition() {
            return new ConditionObject();
        }

        // Methods relayed from outer class

        /**
         * 获取锁对象的持有线程
         * @return
         */
        final Thread getOwner() {
            //如果state为0表示没有任何线程持有锁资源返回null,state不为0表示有线程持有锁则返回持有锁的线程
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        /**
         * 查询当前线程在某个Lock上的数量,它与monitor计数器的作用是一样的
         * @return
         */
        final int getHoldCount() {
            //当前线程是锁的持有者则返回state的值,不是则返回0
            return isHeldExclusively() ? getState() : 0;
        }

        /**
         * 判断锁对象是否被线程持有
         * @return
         */
        final boolean isLocked() {
            return getState() != 0;
        }

        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
        private void readObject(java.io.ObjectInputStream s)
                throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

2.2、内部类NonfairSync:

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
   //实现父类Sync的抽象接口
    final void lock() {
        //通过CAS的方式修改state状态,期望值是0,新值是1
        //修改成功,则表示获取锁成功,将当前线程设置为锁持有者
        //修改失败,则调用AQS的acqiure方法,将当前线程加入同步队列
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
 	//AQS的子类实现方法tryAcquire,其调用了父类Sync的非公平锁获取方法nonfairTryAcquire
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

在这里插入图片描述

2.3、内部类FairSync:

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
 	//实现父类Sync的抽象接口
    final void lock() {
        //调用AQS的acqiure方法,将当前线程加入同步队列
        acquire(1);
    }
     //AQS的子类实现方法tryAcquire,它没有调用父类Sync的方法,而是自己实现了tryAcquire方法
     protected final boolean tryAcquire(int acquires) {
        //获取当前线程对象
      	final Thread current = Thread.currentThread();
         //获取同步器状态值
      	int c = getState();
        //如果c为0,表示锁资源没有被其他线程持有
     	if (c == 0) {
          //判断同步队列中的当前线程节点是head的下一个节点
          //如果是则通过CAS的方式修改state的值,修改成功将当前线程设置为锁的持有者
          if (!hasQueuedPredecessors() &&
              compareAndSetState(0, acquires)) {
              setExclusiveOwnerThread(current);
              //当前线程获取锁成功返回true
              return true;
          }
      }
      //如果当前线程是锁的持有者,则将状态的值+此次需要获取的锁的个数将其赋值给state
      else if (current == getExclusiveOwnerThread()) {
          int nextc = c + acquires;
          if (nextc < 0)
              throw new Error("Maximum lock count exceeded");
          setState(nextc);
          //当前线程获取锁成功返回true
          return true;
      }
      //其他情况锁获取失败,返回false
      return false;
    }
}
//AQS方法
public final boolean hasQueuedPredecessors() {     
    Node t = tail; 
    Node h = head;
    Node s;   
    //如果头节点和尾节点相等,则返回false,表示同步队列中只有一个节点是当前节点
    //如果不相等,那么如果头节点的next不等于null并且next的节点线程是当前节点返回false
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

在这里插入图片描述


3、ReentrantLock的用法

      ReentrantLock提供了两个构造函数,可以指定锁获取的方式:公平和非公平。

public class ReentrantLock implements Lock, java.io.Serializable {   
    /** 内部类Sync对象 */
    private final Sync sync;
     /**
     * 无参构造,默认是非公平锁
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    /**
     * 有参构造,可以指定公平锁和非公共锁
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
}
private final ReentrantLock lock = new ReentrantLock();
public void fun() {
    lock.lock();
    try {
    //同步代码逻辑
    }finally {
        lock.unlock();
    } 
}

例子:

public class ReentrantLockTest {
    private final ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        ReentrantLockTest test = new ReentrantLockTest();
        test.creatThread().start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //main线程不能释放其他线程加的锁
        //test.lock.unlock();
        test.lock.lock();
        System.out.println("main线程计数器=" +  test.lock.getHoldCount());
        test.lock.unlock();
    }
    public Thread creatThread() {
        return  new Thread(()->{
                lock.lock();
                try {
                    System.out.println("计数器=" + lock.getHoldCount());
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //加两次锁,只释放一次,会一直阻塞
                   // lock.lock();
                } finally {
                    lock.unlock();
                }
            });
    }
}
  1. ReentrantLock的用法比较简单,在使用的时候需注意加锁几次就要解锁几次,否则可能出现锁资源无法释放导致一直阻塞的情况。
  2. 锁的释放写在finally语句块中保证锁资源一定释放。
  3. 只有获取锁的线程才能释放锁资源,没有持有锁的线程释放锁资源抛出IllegalMonitorStateException异常。
  4. ReentrantLock是独占锁,main线程只有在thread线程释放锁资源后才能获取到锁。

公平锁与非公平锁的区别:

      公平锁:当一个线程尝试获取锁的时候先加入同步队列,如果是下一个需要唤醒的节点则去竞争锁,锁的竞争是先到先得,保证了公平性,但是锁的竞争效率会变低。
      非公平锁:当一个线程尝试获取锁的时候优先尝试获取锁,如果获取失败再加入同步队列,提高了锁竞争的性能,但是会出现同步队列中的线程一直获取不到锁的现象,称为饥饿现象。


5、往期佳文

5.1、面试系列

1、吊打面试官之一面自我介绍
2、吊打面试官之一面项目介绍
3、吊打面试官之一面系统架构设计
4、吊打面试官之一面你负责哪一块
5、吊打面试官之一面试官提问
6、吊打面试官之一面你有什么问题吗

······持续更新中······


5.2、技术系列

1、吊打面试官之分布式会话
2、吊打面试官之分布式锁
3、吊打面试官之乐观锁
4、吊打面试官之幂等性问题
5、吊打面试关之分布式事务
6、吊打面试官之项目线上问题排查

······持续更新中······

5.3、源码系列

1、源码分析之SpringBoot启动流程原理
2、源码分析之SpringBoot自动装配原理
3、源码分析之ArrayList容器
4、源码分析之LinkedList容器
5、码分析之HashMap容器

5.4、数据结构和算法系列

1、数据结构之八大数据结构
2、数据结构之动态查找树(二叉查找树,平衡二叉树,红黑树)

······持续更新中······

5.5、多线程系列

1、并发系列之初识多线程
2、并发系列之JMM内存模型
3、并发系列之synchronized解析
4、并发系列之volatile解析
5、并发系列之synchronized与volatile的区别

······持续更新中······


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值