在上一节我们说了synchronized关键字和ReentrantLock锁的区别,如果没有看过上一节的同学可以先看一下,synchronized与ReentrantLock底层原理与区别1
这一节将接着阐述,主要讲解ReentrantLock的公平锁和非公平锁的机制.
一.首先说一下公平锁和非公平锁的概念
公平锁–>排队一个一个来
非公平锁–>可以插队
这里需要指明的是非公平锁并不是随机的,随机还是相对而言比较公平的,而非公平锁就是新来的线程可以直接进行插队.
二.公平锁和非公平锁举例和实现
其实概念还是很容易理解的,但是很多同学还没有真正理解非公平锁的含义.下面给出一个小demo,大家可以先不用看输出结果,先想一下结果会是什么样子
package 线程;
import java.util.concurrent.locks.ReentrantLock;
/**
* 公平锁和非公平锁
* @author tim
*
*/
public class MyReentrantLock3 {
//新建一个非公平锁
private static ReentrantLock lock=new ReentrantLock(false);
static class Task extends Thread{
int count=0;
@Override
public void run() {
for(;;) {
try {
lock.lock();
count++;
Thread.sleep(100); //模拟业务操作,耗时0.1秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
public static void main(String[] args) {
//开启5个线程,不断循环去抢锁
Task[] arr=new Task[5];
for(int i=0;i<5;i++) {
Task task = new Task();
task.start();
arr[i]=task;
}
//总共执行10秒钟,每一次耗时0.1秒,也就是总共最多执行100次
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//查看结果
for(int i=0;i<5;i++) {
System.out.println(arr[i].getName()+">>>"+arr[i].count);
}
//程序退出
System.exit(0);
}
}
以上程序很简单,开启5个线程,每个线程都不断循环去争抢锁,这把锁是非公平锁,大家可以先不看运行结果,猜一下.
结果如下:
是不是感到结果有些意外,这就是非公平锁,线程0并没有排队,而是每次插队去争抢锁,所以线程0一直在运行,而其他线程一次都没有运行.
要注意的是这个结果并不是确定的,因为具体还要取决于cpu的调度.
现在将锁改为公平锁,在ReentrantLock的构造函数中从传入true即可,下面贴上运行结果.
可以看到,每一个线程都均匀的运行20次,因为每次都需要排队.
三.非公平锁在开发中需要注意的问题
在真正理解了非公平锁之后,就要注意他可能带来的问题.非公平锁可能带来线程饥饿问题,就是有些线程一直获取不到资源.
在用户访问请求的时候,都会开启一个线程,比如一瞬间开启了十个线程,需要查询数据库,用到数据库连接池,但是连接池中的连接是有限的,比如说有三个连接,为了保证某个线程在抢到一个连接后不被其他线程再一次抢占,需要加锁,如果是非公平锁,那么就可能发生线程饥饿的事件,带来的结果就是有些用户一直在等待,响应时间激增,系统突然抖动.
要注意大多数情况还是用的非公平锁,因为公平锁会带来性能损耗,少部分需要考虑线程饥饿情况,使用公平锁.
在下一节将会和大家一起手写lock,从底层去更加理解锁的机制