在java的锁机制中,公平和非公平的参考物是什么,个人而言觉得是相对产生的结果而立,简单的来说,如果一个线程组里,能保证每个线程都能拿到锁,那么这个锁就是公平锁。相反,如果保证不了每个线程都能拿到锁,也就是存在有线程饿死,那么这个锁就是非公平锁。本文围绕ReenTrantLock来讲。
实现原理
那如何能保证每个线程都能拿到锁呢,队列FIFO是一个完美的解决方案,也就是先进先出,java的ReenTrantLock也就是用队列实现的公平锁和非公平锁。
在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个所,那么新发出的请求的线程将被放入到队列中。而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中(此时和公平锁是一样的)。所以,它们的差别在于非公平锁会有更多的机会去抢占锁。
公平锁:
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
#hasQueuedPredecessors的实现
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
复制代码
非公平锁:
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
复制代码
示例
公平锁:
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by Fant.J.
*/
public class MyFairLock {
/**
* true 表示 ReentrantLock 的公平锁
*/
private ReentrantLock lock = new ReentrantLock(true);
public void testFail(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() +"获得了锁");
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
MyFairLock fairLock = new MyFairLock();
Runnable runnable = () -> {
System.out.println(Thread.currentThread().getName()+"启动");
fairLock.testFail();
};
Thread[] threadArray = new Thread[50];
for (int i=0; i<50; i++) {
threadArray[i] = new Thread(runnable);
threadArray[i].start();
}
}
}
复制代码
Thread-0启动
获得了锁Thread-0获得了锁
Thread-1启动
获得了锁Thread-1获得了锁
Thread-2启动
获得了锁Thread-2获得了锁
Thread-3启动
获得了锁Thread-3获得了锁
Thread-4启动
获得了锁Thread-4获得了锁
Thread-5启动
获得了锁Thread-5获得了锁
Thread-6启动
Thread-7启动
获得了锁Thread-6获得了锁
获得了锁Thread-7获得了锁
Thread-8启动
Thread-9启动
获得了锁Thread-8获得了锁
Thread-10启动
获得了锁Thread-9获得了锁
获得了锁Thread-10获得了锁
Thread-11启动
获得了锁Thread-11获得了锁
Thread-13启动
获得了锁Thread-13获得了锁
Thread-14启动
获得了锁Thread-14获得了锁
Thread-15启动
获得了锁Thread-15获得了锁
Thread-16启动
获得了锁Thread-16获得了锁
Thread-17启动
获得了锁Thread-17获得了锁
Thread-18启动
获得了锁Thread-18获得了锁
Thread-19启动
获得了锁Thread-19获得了锁
Thread-20启动
获得了锁Thread-20获得了锁
Thread-24启动
获得了锁Thread-24获得了锁
Thread-22启动
获得了锁Thread-22获得了锁
Thread-27启动
获得了锁Thread-27获得了锁
Thread-25启动
Thread-26启动
获得了锁Thread-25获得了锁
获得了锁Thread-26获得了锁
Thread-29启动
获得了锁Thread-29获得了锁
Thread-28启动
获得了锁Thread-28获得了锁
Thread-30启动
获得了锁Thread-30获得了锁
Thread-32启动
获得了锁Thread-32获得了锁
Thread-31启动
获得了锁Thread-31获得了锁
Thread-33启动
获得了锁Thread-33获得了锁
Thread-34启动
获得了锁Thread-34获得了锁
Thread-36启动
获得了锁Thread-36获得了锁
Thread-35启动
获得了锁Thread-35获得了锁
Thread-38启动
获得了锁Thread-38获得了锁
Thread-37启动
获得了锁Thread-37获得了锁
Thread-39启动
获得了锁Thread-39获得了锁
Thread-42启动
Thread-41启动
获得了锁Thread-42获得了锁
获得了锁Thread-41获得了锁
Thread-40启动
获得了锁Thread-40获得了锁
Thread-45启动
获得了锁Thread-45获得了锁
Thread-44启动
获得了锁Thread-44获得了锁
Thread-43启动
获得了锁Thread-43获得了锁
Thread-23启动
Thread-46启动
Thread-21启动
获得了锁Thread-23获得了锁
Thread-49启动
Thread-48启动
Thread-12启动
获得了锁Thread-46获得了锁
Thread-47启动
获得了锁Thread-21获得了锁
获得了锁Thread-49获得了锁
获得了锁Thread-48获得了锁
获得了锁Thread-12获得了锁
获得了锁Thread-47获得了锁
复制代码
可以看到,获取锁的线程顺序正是线程启动的顺序。
非公平锁:
/**
* Created by Fant.J.
*/
public class MyNonfairLock {
/**
* false 表示 ReentrantLock 的非公平锁
*/
private ReentrantLock lock = new ReentrantLock(false);
public void testFail(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName() +"获得了锁");
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
MyNonfairLock nonfairLock = new MyNonfairLock();
Runnable runnable = () -> {
System.out.println(Thread.currentThread().getName()+"启动");
nonfairLock.testFail();
};
Thread[] threadArray = new Thread[10];
for (int i=0; i<10; i++) {
threadArray[i] = new Thread(runnable);
threadArray[i].start();
}
}
}
复制代码
Thread-1启动
Thread-0启动
Thread-0获得了锁
Thread-1获得了锁
Thread-8启动
Thread-8获得了锁
Thread-3启动
Thread-3获得了锁
Thread-4启动
Thread-4获得了锁
Thread-5启动
Thread-2启动
Thread-9启动
Thread-5获得了锁
Thread-2获得了锁
Thread-9获得了锁
Thread-6启动
Thread-7启动
Thread-6获得了锁
Thread-7获得了锁
复制代码
可以看出非公平锁对锁的获取是乱序的,即有一个抢占锁的过程。
最后
那非公平锁和公平锁适合什么场合使用呢,他们的优缺点又是什么呢?
优缺点:
非公平锁性能高于公平锁性能。首先,在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。而且,非公平锁能更充分的利用cpu的时间片,尽量的减少cpu空闲的状态时间。
使用场景
使用场景的话呢,其实还是和他们的属性一一相关,举个栗子:如果业务中线程占用(处理)时间要远长于线程等待,那用非公平锁其实效率并不明显,但是用公平锁会给业务增强很多的可控制性。