在很多程序语言以及中间件中都存在“锁”的概念,在Java中同样根据不同的特性有不同的锁,其中我们常见的锁有以下几类:
- 公平锁/非公平锁
- 可重入锁
- 独享锁/共享锁
- 互斥锁/读写锁
- 乐观锁/悲观锁
- 分段锁
- 偏向锁/轻量级锁/重量级锁
- 自旋锁
一、Lock与syncronized
在jdk1.5之前,我们实现锁机制大多数情况下都是使用syncronized关键字,在jdk1.5之后,java引入了Lock机制,以修补syncronized存在的问题与缺陷。
类别 | syncronized | Lock |
---|---|---|
存在层次 | Java的关键字,在jvm层次上 | 类 |
锁的释放 | 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 | 在finally中必须释放锁,不然容易造成线程死锁 |
锁的获取 | 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 | 分情况而定,Lock有多个锁获取的方式 |
锁状态 | 无法判断 | 可以判断 |
锁类型 | 可重入 不可中断 非公平 | 可重入 可判断 可公平(两者皆可) |
性能 | 少量同步 | 大量同步 |
二、公平锁/非公平锁
公平锁顾名思义就是对每一个线程进行锁的分配时,在优先级上都持有比较公平的分配策略的锁,当线程在请求资源时,如果资源被其他线程占用,则将此线程挂起,置入请求队列中,在资源的锁被释放时,激活请求队列的头部线程,让他对资源进行操作。
非公平锁相较于公平锁具有更高的效率,在线程请求资源时,如果资源被占用,同样是将线程挂起置入请求队列,但当资源锁被释放时,如果恰好有新的线程对资源发起请求,则优先让新线程对资源进行操作,不去查找请求队列,这样就节省了切换上下文的开销。
公平锁与非公平锁的创建方式:
//创建一个非公平锁,默认是非公平锁
Lock lock = new ReentrantLock();
Lock lock = new ReentrantLock(false);
//创建一个公平锁,构造传参true
Lock lock = new ReentrantLock(true);
我们先实现一个公平锁
public class Main {
public static void main(String[] args) throws InterruptedException {
TestRes testRes = new TestRes();
ReentrantLockTest t1 = new ReentrantLockTest(testRes);
ReentrantLockTest t2 = new ReentrantLockTest(testRes);
ReentrantLockTest t3 = new ReentrantLockTest(testRes);
ReentrantLockTest t4 = new ReentrantLockTest(testRes);
ReentrantLockTest t5 = new ReentrantLockTest(testRes);
t1.setName("线程1");
t2.setName("线程2");
t3.setName("线程3");
t4.setName("线程4");
t5.setName("线程5");
t1.start();
Thread.sleep(100);
t2.start();
Thread.sleep(100);
t3.start();
Thread.sleep(100);
t4.start();
Thread.sleep(100);
t5.start();
}
}
class ReentrantLockTest extends Thread{
private TestRes testRes;
public ReentrantLockTest(TestRes testRes) {
this.testRes = testRes;
}
@Override
public void run() {
while(true){
testRes.resMethod(Thread.currentThread());
}
}
}
class TestRes{
private Lock lock = new ReentrantLock(true);
public void resMethod(Thread thread){
lock.lock();
try{
System.out.println(thread.getName() + "获取了锁");
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
System.out.println(thread.getName() + "释放了锁");
}
}
}
运行后的输出如下
/app/jdk-10.0.2/bin/java -javaagent:/app/idea-IU-182.4129.33/lib/idea_rt.jar=37233:/app/idea-IU-182.4129.33/bin -Dfile.encoding=UTF-8 -classpath /home/jacatch/IdeaProjects/JavaLock/out/production/JavaLock Main
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=gasp
线程1获取了锁
线程2获取了锁
线程1释放了锁
线程2释放了锁
线程3获取了锁
线程3释放了锁
线程4获取了锁
线程4释放了锁
线程5获取了锁
线程5释放了锁
线程1获取了锁
线程1释放了锁
线程2获取了锁
Process finished with exit code 130 (interrupted by signal 2: SIGINT)
可以看出,锁的获取与释放都是按照发起请求的顺序。接下来我们对代码进行修改,实现一个非公平锁
public class Main {
public static void main(String[] args) throws InterruptedException {
TestRes testRes = new TestRes();
ReentrantLockTest t1 = new ReentrantLockTest(testRes);
ReentrantLockTest t2 = new ReentrantLockTest(testRes);
ReentrantLockTest t3 = new ReentrantLockTest(testRes);
ReentrantLockTest t4 = new ReentrantLockTest(testRes);
ReentrantLockTest t5 = new ReentrantLockTest(testRes);
t1.setName("线程1");
t2.setName("线程2");
t3.setName("线程3");
t4.setName("线程4");
t5.setName("线程5");
t1.start();
Thread.sleep(1000); //线程太少,为了模拟,增加时长
t2.start();
Thread.sleep(1000);
t3.start();
Thread.sleep(1000);
t4.start();
Thread.sleep(1000);
t5.start();
}
}
class ReentrantLockTest extends Thread{
private TestRes testRes;
public ReentrantLockTest(TestRes testRes) {
this.testRes = testRes;
}
@Override
public void run() {
while(true){
testRes.resMethod(Thread.currentThread());
}
}
}
class TestRes{
private Lock lock = new ReentrantLock(false); //设置为false
public void resMethod(Thread thread){
lock.lock();
try{
System.out.println(thread.getName() + "获取了锁");
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
System.out.println(thread.getName() + "释放了锁");
}
}
}
这时的运行结果如下
/app/jdk-10.0.2/bin/java -javaagent:/app/idea-IU-182.4129.33/lib/idea_rt.jar=43359:/app/idea-IU-182.4129.33/bin -Dfile.encoding=UTF-8 -classpath /home/jacatch/IdeaProjects/JavaLock/out/production/JavaLock Main
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=gasp
线程1获取了锁
线程2获取了锁
线程1释放了锁
线程2释放了锁
线程3获取了锁
线程3释放了锁
线程4获取了锁
线程4释放了锁
线程1获取了锁
线程1释放了锁
线程5获取了锁
Process finished with exit code 130 (interrupted by signal 2: SIGINT)
我们可以看到,在4释放锁之后,并不是5获取了锁,而是1,因为此时1发起请求,5正挂起在等待队列中,非公平锁优先将资源给了线程1。
以上两个例子详细展示了公平锁与非公平锁的区别,我们再来深入研究一下在ReentrantLock中是如何实现公平锁与非公平锁的。
2.1、ReentrantLock中的FairSync与NonfairSync
我们打开java的源代码,找到ReentrantLock的构造方法,可以看到如下代码
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
在此我们可以看出,在ReentrantLock中的公平性与非公平性分别是由FairSync与NonfairSync两个方法实现的。
2.1.1、NonfairSync
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
我们可以看出,jdk中对NonfairSync的定义主要是对Sync的继承,所以我们再来看Sync的源代码。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/*
*获取非公平锁,直接将非公平锁的获取在Sync中进行实现
*/
@ReservedStackAccess
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;
}
/*
*释放锁
*/
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
booleadengdain free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
/*
*判断当前线程是否为锁的持有者
*/
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
/*
*获取锁的持有者线程
*/
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
/*
*返回当前线程status的状态,如果持有锁就读取status,没有就0
*/
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
/*
*判断是否上锁
*/
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
在这里我们并没有找到lock()方法,我们在ReentrantLock中找到lock的实现
public void lock() {
sync.acquire(1);
}
注意,在JDK8中,lock()方法分别定义在公平锁与非公平锁的具体实现中,然后在ReentrantLock中调用锁对象的lock()方法,而在JDK10中将lock()方法进行了拆分,这里主要写一下JDK10中的实现,JDK8的实现将在附录中补充。
其中的acquire为Sync类继承的AQS的方法,其主要作用是获取线程的同步状态,若获取同步状态失败,则将线程放入同步队列。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
将节点放入同步队列
private Node addWaiter(Node mode) {
Node node = new Node(mode);
for (;;) {
Node oldTail = tail;
if (oldTail != null) {
node.setPrevRelaxed(oldTail);
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return node;
}
} else {
initializeSyncQueue();
}
}
}
2.1.2、FairSync
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
@ReservedStackAccess
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;
}
}
与非公平锁相比,公平锁的实现上增加了hasQueuedPredecessors()方法,其主要作用是检测是否有线程比当前线程更早发起锁获取申请,如果有,则从同步队列中取出这个线程,对其进行操作。
参考文章
java中锁的分类
一张图读懂公平锁与非公平锁
ReentrantLock与公平锁、非公平锁的实现
详解sycronized与Lock的区别与使用
附录
- JDK1.8中实现lock()的代码
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
}
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
}
public void lock() {
sync.lock();
}
- 同步队列结构是双向链表,在运行过程中为FIFO
- 处于等待的线程为休眠状态