AQS理解之二,自己设计一个锁
一,实现锁的条件
首先我们想一想,如果我们自己实现一个类似于java中的锁,我们可能需要哪些必要的东西:
1,记录是哪个线程持有了锁。
2,如果有一个变量代表加锁,A线程加锁,需要让B线程能看见,这里需要可见性,所以我们可能要用到volatile变量。
3,对变量的操作应该是一个原子操作。
二,实现
首先我们定义一个类,这个类需要有一个持有锁的变量。这里使用ownerThread这个变量来代表持有锁的线程。
class MyLock{
private Thread ownerThread;
public Thread getOwnerThread() {
return ownerThread;
}
public void setOwnerThread(Thread ownerThread) {
this.ownerThread = ownerThread;
}
}
我们先简单定义一下加锁和解锁的方法。并设置一个volatile的变量来保证锁的可见性。
class MyLock{
private Thread ownerThread;
private volatile int state;
public Thread getOwnerThread() {
return ownerThread;
}
public void setOwnerThread(Thread ownerThread) {
this.ownerThread = ownerThread;
}
public boolean lock(){
//可重入,如果锁设置不可重入,则state应该只能为1和0,并在此处抛出异常。
if (Thread.currentThread() == getOwnerThread()){
//临时写法,应该为原子操作的++;
state++;
return true;
} else if(state == 0){
state++; //这里的判断相等和++操作不应该分开,临时写法。正确方法应该为CAS操作。
setOwnerThread(Thread.currentThread());
return true;
}
return false;
}
public void unlock(){
if (Thread.currentThread() != getOwnerThread()){
throw new RuntimeException("不是锁持有线程,不能解锁");
}
state--;
setOwnerThread(null);
}
}
这里我们看到lock里我们考虑到了可重入性,所以简单实现了一下,如果还要实现lock阻塞,应该加上自旋操作,如:
public boolean lock(){
//加上自旋,如果失败,继续。
for (;;) {
if (Thread.currentThread() == getOwnerThread()){
state++;
return true;
}else if(state == 0){
state++;
setOwnerThread(Thread.currentThread());
return true;
}
}
}
我们继续完善代码,将之前的临时操作进一步优化。因为我们无法使用unsafe类来操作,我门这里将state修改为AtomicInteger。
class MyLock{
private Thread ownerThread;
private volatile AtomicInteger state;
public MyLock(){
state = new AtomicInteger(0);
}
public Thread getOwnerThread() {
return ownerThread;
}
public void setOwnerThread(Thread ownerThread) {
this.ownerThread = ownerThread;
}
public boolean lock(){
//可重入
for (;;) {
if (Thread.currentThread() == getOwnerThread()){
state.incrementAndGet();
return true;
}else if(state.compareAndSet(0,1)){
setOwnerThread(Thread.currentThread());
}
}
}
public void unlock(){
if (Thread.currentThread() != getOwnerThread()){
throw new RuntimeException("不是锁持有线程,不能解锁");
}
setOwnerThread(null);
state.decrementAndGet();
}
}
三,验证
public static void testSout(){
try{
lock.lock();
System.out.println("a");
Thread.sleep(100);
System.out.println("b");
Thread.sleep(100);
System.out.println("c");
Thread.sleep(100);
System.out.println("d");
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
TestUtil.testSout();
}
};
Thread thread = new Thread(runnable);
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
Thread thread3 = new Thread(runnable);
thread.start();
thread1.start();
thread2.start();
thread3.start();
thread.join();
thread1.join();
thread2.join();
thread3.join();
}
控制台可以正常输出,说明我们加的锁是可以用的。
四,总结
在这里我们实现的是一个可重入的,不公平,利用自旋来阻塞的一个锁,后续继续深入。