前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >为什么会有Lock

为什么会有Lock

作者头像
小土豆Yuki
发布于 2020-12-16 01:43:29
发布于 2020-12-16 01:43:29
48200
代码可运行
举报
文章被收录于专栏:洁癖是一只狗洁癖是一只狗
运行总次数:0
代码可运行

在并发编程的领域中,有两个核心问题,一个是互斥,即同一时刻只有一个线程访问共享资,一个是同步,即线程之间如何通讯,协作,这两大问题,管程都能够实现,在java jdk并发包通过Lock和Condition两个接口实现管程,其中lock实现互斥,condition用于解决同步问题

为什么JDK添加了Lock锁

我们知道在JDK1.5版本,synchronized性能不如SDK里面的Lock,但是在jdk1.6版本对synchronized进行了优化,同时推荐使用synchronized,此时为什么要要有Lock呢

我们知道synchronized申请资源的时候,如果申请不到,线程直接进入阻塞状态,而线程进入阻塞状态,什么都不能干,也释放不了已经持有的资源,但是我们希望的是

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
对于“不可抢占”这个条件,占用部分资源的线程在申请其他资源的时候,如果申请不到
可以主动释放它占有的资源,这样不可抢占就可以破坏

此时如果我们要重新设计互斥锁去解决这个问题,可以有三种方案

  • 能够中断,synchronized持有A锁后,如果尝试获取锁B,那么线程就进入阻塞状态,但是一旦发生死锁,就没有任何机会唤醒持有锁的线程,但是如果此时阻塞的线程可以相应中断信号,也就是说我们给阻塞线程发送中断信号的时候,能够唤醒他,那么只有锁A就会有机会释放,这样就可以破坏不可抢占条件
  • 支持超时,如果线程在一定时间内不能获取到锁,可以不进入阻塞,而是返回一个错误,此时就有机会释放持有的锁,这样也可以破坏不可抢占条件
  • 非阻塞获取锁,当尝试获取锁失败的时候,却不进入阻塞条件,而是直接返回,那这个线程也有机会释放持有的锁,这样也能够破坏不可抢占条件

看到这里我们就知道为什么JDK还有创建Lock接口了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 支持中断的API
void lockInterruptibly()
throws InterruptedException;
// 支持超时的API
boolean tryLock(long time, TimeUnit unit)
throws InterruptedException;
// 支持非阻塞获取锁的API
boolean tryLock();

如何保证可见性

我们知道synchronized之所以能保证可见性是因为有一条synchronized相关规则,synchronized的解锁Happens-Before于后续对这个锁的加锁,我们看看下面例子是否也能够保证可见性

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

class X {
private final Lock rtl =
new ReentrantLock();
int value;
public void addOne() {
// 获取锁
    rtl.lock();  
try {
value+=1;
    } finally {
// 保证锁能释放
      rtl.unlock();
    }
  }
}

上面value+=1可以肯定的保证其他线程看到value的正确的值,因为他利用volatile相关的Happens-Before规则,因为在ReentantLock里面有一个被volatile修饰的state变量,这样就可以保证state的可见性,比如当获取到锁的时候,会先读写state的值,解锁的时候也会读state的值,也就是说,在执行value+=1之前,程序先会读写一次volatile变量state,在执行value+=1之后,有会读写一次volatile变量state,如下规则

  • 顺序规则,线程T1,value+=1Happens-Before释放锁的操作unLock
  • volatile变量,由于state=1会先读取state,所以线程T1的unlock操作Happens-Before线程T2的lock操作
  • 传递性规则,线程T1的value+=1 Happens-Before线程T2的Lock操作
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

class SampleLock {
volatile int state;
// 加锁
lock() {
// 省略代码无数
    state = 1;
  }
// 解锁
  unlock() {
// 省略代码无数
    state = 0;
  }
}

所以说线程2能够看到value的正确结果。

什么是可重入锁

我们看到ReentrantLock,这个顾名思义就是可以看出是可重入锁,即线程可以重复获取同一把锁,如下面例子

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

class X {
private final Lock rtl =
new ReentrantLock();
int value;
public int get() {
// 获取锁
    rtl.lock();try {
return value;
    } finally {
// 保证锁能释放
      rtl.unlock();
    }
  }
public void addOne() {
// 获取锁
    rtl.lock();  
try {
value = 1 + get();} finally {
// 保证锁能释放
      rtl.unlock();
    }
  }
}

当线程T1进入到1的时候,已经获取到了rt1的锁,然后调用2的时候再次对rt1加锁,此时如果锁rt1是可重入锁,那么线程可以再次加锁成功,如果rt1不是可重入锁,就会阻塞,

当然我们可能还听过,可重入函数,即多个线程可以同时调用函数,每个线程都能够得到正确的结果,同时在一个线程内支持线程切换,最终结果都是正确的,可以看出可重入函数是线程安全的

公平锁和非公平锁

在使用ReentantLock的时候,你会发现他的有个构造函数,默认是非公平锁,如下面

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

//无参构造函数:默认非公平锁
public ReentrantLock() {
    sync = new NonfairSync();
}
//根据公平策略参数创建锁
public ReentrantLock(boolean fair){
    sync = fair ? new FairSync() 
                : new NonfairSync();
}

我们知道锁对应一个等待队列,如果一个线程没有获取到锁,就会进入等待队列,当有我线程释放的时候,就会唤醒等待队列的线程,如果是公平锁,唤醒的策略就是谁等待的时间长,就唤醒谁,很公平,如果是非公平锁,则不提供这个公平保证,有可能等待短的线程可能会被唤醒。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 洁癖是一只狗 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
这一次彻底搞懂Java的Lock接口到底有什么用!
这俩问题,管程都能一把梭。JUC是通过Lock、Condition接口实现的管程:
JavaEdge
2021/04/28
4860
这一次彻底搞懂Java的Lock接口到底有什么用!
深入理解java并发锁
确保线程安全最常见的做法是利用锁机制(Lock、sychronized)来对共享数据做互斥同步,这样在同一个时刻,只有一个线程可以执行某个方法或者某个代码块,那么操作必然是原子性的,线程安全的。
Java宝典
2021/01/14
4400
深入理解java并发锁
AQS扩展与ReentrantLock实现
在ReentrantLock内部实现了公平锁(FairSync)和非公平锁(NonfairSync)两套锁实现, 两者区别可以参考AQS介绍.
一个架构师
2022/06/20
2640
AQS扩展与ReentrantLock实现
Java并发面试题&知识点总结(下篇)
volatile 是 Java 中用于实现共享变量可见性的关键字。它具有以下特点:
栗筝i
2023/11/15
3390
高并发编程-ReentrantLock公平锁深入解析
ReentrantLock是一个可重入的互斥锁,它不但具有synchronized实现的同步方法和同步代码块的基本行为和语义,而且具备很强的扩展性。ReentrantLock提供了公平锁和非公平锁两种实现,在默认情况下构造的ReentrantLock实例是非公平锁,可以在创建ReentrantLock实例的时候通过指定公平策略参数来指定是使用公平锁还是非公平锁。多线程竞争访问同一资源的时,公平锁倾向于将访问权授予等待时间最长的线程,但需要明确的是公平锁不能保证线程调度的公平性。和非公平锁相比,公平锁在多线程访问时总体吞吐量偏低,但是获得锁和保证锁分配的均衡性差异较小。本篇将基于JDK7深入源码解析公平锁的实现原理。
JavaQ
2018/09/14
9650
ReentrantLock知识点梳理
接下来几篇文章会对JUC并发包里面的锁工具类做下梳理,如:ReentrantLock、
微观技术
2020/08/20
3700
通过一个故事理解可重入锁的机制
在一个村子里面,有一口井水,水质非常的好,村民们都想打井里的水。这井只有一口,村里的人那么多,所以得出个打水的规则才行。村长绞尽脑汁,最终想出了一个比较合理的方案,咱们来仔细的看看聪明的村长大人的智慧。
小勇DW3
2018/08/30
9500
通过一个故事理解可重入锁的机制
一次说清,Java 中的各种锁和 CAS 经典面试题
如果说快速理解多线程有什么捷径的话,那本文介绍的各种锁无疑是其中之一,它不但为我们开发多线程程序提供理论支持,还是面试中经常被问到的核心面试题之一。因此下面就让我们一起深入地学习一下这些锁吧。
CSDN技术头条
2020/03/25
1.1K0
ReentrantLock及AQS原理
之前我们学习了synchronized在偏向锁、轻量级锁、重量级锁下的不同的原理(并发基础之Synchronized原理),这篇文章我们来讲讲另一个锁Lock的原理。
崩天的勾玉
2021/12/20
3540
ReentrantLock及AQS原理
【Java并发系列】AQS原理
Java供用户直接使用的锁有"synchronized同步锁"和"JUC包中的锁"。
章鱼carl
2022/03/31
3840
【Java并发系列】AQS原理
java架构之路(多线程)AQS之ReetrantLock显示锁的使用和底层源码解读
  说完了我们的synchronized,这次我们来说说我们的显示锁ReetrantLock。
小菜的不能再菜
2020/02/21
4630
高并发编程必备基础(上)
借用Java并发编程实践中的话"编写正确的程序并不容易,而编写正常的并发程序就更难了",相比于顺序执行的情况,多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作的顺序是不可预期的,本文算是对多线程情况下同步策略的一个一个简单介绍。
加多
2018/09/06
4480
高并发编程必备基础(上)
一文带你读懂JDK源码:synchronized
Java提供的常用同步手段之一就是sychronized关键字,synchronized 是利用锁的机制来实现同步的。
后台技术汇
2022/05/28
2920
一文带你读懂JDK源码:synchronized
彻底理解Java并发:ReentrantLock锁
synchronized 线程等待时间过长,获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,这将极大的影响程序执行效率。
栗筝i
2022/12/01
6820
彻底理解Java并发:ReentrantLock锁
并发编程系列之ReentrantLock用法简介
ReentrantLock是实现底层的Lock接口的可重入锁实现。支持公平锁模式和非公平锁模式。
SmileNicky
2021/12/02
3.1K0
并发编程系列之ReentrantLock用法简介
【进击面试_03】Java 并发
☞ JMM 是什么   JMM(Java 内存模型:Java Memory Model,简称 JMM)本身是一种抽象的概念并不真实存在,它描述的是一组规则或规范,定义了程序中各个共享变量的访问规则,即在虚拟机中将变量存储到内存和从内存读取变量这样的底层细节。   根据 JMM 的设计,系统存在一个主内存(Main Memory),Java 中所有实例变量都储存在主存中,对于所有线程都是共享的。每个线程都有自己的工作内存(Working Memory)是私有数据区域。线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝。不同的线程间无法访问对方的工作内存,线程间的通信必须通过主内存来完成。
Demo_Null
2021/03/04
3050
【进击面试_03】Java 并发
多线程方向的锁
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
suveng
2019/09/17
4050
多线程方向的锁
Java 中ReentrantLock快速入门
ReentrantLock是Java中提供的一种可重入锁的实现。它的实现原理主要基于AQS(AbstractQueuedSynchronizer)框架。
jack.yang
2025/04/05
860
Java并发编程(六)---lock
前面几篇文章,我们学习了synchronized的相关知识,以及死锁的发生条件以及避免的方式,其中有一种破坏死锁的方式就是破坏不可抢占条件,通过synchronzied不能实现的,因为synchronized在申请资源的时候,如果申请不到就只能进入阻塞状态,啥都干不了,也不能中断。所以只能通过本期的主角lock 来处理。
码农飞哥
2021/08/18
2850
JUC并发知识_并行与并发
重入锁实现可重入性原理或机制是:每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。
全栈程序员站长
2022/09/21
3100
JUC并发知识_并行与并发
相关推荐
这一次彻底搞懂Java的Lock接口到底有什么用!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验