人海源码系列-Reentrantlock
今天讲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相关推荐
- synchronized 和 reentrantlock 区别是什么_JUC源码系列之ReentrantLock源码解析
目录 ReentrantLock 简介 ReentrantLock 使用示例 ReentrantLock 与 synchronized 的区别 ReentrantLock 实现原理 Reentrant ...
- c++ map 获取key列表_好未来Golang源码系列一:Map实现原理分析
分享老师:学而思网校 郭雨田 一.map的结构与设计原理 golang中map是一个kv对集合.底层使用hash table,用链表来解决冲突 ,出现冲突时,不是每一个key都申请一个结构通过链表串起 ...
- 无码系列5.1 代码重构 消除重复代码
1 前言 本文可以视为对ThoughtWorks高级顾问yuanyingjie关于"正交四原则"策略"消除重复"的"个人解读". 如有谬误, ...
- Spring源码系列- Spring Beans - 核心类的基本介绍
Spring源码系列- Spring Beans - 核心类的基本介绍 读过上一篇文章的读者应该都能对Spring的体系结构有一个大致的了解,在结尾处,我也说过会从spring-beans包开始分析, ...
- java 源码系列 - 带你读懂 Reference 和 ReferenceQueue
java 源码系列 - 带你读懂 Reference 和 ReferenceQueue https://blog.csdn.net/gdutxiaoxu/article/details/8073858 ...
- 高效阅读嵌入式源码系列一:静态分析神器understand软件基本操作
系列文章目录 高效阅读嵌入式源码系列一:静态分析神器understand软件基本操作 高效阅读嵌入式源码系列二:understand阅读linux.uboot等源码 高效阅读嵌入式源码系列三:unde ...
- 从梁飞的微型rpc 细节说起--Dubbo源码系列解读(5)
7年前,梁飞公布了一个微型的rpc,这个rpc核心就是一个类,2个方法,但重点我们要探讨是细节的设计和质量一些问题 package com.rpc;import java.io.ObjectInput ...
- Android 源码系列之二十通过反射解决在HuaWei手机出现Register too many Broadcast Receivers的crash
转载请注明出处:http://blog.csdn.net/llew2011/article/details/79054457 Android开发适配问题一直是一个让人头疼的话题,由于国内很多厂商都有对 ...
- SpringMVC源码系列:HandlerMapping
SpringMVC源码系列:HandlerMapping SpringMVC源码系列:AbstractHandlerMapping HandlerMapping接口是用来查找Handler的.在Spr ...
最新文章
- 程序员毕业两年,如何在帝都购房上车?
- phpgif图片包_php生成动态验证码gif图片
- cmos存储器中存放了_CMOS存储器中存放了计算机的一些参数和信息,其中不包含在内的是( )。_学小易找答案...
- 常用JS积累之获取节点高度(基于浏览器)
- AD19原理图背景栅格去掉(改为纯色)
- java 读取资源文件最详细解读
- linux卸载intel驱动程序,Linux的英特尔图形驱动程序调试工具删除Android支持
- u盘测试工具linux,MultiBootUSB简单测试工具 V7.5.0 升级版
- 2018.7.18 上半年课程总结 4- 高级英语
- android 时区表以及设置系统时区
- Python利用pptx模块三步将图片插入特定PPT模板
- 职场34个必备的沟通技巧
- win10系统重装之u盘装系统,u盘安装win10系统
- 卡塔兰(Catalan)数
- C#读取Word文件的方法
- iphone6 分辨率与适配
- 迅雷看看免费高清影视 v4.9.15.2156 绿色免费版
- 为什么总有人会认为斗地主靠运气就能打好?
- 计算机技术与软件专业技术资格有哪些?
- 如何展示您的数字作品集:来自创意招聘人员的建议
热门文章
- Spark Streaming 2.2.1 处理Kafka数据源的实战准备
- JS基础-Java Class类以及获取Class实例的三种方式
- Python实例30:程序员表达爱意的方式——一行代码画心
- 小学奥数题Java编程(2)
- 量化交易入门阶段——乖离率是个好指标吗?
- 专访百度副总裁景鲲:智能音箱进入黄金期,让小度助手像贾维斯一样无处不在...
- linux读取 第一个分区 命令,partprobe命令
- 拓嘉辰丰:为什么创业者入驻拼多多?八大优势揭晓!
- 多线程线程数经验公式
- 商场三十六计——第26计 “指桑骂槐”