摘要
本系列主要记下自己阅读ReentrantLock类时,结合源码和网上资源所得感悟。本章主要讨论ReentrantLock的由来和如何在JDK层面上简单的去实现一个锁。
synchronized和ReentrantLock
这是一个很典型的面试题。这两个有什么区别?
这需要按照JDK的版本来看,在1.6版本之前,synchronized的主要依赖操作系统的指令去实现。性能不好。于是大神Doug Lea在1.5版本提出了ReentrantLock,除了CAS(Compare And Swap)操作外,其所有的操作都JDK层面实现,性能肯定是要强于当时的synchronized。在1.6版及以上,JDK开发人员对synchronized关键字的实现进行优化。目前synchronized的性能已经与ReentrantLock相差无几(笔者没有做过实测,如果有同学测试过,可以留言给作者),但是ReentrantLock还有很多好用API。
实现锁的思路
1、自旋
class Lock{
private volatile int state = 0;
//上锁
void lock(){
//成功获取锁则将state置1,否则自旋
while(!compareAndSetState(0, 1)){
}
}
//释放锁
void unlock(){
state = 0;
}
}
上面是最简单的一种锁了,但是带来一个典型问题,当线程获取自旋时,并没有释放CPU资源,当线程数量较多时,会导致风扇爆炸。
2、自旋+yield()
class Lock{
private volatile int state = 0;
//上锁
void lock(){
//成功获取锁则将state置1,否则自旋
while(!compareAndSetState(0, 1)){
yield();
}
}
//释放锁
void unlock(){
state = 0;
}
}
yield()可以让线程重新回到就绪态,但是线程下次调度由CPU决定,会白白增加我们代码的工作时间。这对于我们的代码肯定是不合适的。
3、自旋+sleep()
class Lock{
private volatile int state = 0;
//上锁
void lock(){
//成功获取锁则将state置1,否则自旋
while(!compareAndSetState(0, 1)){
sleep(10);
}
}
//释放锁
void unlock(){
state = 0;
}
}
sleep()让当前线程进入等待状态,但是等待的时间极难控制,也不利于使用。
4、自旋+park()
class Lock{
private volatile int state = 0;
private Queue queue;
//上锁
void lock(){
//成功获取锁则将state置1,否则自旋
while(!compareAndSetState(0, 1)){
park();
}
}
//释放锁
void unlock(){
if(!queue.isEmpty()) {
unpark(queue.poll())
} elae{
state = 0;
}
}
park(){
queue.add(currentThread);
releaseCPU();
}
}
通过自旋+park()的方式基本可以实现一个简单的锁。当锁被T1占用时,将T2放入队列中,当T1释放锁时,从队列中取出下一个线程。同时这也是ReentrantLock的基本实现思想。