码农在囧途
心里只有终点的时候,路途的风景已经丢了一半,心里只想看风景的时候,离终点又远了一半!
ReentrantLock公平锁和非公平锁
ReentrantLock是一个独占锁,基于AQS实现,如果有线程获取了锁,那么其他线程来获取该锁的时候会被阻塞,ReentrantLock有两种 方式,一种是公平锁(FairLock),一种是非公平锁(NoFairLock),ReentrantLock默认是非公平锁,下面解释一下公平锁和非公平锁。
公平锁
公平锁的意思就是任何线程想要获取锁,就必须要排队等候,就好比你去饭堂吃饭,有素质的的同学都在排队,如下,使用公平锁的好处是线程 是按照先到先处理的顺序来进行,保证所有线程都能够获取到锁,不过这样的效率就会变得很低。
非公平锁
非公平锁就是不必排队就能拿到锁,就像食堂里面,那些横行霸道的同学就不排队,直接冲到第一位去,使用非公平锁的好处就是效率比较高一点, 不过就会导致有些线程一直获取不到锁,ReentrantLock默认为非公平锁。
可通过构造函数设置ReentrantLock的公平与非公平锁,默认为公平锁
代码语言:javascript复制public ReentrantLock() {
sync = new NonfairSync();
}
构造函数为true为公平锁,false为非公平锁
代码语言:javascript复制public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock原理
ReentrantLock是基于AQS来实现的,其核心是对state
的处理,关于state
,可见上一篇文章对AQS的讲解中有详细的讲解,我们主要来看一下公平锁和非公平锁的实现方式。
线程首先去尝试获取锁,发现state
,就将state
通过CAS改为1,然后返回,如果线程获取锁时发现state
不为0,则证明锁已经被 其他线程持有,所以就被加入AQS队列的尾部,然后被挂起,当获取到锁的线程执行完任务后释放锁后,它会唤醒队列的队头节点,然后队头的线程再执行任务, 可以看出线程的执行是有顺序的,按照FIFO先进先出的原则。
尝试获取锁
加入AQS队列,因为锁已经被其他线程持有
如下代码将线程封装成EXCLUSIVE
类型的节点加入队列尾部。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
对于公平锁,它主要是通过hasQueuedPredecessors()
判断AQS队列中的第一个线程是不是当前线程,而非公平锁则不用判断,无论是否位于队首都能抢占到锁,为什么要判断呢,因为公平锁需要每个线程都按照顺序 执行,如果当前的线程位于队首位置,则就能获取锁,如果队列首位不是当前线程,那么就需要排队。
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}
当前线程尝试获取锁,判断是否位于AQS队首
位于队首并且线程A释放子锁,这时候线程B就直接获取到锁。
ReentrantLock使用
ReentrantLock只需要在需要同步的代码段进行加锁,需要用try来包裹代码,在finally里进行释放锁,关于它的其他方法,大家可以自行去看。
代码语言:javascript复制public class ReentrantLockTest {
private final ReentrantLock lock = new ReentrantLock();
public void add(){
lock.lock();
try {
System.out.println("get data");
}finally {
lock.unlock();
}
}
}
今天的分享就到这里,感谢你的观看,下期间。