在并发编程的领域中,有两个核心问题,一个是互斥,即同一时刻只有一个线程访问共享资,一个是同步,即线程之间如何通讯,协作,这两大问题,管程都能够实现,在java jdk并发包通过Lock和Condition两个接口实现管程,其中lock实现互斥,condition用于解决同步问题
为什么JDK添加了Lock锁
我们知道在JDK1.5版本,synchronized性能不如SDK里面的Lock,但是在jdk1.6版本对synchronized进行了优化,同时推荐使用synchronized,此时为什么要要有Lock呢
我们知道synchronized申请资源的时候,如果申请不到,线程直接进入阻塞状态,而线程进入阻塞状态,什么都不能干,也释放不了已经持有的资源,但是我们希望的是
对于“不可抢占”这个条件,占用部分资源的线程在申请其他资源的时候,如果申请不到
可以主动释放它占有的资源,这样不可抢占就可以破坏
此时如果我们要重新设计互斥锁去解决这个问题,可以有三种方案
看到这里我们就知道为什么JDK还有创建Lock接口了
// 支持中断的API
void lockInterruptibly()
throws InterruptedException;
// 支持超时的API
boolean tryLock(long time, TimeUnit unit)
throws InterruptedException;
// 支持非阻塞获取锁的API
boolean tryLock();
如何保证可见性
我们知道synchronized之所以能保证可见性是因为有一条synchronized相关规则,synchronized的解锁Happens-Before于后续对这个锁的加锁,我们看看下面例子是否也能够保证可见性
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,如下规则
class SampleLock {
volatile int state;
// 加锁
lock() {
// 省略代码无数
state = 1;
}
// 解锁
unlock() {
// 省略代码无数
state = 0;
}
}
所以说线程2能够看到value的正确结果。
什么是可重入锁
我们看到ReentrantLock,这个顾名思义就是可以看出是可重入锁,即线程可以重复获取同一把锁,如下面例子
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的时候,你会发现他的有个构造函数,默认是非公平锁,如下面
//无参构造函数:默认非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//根据公平策略参数创建锁
public ReentrantLock(boolean fair){
sync = fair ? new FairSync()
: new NonfairSync();
}
我们知道锁对应一个等待队列,如果一个线程没有获取到锁,就会进入等待队列,当有我线程释放的时候,就会唤醒等待队列的线程,如果是公平锁,唤醒的策略就是谁等待的时间长,就唤醒谁,很公平,如果是非公平锁,则不提供这个公平保证,有可能等待短的线程可能会被唤醒。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有