动力节点首页 全国咨询热线:400-8080-105

绑定手机号,登录
手机号

验证码

微信登录
手机号登录
手机号

验证码

30天自动登录
微信登录与注册
微信扫码登录与注册

扫码关注微信公众号完成登录与注册
手机号登录
首页 > 文章

简述5种线程锁

05-24 10:01 378浏览
举报 T字号
  • 大字
  • 中字
  • 小字

线程锁的存在是为了保证共享资源在任意时间里,只有一个线程访问,这样就可以避免多线程导致共享数据错乱的问题。多线程带来效率提升的同时,也会经常出现资源占用的问题,线程锁的出现可以保证同一时间一个对象只有一个线程在访问,以此来保证数据的安全性。然而,线程锁种类繁多,很多时候我们容易混淆概念,下面我们来简单介绍一下常见的5种线程锁。

1.公平锁与非公平锁

从其它等待中的线程是否按顺序获取锁的角度划分。

公平锁:是指多个线程在等待同一个锁时,必须按照申请锁的先后顺序来一次获得锁。

公平锁的好处是等待锁的线程不会饿死,但是整体效率相对低一些;非公平锁的好处是整体效率相对高一些,但是有些线程可能会饿死或者说很早就在等待锁,但要等很久才会获得锁。其中的原因是公平锁是严格按照请求所的顺序来排队获得锁的,而非公平锁时可以抢占的,即如果在某个时刻有线程需要获取锁,而这个时候刚好锁可用,那么这个线程会直接抢占,而这时阻塞在等待队列的线程则不会被唤醒。

在Java中,公平锁可以通过 new ReentrantLock (true) 来实现;非公平锁可以通过 new ReentrantLock (false) 或者默认构造函数 new ReentrantLock () 实现。

2.互斥锁

从能否有多个线程持有同一把锁的角度划分

互斥锁的概念非常简单,也就是我们常说的同步,即一次最多只能有一个线程持有的锁,当一个线程持有该锁的时候其它线程无法进入上锁的区域。在 Java 中 synchronized 就是互斥锁,从宏观概念来讲,互斥锁就是通过悲观锁的理念引出来的,而非互斥锁则是通过乐观锁的概念引申的。

3. 重入锁(递归锁)与 不可重入锁(自旋锁)

从一个线程能否递归获取自己的锁的角度划分

我们知道,一条线程若想进入一个被上锁的区域,首先要判断这个区域的锁是否已经被某条线程所持有。如果锁正在被持有那么线程将等待锁的释放,但是这就引发了一个问题,我们来看这样一段简单的代码:

public class ReentrantDemo {
private Lock mLock;
public ReentrantDemo(Lock mLock) {
this.mLock = mLock;
}
public void outer() {
mLock.lock();
inner();
mLock.unlock();
}
public void inner() {
mLock.lock();
// do something
mLock.unlock();
}
}

当线程 A 调用 outer () 方法的时候,会进入使用传进来 mlock 实例来进行 mlock.lock () 加锁,此时 outer () 方法中的这片区域的锁 mlock 就被线程 A 持有了,当线程 B 想要调用 outer () 方法时会先判断,发现这个 mlock 这把锁被其它线程持有了,因此进入等待状态。我们现在不考虑线程 B,单说线程 A,线程 A 进入 outer () 方法后,它还要调用 inner () 方法,并且 inner () 方法中使用的也是 mlock () 这把锁,于是接下来有趣的事情就来了。按正常步骤来说,线程 A 先判断 mlock 这把锁是否已经被持有了,判断后发现这把锁确实被持有了,但是可笑的是,是 A 自己持有的。那你说 A 能否在加了 mlock 锁的 outer () 方法中调用加了 mlock 锁的 inner 方法呢?答案是如果我们使用的是可重入锁,那么递归调用自己持有的那把锁的时候,是允许进入的。

可重入锁:可以再次进入方法 A,就是说在释放锁前此线程可以再次进入方法 A(方法 A 递归)。

不可重入锁(自旋锁):不可以再次进入方法 A,也就是说获得锁进入方法 A 是此线程在释放锁前唯一的一次进入方法 A。

4.悲观锁与乐观锁

悲观锁是就是悲观思想,即认为读少写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会 block 直到拿到锁。java 中的悲观锁就是 Synchronized,AQS 框架下的锁则是先尝试 cas 乐观锁去获取锁,获取不到,才会转换为悲观锁,如 RetreenLock。

乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读 - 比较 - 写的操作。

5.共享锁、排它锁

共享锁和排它锁多用于数据库中的事物操作,主要针对读和写的操作。而在 Java 中,对这组概念通过 ReentrantReadWriteLock 进行了实现,它的理念和数据库中共享锁与排它锁的理念几乎一致,即一条线程进行读的时候,允许其他线程进入上锁的区域中进行读操作;当一条线程进行写操作的时候,不允许其他线程进入进行任何操作。即读 + 读可以存在,读 + 写、写 + 写均不允许存在

共享锁:也称读锁或 S 锁。如果事务 T 对数据 A 加上共享锁后,则其他事务只能对 A 再加共享锁,不能加排它锁。获准共享锁的事务只能读数据,不能修改数据。

排它锁:也称独占锁、写锁或 X 锁。如果事务 T 对数据 A 加上排它锁后,则其他事务不能再对 A 加任何类型的锁。获得排它锁的事务即能读数据又能修改数据。

当然,线程锁的分类不只有这一种方式,我们也可以根据不同的分类方法来对线程锁进行分类,在动力节点在线的视频课程中对线程锁的知识讲的很透彻,我们只有熟悉线程锁的各个属性才能对各种线程锁进行合理的分类。

0人推荐
共同学习,写下你的评论
0条评论
代码小兵652
程序员代码小兵652

113篇文章贡献392215字

相关课程 更多>

作者相关文章更多>

推荐相关文章更多>

Java面试题及答案整理

代码小兵66904-21 20:01

6道经典算法面试题

杨晶珍05-12 16:39

简述Spring MVC的核心组件

代码小兵49806-11 16:26

SpringMVC 中的组件

代码小兵49806-11 16:28

Spring常见面试题

代码小兵92504-17 16:07

发评论

举报

0/150

取消