今天讲reentrantlock源码,讲解的思路是先自己手写最简单lock,然后一步一步增加功能,然后再去看源码

目录

手写版本1,最基础的lock,抢不到锁就自旋

手写版本2,抢不到锁就把自己放入队列并且阻塞自己


手写版本1,最基础的lock,抢不到锁就自旋

先写基础部分,获取unsafe类和stateOffset,这个主要是也用于CAS操作

    private final static Unsafe unsafe = UnsafeUtils.getUnsafe();//1. 获取unsafe类private final static long stateOffset; //2. 定义偏移量static {try {stateOffset = unsafe.objectFieldOffset(MyLock2.class.getDeclaredField("state"));} catch (NoSuchFieldException e) {throw new Error(e);}}

接着写lock方法和unlock方法,并且定义一个int类型的变量state当做锁

    private int state = 0;    public void lock() {//抢锁if (!unsafe.compareAndSwapInt(this, stateOffset, 0, 1)) {//抢锁失败自旋,这里睡眠100ms是为了让出cpu,不然一直抢占着cpu是不行的try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}public void unlock() {//放锁state = 0;}

这样,一个最最简单的lock就完成了,让我们测试一下

    public static void main(String[] args) throws InterruptedException {RenhaiLock lock = new RenhaiLock();Thread t1 = new Thread(() -> {for (int i = 0; i < 100000; i++) {
//                lock.lock();k++;
//                lock.unlock();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 100000; i++) {
//                lock.lock();k++;
//                lock.unlock();}});t1.start();t2.start();t1.join();t2.join();System.out.println(k);}

输出结果

Connected to the target VM, address: '127.0.0.1:65421', transport: 'socket'
112277
Disconnected from the target VM, address: '127.0.0.1:65421', transport: 'socket'Process finished with exit code 0

然后再看加上lock的测试结果

    public static void main(String[] args) throws InterruptedException {RenhaiLock lock = new RenhaiLock();Thread t1 = new Thread(() -> {for (int i = 0; i < 100000; i++) {lock.lock();k++;lock.unlock();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 100000; i++) {lock.lock();k++;lock.unlock();}});t1.start();t2.start();t1.join();t2.join();System.out.println(k);}

测试结果

Connected to the target VM, address: '127.0.0.1:59131', transport: 'socket'
200000
Disconnected from the target VM, address: '127.0.0.1:59131', transport: 'socket'Process finished with exit code 0

测试结果证明我们的lock生效了,但是这个版本的lock存在一个问题,就是当抢不到锁的时候会进入自旋,如果抢到锁的任务耗时太长,那么就会浪费很多cpu资源,所以我们要增加一个队列用于存放抢不到锁的线程,并且用LockSupport.park()方法阻塞它,然后解锁的时候去唤醒队列中的线程,这样在获取锁线程执行时间再长也不会浪费资源,因为抢不到锁的线程是在队列中阻塞着的.

手写版本2,抢不到锁就把自己放入队列并且阻塞自己

lock方法的改动

unlock方法的改动

这么一改,当任务执行时间过长也不怕,等待队列中的线程是被阻塞的,不会浪费cpu资源

版本三,增加可重入功能

整体思路:增加一个变量存放持有锁的线程,当持有锁的线程获取锁时直接把state+1,持有锁的线程要释放锁的时候就把state-1

    private int state = 0;private LinkedBlockingDeque<Thread> waiters = new LinkedBlockingDeque<>();//持有锁的线程private Thread holdThread;public void lock() {if (!tryLock()) { // 先抢锁,抢锁成功直接退出,抢锁失败加入则等待队列waiters.add(Thread.currentThread()); // 抢锁失败,加入等待队列中while (true) { // 自旋 进入:抢锁 -> 抢锁失败 -> 阻塞自己 -> 抢锁 的循环if (tryLock()) { // 抢锁waiters.poll(); // 抢锁成功,把自己从等待队列中移除return; //  退出循环}LockSupport.park(); // 抢锁失败,阻塞自己,等待被唤醒}}}public boolean tryLock() {Thread current = Thread.currentThread();if (holdThread != null && holdThread == current) {state++; // 如果获取锁的线程尝试再次获取锁,则state+1return true;}if (unsafe.compareAndSwapInt(this, stateOffset, 0, 1)) {holdThread = current; // 获取到锁就把holdThread=当前线程return true;}return false;}public void unlock() {state--;// 释放锁,直接把state-1if (state == 0) {//唤醒队列中第一个线程Thread peek = waiters.peek();LockSupport.unpark(peek);}}

测试代码

    public static void main(String[] args) throws InterruptedException {RenhaiLockV2Reentrant lock = new RenhaiLockV2Reentrant();Thread t1 = new Thread(() -> {lock.lock();logger.info("抢到锁");lock.lock();logger.info("抢到锁");lock.unlock();logger.info("解锁");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}lock.unlock();logger.info("解锁");});Thread t2 = new Thread(() -> {lock.lock();logger.info("抢到锁");lock.unlock();});t1.start();t2.start();t1.join();t2.join();}

测试结果,线程1是等线程0释放两次锁才抢到锁的,释放第二次锁之前线程阻塞1秒钟特意给线程1留出时间,然而那时候线程0还没释放完毕,还持有一把锁,所以即使阻塞一秒钟也没办法抢到锁

Connected to the target VM, address: '127.0.0.1:51910', transport: 'socket'
12:37:48.362 [Thread-0] INFO com.lz.demo.concurrent.renhai.RenhaiLockV2Reentrant - 抢到锁
12:37:48.366 [Thread-0] INFO com.lz.demo.concurrent.renhai.RenhaiLockV2Reentrant - 抢到锁
12:37:48.366 [Thread-0] INFO com.lz.demo.concurrent.renhai.RenhaiLockV2Reentrant - 解锁
12:37:49.369 [Thread-0] INFO com.lz.demo.concurrent.renhai.RenhaiLockV2Reentrant - 解锁
12:37:49.369 [Thread-1] INFO com.lz.demo.concurrent.renhai.RenhaiLockV2Reentrant - 抢到锁
0
Disconnected from the target VM, address: '127.0.0.1:51910', transport: 'socket'Process finished with exit code 0

人海源码系列-Reentrantlock相关推荐

  1. synchronized 和 reentrantlock 区别是什么_JUC源码系列之ReentrantLock源码解析

    目录 ReentrantLock 简介 ReentrantLock 使用示例 ReentrantLock 与 synchronized 的区别 ReentrantLock 实现原理 Reentrant ...

  2. c++ map 获取key列表_好未来Golang源码系列一:Map实现原理分析

    分享老师:学而思网校 郭雨田 一.map的结构与设计原理 golang中map是一个kv对集合.底层使用hash table,用链表来解决冲突 ,出现冲突时,不是每一个key都申请一个结构通过链表串起 ...

  3. 无码系列5.1 代码重构 消除重复代码

    1 前言 本文可以视为对ThoughtWorks高级顾问yuanyingjie关于"正交四原则"策略"消除重复"的"个人解读". 如有谬误, ...

  4. Spring源码系列- Spring Beans - 核心类的基本介绍

    Spring源码系列- Spring Beans - 核心类的基本介绍 读过上一篇文章的读者应该都能对Spring的体系结构有一个大致的了解,在结尾处,我也说过会从spring-beans包开始分析, ...

  5. java 源码系列 - 带你读懂 Reference 和 ReferenceQueue

    java 源码系列 - 带你读懂 Reference 和 ReferenceQueue https://blog.csdn.net/gdutxiaoxu/article/details/8073858 ...

  6. 高效阅读嵌入式源码系列一:静态分析神器understand软件基本操作

    系列文章目录 高效阅读嵌入式源码系列一:静态分析神器understand软件基本操作 高效阅读嵌入式源码系列二:understand阅读linux.uboot等源码 高效阅读嵌入式源码系列三:unde ...

  7. 从梁飞的微型rpc 细节说起--Dubbo源码系列解读(5)

    7年前,梁飞公布了一个微型的rpc,这个rpc核心就是一个类,2个方法,但重点我们要探讨是细节的设计和质量一些问题 package com.rpc;import java.io.ObjectInput ...

  8. Android 源码系列之二十通过反射解决在HuaWei手机出现Register too many Broadcast Receivers的crash

    转载请注明出处:http://blog.csdn.net/llew2011/article/details/79054457 Android开发适配问题一直是一个让人头疼的话题,由于国内很多厂商都有对 ...

  9. SpringMVC源码系列:HandlerMapping

    SpringMVC源码系列:HandlerMapping SpringMVC源码系列:AbstractHandlerMapping HandlerMapping接口是用来查找Handler的.在Spr ...

最新文章

  1. 程序员毕业两年,如何在帝都购房上车?
  2. phpgif图片包_php生成动态验证码gif图片
  3. cmos存储器中存放了_CMOS存储器中存放了计算机的一些参数和信息,其中不包含在内的是( )。_学小易找答案...
  4. 常用JS积累之获取节点高度(基于浏览器)
  5. AD19原理图背景栅格去掉(改为纯色)
  6. java 读取资源文件最详细解读
  7. linux卸载intel驱动程序,Linux的英特尔图形驱动程序调试工具删除Android支持
  8. u盘测试工具linux,MultiBootUSB简单测试工具 V7.5.0 升级版
  9. 2018.7.18 上半年课程总结 4- 高级英语
  10. android 时区表以及设置系统时区
  11. Python利用pptx模块三步将图片插入特定PPT模板
  12. 职场34个必备的沟通技巧
  13. win10系统重装之u盘装系统,u盘安装win10系统
  14. 卡塔兰(Catalan)数
  15. C#读取Word文件的方法
  16. iphone6 分辨率与适配
  17. 迅雷看看免费高清影视 v4.9.15.2156 绿色免费版
  18. 为什么总有人会认为斗地主靠运气就能打好?
  19. 计算机技术与软件专业技术资格有哪些?
  20. 如何展示您的数字作品集:来自创意招聘人员的建议

热门文章

  1. Spark Streaming 2.2.1 处理Kafka数据源的实战准备
  2. JS基础-Java Class类以及获取Class实例的三种方式
  3. Python实例30:程序员表达爱意的方式——一行代码画心
  4. 小学奥数题Java编程(2)
  5. 量化交易入门阶段——乖离率是个好指标吗?
  6. 专访百度副总裁景鲲:智能音箱进入黄金期,让小度助手像贾维斯一样无处不在...
  7. linux读取 第一个分区 命令,partprobe命令
  8. 拓嘉辰丰:为什么创业者入驻拼多多?八大优势揭晓!
  9. 多线程线程数经验公式
  10. 商场三十六计——第26计 “指桑骂槐”