线程锁
位于jucl
下,主要有两种接口类型的锁:
-
Lock
:包含以下三种形式锁的类型:
- 公平锁:不同的线程获取锁的过程是公平的
- 非公平锁:不同线程获取锁的过程是不公平的,允许竞争获取。
- 可重入锁:同一个锁可以被一个线程多次获取,避免死锁的出现。
-
ReadWhiteLock
: 提供读锁和写锁,读时锁共享,修改时独占。
ReentrantLock
互斥锁或叫独占锁,是Lock
的实现类,意思是一旦获取到锁之后,其他线程不允许再操作(无论读或写),synchronized
也是互斥锁。
先观察下ReentrantLock
的源码:
package java.util.concurrent.locks;
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
}
继续扒Sync
:
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
}
继续扒AbstractQueuedSynchronizer
:(这就是JUC另一核心:AQS
)
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer implements java.io.Serializable {
private static final long serialVersionUID = 7373984972572414691L;
}
继续:
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
AQS 的本质是一个执行队列,所有的待执行线程全部都保存在一个队列之中,可以解决死锁问题。在JUC的AQS里面提供了一个CLH队列。CLH (Craig, Landin, and Hagersten)锁是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。
/** JDK17 CLH Nodes */
abstract static class Node {
volatile Node prev; // initially attached via casTail
volatile Node next; // visibly nonnull when signallable
Thread waiter; // visibly nonnull when enqueued
volatile int status; // written by owner, atomic bit ops by others
}
获取锁的机制是由AQS
的子类Sync
提供的,Sync
也有两个子类:FairSync
、NonfairSync
。
ReentrantLock
在操作时有两种情况:
- 多个线程抢占互斥锁资源:
- 互斥锁抢夺与等待:
等待的线程会保存在AQS
中的CLH等待队列中,锁竞争的机制是由Sync
决定的,可以发现默认是非公平锁,是比公平锁更有效率,也是更能发挥出计算机的性能。
private final Sync sync;
public ReentrantLock() {sync = new NonfairSync();}
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}
lock()与unlock()
public void lock() { // 此处简写
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
在进行线程锁定的过程之中是依靠线程锁定数量的控制,锁定的时候都会调用锁定计数的方法:acquire(1)
,每当一个线程被锁定了,这个时候计数就会+1
,CLH就依靠锁定数量是否为0来确定是否有锁定的线程,从而解决线程死锁问题。在每一次解锁的时候会调用release(1)
释放一个锁定。
设计一个抢票系统
class SaleSystem {
private int ticket;
private static final ReentrantLock LOCK = new ReentrantLock();
public SaleSystem() {}
public SaleSystem(int ticket) {
this.ticket = ticket;
}
public void sale() {
LOCK.lock();
try {
if (this.ticket > 0) {
System.out.printf("【%s】售票成功!剩余票数:%d\n",
Thread.currentThread().getName(), this.ticket --);
} else {
System.out.printf("【%s】没有票啦!\n", Thread.currentThread().getName());
}
}catch (Exception e) {
e.printStackTrace();
} finally {
LOCK.unlock();
}
}
}
public class Main {
public static void main(String[] args) throws Exception {
SaleSystem saleSystem = new SaleSystem(5);
for (int i = 0; i < 8; i ++) {
new Thread(saleSystem::sale).start();
}
}
}
【Thread-0】售票成功!剩余票数:5
【Thread-4】售票成功!剩余票数:4
【Thread-1】售票成功!剩余票数:3
【Thread-3】售票成功!剩余票数:2
【Thread-5】售票成功!剩余票数:1
【Thread-2】没有票啦!
【Thread-6】没有票啦!
【Thread-7】没有票啦!
ReentrantReadWriteLock
非独占锁,读锁属于共享锁,所有的读线程共享一个共享锁,当写锁工作的时候,整个共享锁将进入到暂停阶段,等待写完成后再进行多个并发并发读取。
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
private static final long serialVersionUID = -6992448646407690164L;
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
public ReentrantReadWriteLock() {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
}