锁的公平性
将Lock引入Java中的同时也增加锁的一个概念:公平性。在ReentrantLock和ReentrantReadWriteLock的构造函数中可以指定锁的公平性。
如果给构造函数传入false表明锁是非公平锁,在非公平锁中,当有很多线程在等待锁时,锁会随机选择其中一个来访问临界区,这个随机的选择没有任何约束。如果给构造函数传入true表明锁是公平锁,在公平锁中,当有很多线程等待锁时,锁会选择等待时间最长的来访问临界区。这种情况发生在Lock对象的lock和unlock方法。
synchronized关键字是一个非公平锁。非公平锁虽然是随机选择的,但从统计来看每个线程都会被激活,大部分的场景都适合非公平锁。另外,非公平锁的效率要比公平锁的效率高很多。
下面的示例说明了公平锁的运行机制,示例中会启动10个线程,每隔0.01秒启动一个,这样线程0-9的等待时间依次递减,从运行日志中可以看出:锁一定会选择等待时间最长的线程。
public class FireLockDemo {
public static void main(String[] args){
FireLockWorker worker = new FireLockWorker();
Thread[] threads = new Thread[10];
System.out.println("main:创建10个线程");
for(int i=0 ;i<threads.length; i++){
threads[i] = new Thread(worker);
}
System.out.println("main:依次启动10个线程,每个线程之间间隔0.1秒");
for(int i=0 ;i<threads.length; i++){
threads[i].start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class FireLockWorker implements Runnable{
private Lock lock = new ReentrantLock(true);
@Override
public void run() {
//等待其他线程启动
lock.lock();
long duration = (long)(Math.random()*1000);
try{
Thread.sleep(duration);
System.out.println(Thread.currentThread().getName() +":等待其他线程启动");
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();
}
System.out.println(Thread.currentThread().getName() +":申请加锁");
//模拟处理
lock.lock();
try{
Thread.sleep(duration);
System.out.println(Thread.currentThread().getName() +":操作完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock();
}
}
}
程序运行日志:
main:创建10个线程
main:依次启动10个线程,每个线程之间间隔0.1秒
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-6:申请加锁
Thread-7:等待其他线程启动
Thread-7:申请加锁
Thread-8:等待其他线程启动
Thread-8:申请加锁
Thread-9:等待其他线程启动
Thread-9:申请加锁
Thread-0:操作完成
Thread-1:操作完成
Thread-2:操作完成
Thread-3:操作完成
Thread-4:操作完成
Thread-5:操作完成
Thread-6:操作完成
Thread-7:操作完成
Thread-8:操作完成
Thread-9:操作完成
将锁改成非公平锁`private Lock lock = new ReentrantLock(false);`,再考察程序的运行日志:
main:创建10个线程
main:依次启动10个线程,每个线程之间间隔0.1秒
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-6:申请加锁
Thread-7:等待其他线程启动
Thread-7:申请加锁
Thread-7:操作完成
Thread-8:等待其他线程启动
Thread-8:申请加锁
Thread-8:操作完成
Thread-9:等待其他线程启动
Thread-9:申请加锁
Thread-0:操作完成
Thread-1:操作完成
Thread-2:操作完成
Thread-3:操作完成
Thread-4:操作完成
Thread-5:操作完成
Thread-6:操作完成
Thread-9:操作完成