上一节我们阐述了公平锁和非公平锁的概念,运用场景和需要注意的问题,synchronized与ReentrantLock底层原理与区别2,这一节将会和大家一起手写一个简单的公平锁和非公平锁的实现,几乎每一行代码都添加了详细的注释,相信大家看完之后能够对公平锁和非公平锁的底层实现机制有着更清晰的认知.
一.代码实现
package 线程;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import com.sun.org.apache.regexp.internal.recompile;
/**
* 手写一个简单的公平锁和非公平锁的实现
* @author tim
*
*/
public class MyReentrantLock4 {
//锁的拥有者
private Thread owner=null;
//锁的状态,使用volatile保证可见性,使用AtomicInteger保证线程安全
private volatile AtomicInteger state=new AtomicInteger();
//线程等待时,存储的空间-->阻塞队列
private LinkedBlockingQueue<Thread> waitQueue= new LinkedBlockingQueue<>();
//线程公平和非公平的参数,默认为非公平
private boolean fair=false;
public MyReentrantLock4(boolean isFair) {
this.fair=isFair;
}
/**
* 尝试获取锁,获取不到直接返回
* @return
*/
public boolean tryLock() {
if(state.get()==0) {
//底层CAS操作,保证线程安全
if(state.compareAndSet(0, 1)) {
//到这里的线程就成功获取到了锁,compareAndSet()会保证只有一个线程成功执行
owner=Thread.currentThread();
return true;
}
}
return false;
}
/**
* 获取锁,获取不到一直等待
*/
public void lock() {
//非公平
if(!fair) {
if(!tryLock()) {
//现在没有获取到锁,证明当前锁被其他线程占有
//循环是想让在队列中被唤醒的线程重新去争抢锁
for(;;) {
/**
* 这里有这样几种情况
* 1.等待队列中没有线程,当前线程获取锁之后直接返回
* 2.等待队列中已经有其他线程,当前线程和队列中第一个被唤醒的线程去争抢锁
* (1)当前线程获取到锁,直接返回
* (2)队列中的第一个线程获取到锁,从队列中移除
*/
if(tryLock()) {
if(Thread.currentThread()==waitQueue.peek())
waitQueue.poll();
return;
}else {
//如果当前线程没有获取到锁,就需要加到队列中
if(waitQueue.peek()!=Thread.currentThread())
waitQueue.add(Thread.currentThread());
//同时在没有获取到锁的情况下还需要让线程不要跑,就是不要进行其他操作,可以简单的理解为线程睡眠,然后还需要唤醒操作
//该方法是操作系统级的native方法
LockSupport.park();
}
}
}
}else {
//公平锁
//如果等待队列中有线程,直接加入队列中,不尝试获取锁
if(waitQueue.size()>0);
//否则尝试获取锁,如果获取成功,直接返回
else if(tryLock()){
return;
}
//获取不成功的线程加入等待队列中,线程暂停
waitQueue.add(Thread.currentThread());
LockSupport.park();
//线程被唤醒后,直接执行后面的代码,将owner指向队列中的第一个线程
Thread first = waitQueue.poll();
state.compareAndSet(0, 1);
owner=first;
}
}
/**
* 释放锁,只有当前线程拥有者才能够调用该方法
*/
public void unlock() {
if(Thread.currentThread()!=owner) {
throw new RuntimeException(Thread.currentThread().getName()+"并没有占有锁,没有调用权限");
}
//如果当前线程只获取了一次,释放一次就彻底释放了锁
if(state.compareAndSet(1,0)) {
owner=null;
//释放锁之后拿到线程中等待的第一个线程
Thread first = waitQueue.peek();
//然后唤醒线程
LockSupport.unpark(first);
}
}
}
二.验证
这里还会以上一节中验证公平锁和非公平锁的代码去测试,只不过将系统中的lock锁替换成了自己的锁.具体代码如下:
package 线程;
import java.util.concurrent.locks.ReentrantLock;
/**
* 公平锁和非公平锁
* @author tim
*
*/
public class MyReentrantLock3 {
//手写的lock锁
private static MyReentrantLock4 lock=new MyReentrantLock4(false);
static class Task extends Thread{
int count=0;
@Override
public void run() {
for(;;) {
try {
lock.lock();
count++;
Thread.sleep(100); //模拟业务操作,耗时0.1秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
public static void main(String[] args) {
//开启5个线程,不断循环去抢锁
Task[] arr=new Task[5];
for(int i=0;i<5;i++) {
Task task = new Task();
task.start();
arr[i]=task;
}
//总共执行10秒钟,每一次耗时0.1秒,也就是总共最多执行100次
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//查看结果
for(int i=0;i<5;i++) {
System.out.println(arr[i].getName()+">>>"+arr[i].count);
}
//程序退出
System.exit(0);
}
}
代码结果如下:
可以看到,和上一节中使用JDK中的lock锁运行结果是一样的.证明我们的代码没有问题.
将构造方法中传入的参数变为true,就是构造一把公平锁.运行结果如下:
以上代码实现了公平锁和非公平锁,那我们知道ReentrantLock还是一把可重入锁,代码中并没有提现,如果有想要的朋友们可以在评论区留言,我会补充后发出来.希望大家多多支持哦.