Java并发:重入锁 ReentrantLock(二)
一、理解锁的实现原理
1. 用wait()去实现一个lock方法,wait()要和synchronized同步关键字一起去使用的,直接使用wait方法会直接报IllegalMonitorStateException错误,使用wait方法实现一个lock,还要使用synchronized是多此一举的。
1 public void lock() {
2 try {
3 wait(); // 需要搭配synchronized关键字在去使用的
4 } catch (InterruptedException e) {
5 e.printStackTrace();
6 }
7 }
不搭配synchronized关键字会报错。

wait源码注释说明了需要搭配synchronized关键字。

2. 使用sleep来模拟线程阻塞,可以通过很大的数字进行无限阻塞来达到类似的效果,但是这里是有问题,需要传入一个很大的数字,还有sleep之后如何唤醒线程,所以使用sleep也不合适。
3. 使用while循环,不断轮询加锁的状态
1 public void lock() {
2 // 判断当前线程是否要阻塞
3 /*
4 while (state != 0) {}
5 state = 1;
6 */
7 while (!unsafe.compareAndSwapInt(this, stateOffset, 0, 1)) {
8 System.out.println(Thread.currentThread().getName() + "正在加锁");
9 }
10 System.out.println(Thread.currentThread().getName() + "加到锁了");
11 }
当有多个线程同时使用这个lock锁,,其中一个线程抢到了锁,另外的线程就会不断的while循环获取锁,这样会耗费CPU的资源。
1 new Thread(new Runnable() {
2 @Override
3 public void run() {
4 lock.lock();
5 drawMoney();
6 lock.unlock();
7 }
8 }, "线程1/2/3").start();
4. 使用LockSupport.park/unpark去阻塞/解阻塞线程。

二、深入ReentrantLock源码
1 private static final ReentrantLock lock = new ReentrantLock();
2
3 public static void main(String[] args) {
4 new Thread(new Runnable() {
5 @Override
6 public void run() {
7 lock.lock();
8 drawMoney();
9 lock.unlock();
10 }
11 }, "线程一").start();
12 }
查看ReentrantLock.lock方法
1 /**
2 * 获得锁。
3 * 如果其他线程没有持有锁,则获取该锁并立即返回,将锁持有计数设置为 1。
4 * 如果当前线程已经持有锁,那么持有计数加一并且该方法立即返回。
5 * 如果该锁被另一个线程持有,那么当前线程将因线程调度目的而被禁用并处于休眠状态,直到获得该锁为止,此时锁持有计数设置为 1。
6 */
7 public void lock() {
8 sync.lock();
9 }
这个lock方法有两种实现,一个是公平锁,一个是非公平锁。

先看公平锁FairSync,这个acquire方法是加锁。

acquire方法会去尝试加把锁,若果尝试加锁成功了,这个方法就会直接返回了,线程就会继续执行。若是没有成功,就会添加到等待队列中(Node),排队以独占不间断模式获取锁的状态。
进入尝试加锁的tryAcquire方法,这个方法也有公平锁、非公平锁的实现,先看公平锁的。

c是锁的状态,c=0就是这把锁没人占用,就会通过cas修改锁的状态,并设置这个锁属于当前线程,在这之前要hasQueuedPredecessors判断有没有人在排队。这个c可能是1,也可能是2,也可以是3,取决于你线程中加锁的次数(可重入)。后面还会检查当前线程是不是这把锁的线程持有者,是线程的持有者则继续设置state状态,超出最大锁定计数则会报错。前面的两个if条件没有满足,就会返回false,加锁失败,加入等待队列排队。

hasQueuedPredecessors方法,需要注意的是在高并发状态下,头结点不等于尾结点,但是头结点的下一个节点s为空,这是因为可能在头连接在链接一个等待线程的结点的过程还没完成,所以h.next为null,或者是s节点的线程不是当前线程,则判断为队列中有线程在排队,返回true。
没有加锁成功,就会添加到等待队列,并由acquireQueued方法进行park,阻塞线程。

addWaiter方法先构造的同步节点,判断尾节点是否为空,不为空就通过CAS把新的结点加入到队列尾部。加入成功返回结点node给acquireQueued方法。

如果尾节点为空,则看enq方法,for循环不断地去判断队列是否需要重新初始化并把node结点加入到队尾。

有多个线程竞争同一把锁,因为队列里首节点拿到锁以后,就会出队列,这时候队列为空,就需要new一个空结点作为队列的head结点,再把等待的新节点,添加到队尾,


Java并发:重入锁 ReentrantLock(二)的更多相关文章
- 轻松学习java可重入锁(ReentrantLock)的实现原理
转载自https://blog.csdn.net/yanyan19880509/article/details/52345422,(做了一些补充) 前言 相信学过java的人都知道 synchroni ...
- 轻松学习java可重入锁(ReentrantLock)的实现原理(转 图解)
前言 相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么 ...
- java 可重入锁ReentrantLock的介绍
一个小例子帮助理解(我们常用的synchronized也是可重入锁) 话说从前有一个村子,在这个村子中有一口水井,家家户户都需要到这口井里打水喝.由于井水有限,大家只能依次打水.为了实现家家有水喝,户 ...
- java可重入锁reentrantlock
public class ReentrantDemo { //重入锁 保护临界区资源count,确保多线程对count操作的安全性 /*public static ReentrantLock rtlo ...
- Java 重入锁 ReentrantLock 原理分析
1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...
- Java 显示锁 之 重入锁 ReentrantLock(七)
ReentrantLock 重入锁简介 重入锁 ReentrantLock,顾名思义,就是支持同一个线程对资源的重复加锁.另外,该锁还支持获取锁时的公平与非公平性的选择. 重入锁 ReentrantL ...
- synchronized关键字,Lock接口以及可重入锁ReentrantLock
多线程环境下,必须考虑线程同步的问题,这是因为多个线程同时访问变量或者资源时会有线程争用,比如A线程读取了一个变量,B线程也读取了这个变量,然后他们同时对这个变量做了修改,写回到内存中,由于是同时做修 ...
- 17_重入锁ReentrantLock
[概述] 重入锁可以完全代替synchronized关键字. 与synchronized相比,重入锁ReentrantLock有着显示的操作过程,即开发人员必须手动指定何时加锁,何时释放锁,所以重入锁 ...
- Java不可重入锁和可重入锁的简单理解
基础知识 Java多线程的wait()方法和notify()方法 这两个方法是成对出现和使用的,要执行这两个方法,有一个前提就是,当前线程必须获其对象的monitor(俗称“锁”),否则会抛出Ille ...
- Java并发(九):重入锁 ReentrantLock
先做总结: 1.为什么要用ReentrantLock? (1)ReentrantLock与synchronized具有相同的功能和内存语义: (2)synchronized是重量级锁,性能不好.Ree ...
随机推荐
- 羽夏看Win系统内核——简述
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...
- PTA面向对象程序设计6-3 面积计算器(函数重载)
实现一个面积计算器,它能够计算矩形或长方体的面积. 函数接口定义: int area(int x, int y); int area(int x, int y, int z); 第一个函数计算长方形的 ...
- 解决wampserver无法启动问题
如果无法启动,找不到原因.直接依次点击打开到:控制面板--管理工具--事件查看器--windows日志--应用程序,查看对应进程错误信息对症下药即可. 我这个错误就是8099端口错误,运行cmd命令, ...
- Java基础系列(32)- 递归讲解
递归 A方法调用B方法,我们很容易理解 递归就是:A方法调用A方法!就是自己调用自己 利用递归可以用简单的程序来解决一些复杂的问题.它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题 ...
- Java基础系列(18)- if选择结构
if单选择结构 我们很多时候需要去判断一个东西是否可行,然后我们才去执行,这样一个过程在程序中用if语句来表示 语法 if (布尔表达式){ //如果布尔表达式为True将执行的语句 } packag ...
- python 深度学习 库文件安装出错汇总
Cython_bbox FairMOT | win10下cython-bbox安装的心酸之路_是阳阳呀的博客-CSDN博客 swig 安装polyiou.py https://blog.csdn.ne ...
- php-抽象工厂
目标:创建有依赖关系的实例;(套餐) <?php //抽象类 食物 interface IAllayFood { function Allay(); } interface IDrinkFood ...
- Ubuntu学习之alias命令
Ubuntu学习之alias命令 1.1 alias功能介绍 当我们经常需要在命令窗键入复杂冗长的命令时,alias就派上用场啦.alias允许用户为命令创建简单的名称或缩写,哪怕这个缩写只有一个字符 ...
- 鸿蒙内核源码分析(字符设备篇) | 字节为单位读写的设备 | 百篇博客分析OpenHarmony源码 | v67.01
百篇博客系列篇.本篇为: v67.xx 鸿蒙内核源码分析(字符设备篇) | 字节为单位读写的设备 | 51.c.h.o 文件系统相关篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一 ...
- 鸿蒙内核源码分析(异常接管篇) | 社会很单纯 , 复杂的是人 | 百篇博客分析OpenHarmony源码 | v39.03
百篇博客系列篇.本篇为: v39.xx 鸿蒙内核源码分析(异常接管篇) | 社会很单纯,复杂的是人 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU ...