1. 概述
锁(狭义指互斥锁),只是一个逻辑概念,起到线程间同步互斥的功能。同步,是指线程间的通信,通俗的讲就是线程t1释放锁后,其他线程要收到通知,可以争取锁;互斥,是指线程t1获得锁后,其他线程无法在获得锁。
就作者目前知识储备,所有锁的实现都离不开CAS,只有CPU提供了原子性操作,才有各种各样的锁的实现,例如synchronized、ReentrantLock等。因为本人不太懂C++,还没有阅读理解synchronized的源码部分,现在如果让我说出synchronized和ReentrantLock的区别,答案大概是synchonized是C++基于CAS实现的,而ReentrantLock是Java基于CAS实现的。看过几次AbstractQueuedSynchronizer的源码,但是感觉很难掌握其中之精髓,索性慢下来,从需求出发,一点点理解其设计思想。
2. 思路
设计一个Lock类,仅提供互斥锁的功能,不考虑可重入。那么API可以设计成
void lock();
void unlock();
从最朴素的念头出发,Lock类,持有一个变量state,可以是int,也可以是boolean。
- 当state==1/true时,代表锁已经被某线程持有了,其他线程调用lock()应该被阻塞
- 当state==0/false时,代表锁处于释放的状态,任意线程调用lock()将有机会获得锁
这里我们将state设计成int类型。
因为int变量无法提供CAS操作,所以Lock类要已持有一个VarHandle类型的STATE变量,提供对state的CAS操作。
假设当线程t1获得锁后,为了仅使t1.unlock()操作有效,应该在Lock类中提供一个currentThread变量,记录持有锁的当前线程。在unlock()中进行判断,忽略非持有锁线程的调用。
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
/**
* Created by ligc on 2022/5/6 22:16
*/
public class Lock {
private volatile int state;
private static final VarHandle STATE;
private Thread currentThread;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
STATE = l.findVarHandle(Lock.class, "state", int.class);
} catch (NoSuchFieldException e) {
throw new ExceptionInInitializerError(e);
} catch (IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
private boolean compareAndSetState(int expect, int newVal){
return STATE.compareAndSet(this, expect, newVal);
}
//在竞争锁时,lock使用死循环的方式进行CAS操作,也就是所谓的自旋,简单而粗暴。
public void lock() {
while (!compareAndSetState(0, 1));
currentThread = Thread.currentThread();
}
//在释放锁时,进行线程验证。无需自旋
public void unlock(){
if(Thread.currentThread() != currentThread){
return;
}
currentThread = null;
setState(); //这里没有必要进行CAS,因为执行到这里,意味着线程已经获得了锁
}
private void setState(){
state = 0;
}
}
3. 对比
对比一下synchronized关键字,和我们自定义的Lock的速度。5个线程 * 每个线程自增1000 万次,代码如下:
/**
* Created by ligc on 2022/5/6 22:28
*/
public class LockTest {
static final int MAX = 1000 * 10000;
static volatile int x = 0;
static Lock lock = new Lock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Worker());
Thread t2 = new Thread(new Worker());
Thread t3 = new Thread(new Worker());
Thread t4 = new Thread(new Worker());
Thread t5 = new Thread(new Worker());
long start = System.currentTimeMillis();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
long end = System.currentTimeMillis();
System.out.println((end - start) + " ms --> " + x);
}
static synchronized void increase(){
x ++;
}
// static void increase(){
// lock.lock();
// x ++;
// lock.unlock();
// }
static class Worker implements Runnable{
@Override
public void run() {
for(int i = 0; i < MAX; i ++){
increase();
}
}
}
}
结果如下:
synchronized
3026 ms --> 50000000
Lock
12860 ms --> 50000000
结果倒是正确,仅仅差了4倍的性能而已,完败~待续