公平锁与非公平锁的区别
所谓公平锁,即新加入的线程会按照尝试获取锁的先后顺序依次拿到锁
。
所谓非公平锁,即新加入的线程会进行lock方法加锁进行CAS尝试,加锁失败之后tryAcquire方法state为0时候还会进行加锁尝试
,而公平锁lock加锁不会尝试,tryAcquire方法state为0时候并且前面没有线程队列排队的时候才会进行加锁尝试。
非公平锁的线程加锁会先进行CAS操作尝试获取锁,而公平锁直接进行后续操作
。实现即重写抽象类Sync的lock方法的不同。非公平锁如果state状态为0就尝试加锁,而公平锁如果state为0并且前面没有排队的线程队列才尝试加锁
。实现即重写抽象类AQS的tryAcquire方法的不同。
为什么要有非公平锁
提高效率,避免唤醒线程带来的空档期
。在唤醒线程期间,如果有线程立刻获取锁,就可以将锁给他立刻执行。
ReentrantLock公平锁与非公平锁核心源码分析:
公平锁:
非公平锁:
具体请查看:ReentrantLock核心源码分析
公平锁中的特例
tryLock()是一个特例,即使你是公平锁,当你调用tryLock的时候,即使设置的是公平锁,前面有线程排队,也会立即尝试,不遵守公平锁规则
。
1、直接调用非公平锁(抽象类Sync)中的nonfairTryAcquire方法尝试。
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
2、调用抽象类Sync的nonfairTryAcquire方法加锁
public class ReentrantLock implements Lock, java.io.Serializable {
abstract static class Sync extends AbstractQueuedSynchronizer {
/**
* tryLock方法也会调用这个方法直接尝试,即使设置的是公平锁。
*/
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 得到AQS的状态
int c = getState();
// 如果未加锁
if (c == 0) {
// CAS尝试加锁
if (compareAndSetState(0, acquires)) {
// 设置当前线程持有锁
setExclusiveOwnerThread(current);
return true;
}
}
// 如果获取锁是当前线程
else if (current == getExclusiveOwnerThread()) {
// acquires为1,所以加锁每次AQS的状态加1。
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 设置状态
setState(nextc);
return true;
}
return false;
}
}
}
公平锁与非公平锁优缺点
锁形式 | 优点 | 缺点 |
---|---|---|
公平锁 | 公平等待,每个线程在一段时间后都会有执行的机会 | 有线程切换的开销,吞吐量相对较小 |
非公平锁 | 没有线程切换的开销,吞吐量相对较大 | 有时候会导致线程长时间得不到执行 |
公平锁与非公平锁代码演示
公平锁
每次打印任务需要打印两次,即同一个线程需要加锁解锁两次。 获取第一把锁执行时候,别的线程会尝试第一把锁,所以获得第二吧锁的时候需要等待其他线程第一次执行释放锁执行完成之后。每个启动线程启动有10ms的差距保证启动顺序。
import java.util.concurrent.locks.ReentrantLock;
/**
* @author weilai
* @email 352342845@qq.com
* @date 2020/3/23 12:58 下午
*/
public class DemoLock {
public static void main(String[] args) {
PrintQueue printQueue = new PrintQueue();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(new PrintTask(printQueue));
}
for (int i = 0; i < 10; i++) {
threads[i].start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class PrintTask implements Runnable {
private PrintQueue printQueue;
public PrintTask(PrintQueue printQueue) {
this.printQueue = printQueue;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始任务");
printQueue.printTask();
System.out.println(Thread.currentThread().getName() + "结束任务");
}
}
class PrintQueue {
private ReentrantLock lock = new ReentrantLock(true);
public void printTask() {
print(1);
print(2);
}
private void print(int count) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "第" + count + "正在打印,需要1秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
output:
Thread-0开始任务
Thread-0第1正在打印,需要1秒
Thread-1开始任务
Thread-2开始任务
Thread-3开始任务
Thread-4开始任务
Thread-5开始任务
Thread-6开始任务
Thread-7开始任务
Thread-8开始任务
Thread-9开始任务
Thread-1第1正在打印,需要1秒
Thread-2第1正在打印,需要1秒
Thread-3第1正在打印,需要1秒
Thread-4第1正在打印,需要1秒
Thread-5第1正在打印,需要1秒
Thread-6第1正在打印,需要1秒
Thread-7第1正在打印,需要1秒
Thread-8第1正在打印,需要1秒
Thread-9第1正在打印,需要1秒
Thread-0第2正在打印,需要1秒
Thread-0结束任务
Thread-1第2正在打印,需要1秒
Thread-1结束任务
Thread-2第2正在打印,需要1秒
Thread-2结束任务
Thread-3第2正在打印,需要1秒
Thread-3结束任务
Thread-4第2正在打印,需要1秒
Thread-4结束任务
Thread-5第2正在打印,需要1秒
Thread-5结束任务
Thread-6第2正在打印,需要1秒
Thread-6结束任务
Thread-7第2正在打印,需要1秒
Thread-7结束任务
Thread-8第2正在打印,需要1秒
Thread-8结束任务
Thread-9第2正在打印,需要1秒
Thread-9结束任务
非公平锁
每个线程需要加锁解锁两次,第一次释放锁的时候,在还没有唤醒排队线程的时候,当前线程又进行第二次加锁,满足非公平锁的尝试插队的情况,所以第二次会立刻获取锁,执行顺序为每次线程依次打印两次。
import java.util.concurrent.locks.ReentrantLock;
/**
* @author weilai
* @email 352342845@qq.com
* @date 2020/3/23 12:58 下午
*/
public class DemoLock {
public static void main(String[] args) {
PrintQueue printQueue = new PrintQueue();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(new PrintTask(printQueue));
}
for (int i = 0; i < 10; i++) {
threads[i].start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class PrintTask implements Runnable {
private PrintQueue printQueue;
public PrintTask(PrintQueue printQueue) {
this.printQueue = printQueue;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始任务");
printQueue.printTask();
System.out.println(Thread.currentThread().getName() + "结束任务");
}
}
class PrintQueue {
private ReentrantLock lock = new ReentrantLock(false);
public void printTask() {
print(1);
print(2);
}
private void print(int count) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "第" + count + "正在打印,需要1秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
output:
Thread-0开始任务
Thread-0第1正在打印,需要1秒
Thread-1开始任务
Thread-2开始任务
Thread-3开始任务
Thread-4开始任务
Thread-5开始任务
Thread-6开始任务
Thread-7开始任务
Thread-8开始任务
Thread-9开始任务
Thread-0第2正在打印,需要1秒
Thread-0结束任务
Thread-1第1正在打印,需要1秒
Thread-1第2正在打印,需要1秒
Thread-1结束任务
Thread-2第1正在打印,需要1秒
Thread-2第2正在打印,需要1秒
Thread-2结束任务
Thread-3第1正在打印,需要1秒
Thread-3第2正在打印,需要1秒
Thread-3结束任务
Thread-4第1正在打印,需要1秒
Thread-4第2正在打印,需要1秒
Thread-4结束任务
Thread-5第1正在打印,需要1秒
Thread-5第2正在打印,需要1秒
Thread-5结束任务
Thread-6第1正在打印,需要1秒
Thread-6第2正在打印,需要1秒
Thread-6结束任务
Thread-7第1正在打印,需要1秒
Thread-7第2正在打印,需要1秒
Thread-7结束任务
Thread-8第1正在打印,需要1秒
Thread-8第2正在打印,需要1秒
Thread-8结束任务
Thread-9第1正在打印,需要1秒
Thread-9第2正在打印,需要1秒
Thread-9结束任务