背景
公平:排队
非公平:在合适时机插队
非公平还是 ReentrantLock 的默认策略,排队时间不浪费了?
场景
来看这种场景
假如A持有一把锁,B请求这把锁,这时候B被挂起进入阻塞,A释放锁的时候,C来了进行请求,A就把锁给了C,因为唤醒B是需要很大开销的,很可能在B唤醒之前C就已经拿到这把锁执行完任务释放了这把锁,那就是双赢,C的执行速度相比于B被唤醒是很快的,这样设计就提高了整体运行效率
那假如公平呢?
那就进入等待队列等待,依次获得锁
不公平呢?
T1释放锁时,T5没排队直接请求,非公平策略:从整体效率考虑,这把锁会给T5
实验
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class FairAndUnfair {
public static void main(String args[]) {
PrintQueue printQueue = new PrintQueue();
Thread thread[] = new Thread[10];
for (int i = 0; i < 10; i++) {
thread[i] = new Thread(new Job(printQueue), "Thread " + i);
}
for (int i = 0; i < 10; i++) {
thread[i].start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Job implements Runnable {
private PrintQueue printQueue;
public Job(PrintQueue printQueue) {
this.printQueue = printQueue;
}
@Override
public void run() {
System.out.printf("%s: Going to print a job\n", Thread.currentThread().getName());
printQueue.printJob(new Object());
System.out.printf("%s: The document has been printed\n", Thread.currentThread().getName());
}
}
class PrintQueue {
private final Lock queueLock = new ReentrantLock(false);
public void printJob(Object document) {
queueLock.lock();
try {
Long duration = (long) (Math.random() * 10000);
System.out.printf("%s: PrintQueue: Printing a Job during %d seconds\n", Thread.currentThread().getName(),
(duration / 1000));
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
queueLock.unlock();
}
queueLock.lock();
try {
Long duration = (long) (Math.random() * 10000);
System.out.printf("%s: PrintQueue: Printing a Job during %d seconds\n", Thread.currentThread().getName(),
(duration / 1000));
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
queueLock.unlock();
}
}
}
false:不公平
这里只给出非公平锁结果
Thread 0: Going to print a job
Thread 0: PrintQueue: Printing a Job during 8 seconds
Thread 1: Going to print a job
Thread 2: Going to print a job
Thread 3: Going to print a job
Thread 4: Going to print a job
Thread 5: Going to print a job
Thread 6: Going to print a job
Thread 7: Going to print a job
Thread 8: Going to print a job
Thread 9: Going to print a job
Thread 0: PrintQueue: Printing a Job during 9 seconds
Thread 0: The document has been printed
Thread 1: PrintQueue: Printing a Job during 8 seconds
Thread 1: PrintQueue: Printing a Job during 2 seconds
Thread 1: The document has been printed
Thread 2: PrintQueue: Printing a Job during 7 seconds
Thread 2: PrintQueue: Printing a Job during 8 seconds
...
本应是1获得锁但此时被0插队,由此可见0释放锁又获得了锁
优缺点
公平锁、非公平锁源码分析
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {...}
static final class FairSync extends Sync {...}
static final class NonfairSync extends Sync{...}
}
公平锁和非公平锁加锁方法源码
公平锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
非公平锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
由此可见公平锁和非公平锁lock方法唯一区别就是公平锁获取锁时多了一个限制:hasQueuedPredecessors,等待队列中是否有线程在排队,公平锁:一旦有线程在排队,当前线程就不再尝试获取锁,对于非公平锁:无论有没有都尝试获取一下锁,获取不到再去排队
特例:tryLock方法
上源码
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
意思就是一旦有线程释放锁,那么正在tryLock的线程就能获取到锁,即便设置的是公平锁模式,即便他前面有等待的线程,它也可以插队