Java中的锁
synchronized
synchronized用于并发场景,在1.6版本之前被称为重量级锁,也是一种悲观锁,可重入锁。1.6版本进行可优化,大大提高了synchronized的效率。
注意,synchronized关键字修饰类和静态方法,锁住类对象;修饰方法,锁住调用此方法的对象
实现原理
synchronized是jvm实现的一种互斥同步访问方式,底层是基于每个对象的监视器(monitor)来实现的。被synchronized修饰的代码,在被编译器编译后在被修饰的代码前后加上了一组字节指令。
在代码开始加入了monitorenter,在代码后面加入了monitorexit,这两个字节码指令配合完成了synchronized关键字修饰代码的互斥访问。
在虚拟机执行到monitorenter指令的时候,会请求获取对象的monitor锁,基于monitor锁又衍生出一个锁计数器的概念。
当执行monitorenter时,若对象未被锁定时,或者当前线程已经拥有了此对象的monitor锁,则锁计数器+1,该线程获取该对象锁。
当执行monitorexit时,锁计数器-1,当计数器为0时,此对象锁就被释放了。那么其他阻塞的线程则可以请求获取该monitor锁。
1.6版本做了什么优化
Java 1.6版本为了减少获得锁和释放锁带来的性能消耗,引入了偏向锁和轻量级锁的概念,级别从高到低分别是重量级锁、轻量级锁、偏向锁。只能升级不能降级。
对象头
对象头三部分组成,Mark Word,Class Pointer, Array Length
关于锁的信息就存储在对象头里
原图来自《并发编程的艺术》这本书。
可以看到Markword 内有2比特是锁标志位,还有一位是偏向锁标志位
锁升级机制
偏向锁
研究人员发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。
以上是偏向锁的获得和撤销流程,图片来自《并发编程的艺术》,这张是网上找的图,见谅。
可以看到,线程2CAS操作替换Mark Word,如果不成功,锁才会升级为轻量级锁,如果线程A已经不存在了,那么此时锁状态还是偏向锁。
轻量级锁
上图可以看到,轻量级锁的加锁过程如下
首先线程在栈上分配空间,复制Mark Word到栈帧上。然后CAS修改MarkWord,如果成功,将Mark Word上的线程ID指向自己。
解锁过程:会使用CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。
Java语言实现的锁
Lock类是在JDK 5 时java/util/currrent包中出现,可以解决synchronized关键字的一些功能限制,例如:
synchronized无法中断一个正在等候获得锁的线程,也无法通过投票得到锁。synchronized还要求锁的释放只能在与获得锁所在的堆栈帧相同的堆栈帧中进行。
而且对多线程环境中,使用synchronized后,线程要么获得锁,执行相应的代码,要么无法获得锁处于等待状态,对于锁的处理不灵活。而Lock提供了多种基于锁的处理机制。比如:
- void lock(),获取一个锁,如果锁当前被其他线程获得,当前的线程将被休眠。
- boolean tryLock(),尝试获取一个锁,如果当前锁被其他线程持有,则返回false,不会使当前线程休眠。
- boolean tryLock(long timeout,TimeUnit
unit),如果获取了锁立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁,就返回true,如果等待超时,返回false。 - void lockInterruptibly(),如果当前线程未被中断,则获取锁,立即返回,将锁的保持计数设置为
1,如果当前线程已获取锁,保持计数+1。如果锁被另一个线程保持,则出于线程调度目的,禁用当前线程,并且在发生以下两种情况之一以
前,该线程将一直处于休眠状态: 锁被其他线程中断、该线程获得锁
以上内容参考自:https://https://blog.csdn.net/a2225791/article/details/101560871
https://blog.csdn.net/u013851082/article/details/70140223
LockSupport 类
LockSupport 类主要作用是挂起和唤醒线程, 该工具类是创建锁和其他同步类的基础。LockSupport 和 CAS 是Java并发包中很多并发工具控制机制的基础,它们底层其实都是依赖Unsafe实现。
常用方法:
public static void park(Object blocker); // 暂停当前线程
public static void parkNanos(Object blocker, long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(Object blocker, long deadline); // 暂停当前线程,直到某个时间
public static void park(); // 无期限暂停当前线程
public static void parkNanos(long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(long deadline); // 暂停当前线程,直到某个时间
public static void unpark(Thread thread); // 恢复指定线程
public static Object getBlocker(Thread t);
AQS
AbstractQueueSynchronizer(AQS),抽象同步队列,它是实现同步器的基础组件, 并发包中锁的底层就是使用 AQS 实现的。
- AQS是双向队列,FIFO;
- 竞争资源失败的线程转换成node节点阻塞挂起存放进入 AQS 队列的,Node节点有两种类型,一种是EXCLUSIVE类型(独占)、一种是SHARED(共享)。waitStatus 记录当前线程等待状态(可以为 CANCELLED(线程被取消了)、 SIGNAL (线程需要被唤醒)、 CONDITION (线程在条件队列里面等待〉、 PROPAGATE (释放共享资源时需要通知其他节点〕); prev 记录当前节点的前驱节点, next 记录当前节点的 后继节点。
- 在 AQS 中 维持了 一 个 单 一 的状态信息 state(lock就是基于state实现的),可以通过 getState、 setState、 compareAndSetState 函数修改其值。 对于 ReentrantLock 的实现来说, state可以用来表示 当前线程获取锁的可重入次数 ;对于读写锁 ReentrantReadWriteLock 来说, state 的高 16位表示读状态,也就是获取该读锁的次数,低 16 位表示获取到写锁的线程的可重入次数; 对于 semaphore 来说, state用来表示当前可用信号的个数:对于 CountDownlatch 来说, state 用来表示计数器当前的值。
- AQS 有个内部类 ConditionObject, 用来结合锁实现线程同步。 ConditionObject 可以 直接访问 AQS对象 内部的变量,比如 state 状态值和 AQS 队列。 ConditionObject 是条件 变量, 每个条件变量对应一个条件队列(单向链表队列),其用来存放调用条件变量的 await 方法后被阻塞的线程,如类图所示, 这个条件队列的头、尾元素分别为自rstWaiter 和last Waiter。
类结构图,AQS介绍来自:https://blog.csdn.net/qq_35599414/article/details/105489021
AQS类底层通过CAS实现,具体程序逻辑结合Lock接口深入。
Lock接口
Lock接口共声明5个方法,除了上面所说的4个,还有unlock方法。
他的实现类有6个,其中5个实现类是内部类
ReentrantLock
ReentrantLock是一个可重入锁的互斥锁,当锁没有被占有时,调用lock()方法的线程将成功获取锁。可以使用isHeldByCurrentThread()和 getHoldCount()方法来判断当前线程是否拥有该锁。
ReentrantLock既可以是公平锁又可以是非公平锁,没记错的话JUC定义了两个抽象类,根据fair参数确定锁对象是否是公平的。
源码分析
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
*/
//内部类,实现AQS接口
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock();
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
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;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
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
}
}
/**
* Sync object for non-fair locks
*/
//内部类,非公平锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
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;
}
}
//默认非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//带参构造函数
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
//调用aquire方法
public void lock() {
sync.lock();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.release(1);
}
public Condition newCondition() {
return sync.newCondition();
}
public int getHoldCount() {
return sync.getHoldCount();
}
public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}
public boolean isLocked() {
return sync.isLocked();
}
public final boolean isFair() {
return sync instanceof FairSync;
}
protected Thread getOwner() {
return sync.getOwner();
}
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
public final boolean hasQueuedThread(Thread thread) {
return sync.isQueued(thread);
}
public final int getQueueLength() {
return sync.getQueueLength();
}
protected Collection<Thread> getQueuedThreads() {
return sync.getQueuedThreads();
}
public boolean hasWaiters(Condition condition) {
if (condition == null)
throw new NullPointerException();
if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
throw new IllegalArgumentException("not owner");
return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
}
public int getWaitQueueLength(Condition condition) {
if (condition == null)
throw new NullPointerException();
if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
throw new IllegalArgumentException("not owner");
return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
}
protected Collection<Thread> getWaitingThreads(Condition condition) {
if (condition == null)
throw new NullPointerException();
if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
throw new IllegalArgumentException("not owner");
return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
}
/**
* Returns a string identifying this lock, as well as its lock state.
* The state, in brackets, includes either the String {@code "Unlocked"}
* or the String {@code "Locked by"} followed by the
* {@linkplain Thread#getName name} of the owning thread.
*
* @return a string identifying this lock, as well as its lock state
*/
public String toString() {
Thread o = sync.getOwner();
return super.toString() + ((o == null) ?
"[Unlocked]" :
"[Locked by thread " + o.getName() + "]");
}
}
lock.lock()方法相当于进入synchronized同步代码块,用于获取独占锁(应该也可以说是排它锁);
公平锁
final void lock() {
acquire(1);
}
//AQS的acquie方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
//如果获取锁失败,当前线程加入到CLH队列队尾,如果当前线程是老二,再次尝试获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//如果没拿到锁并且需要interrupt
selfInterrupt();
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
//线程中断标志位
boolean interrupted = false;
for (;;) {
//上一个节点,因为node相当于当前线程,所以上一个节点表示“上一个等待锁的线程”
final Node p = node.predecessor();
/*
* 如果当前线程是head的直接后继则尝试获取锁
* 这里不会和等待队列中其它线程发生竞争,但会和尝试获取锁且尚未进入等待队列的线程发生竞争。这是非公平锁和公平锁的一个重要区别。
*/
if (p == head && tryAcquire(arg)) {
setHead(node); //将当前节点设置设置为头结点
p.next = null;
failed = false;
return interrupted;
}
/* 如果不是老二或获取锁失败,则检查是否要阻塞当前线程,是则阻塞当前线程
* shouldParkAfterFailedAcquire:判断“当前线程”是否需要阻塞
* parkAndCheckInterrupt:阻塞当前线程
*/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//公平锁的tryAcquire方法
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//锁未被任何线程持有
if (c == 0) {
if (!hasQueuedPredecessors() &&
//只有一个线程能成功
compareAndSetState(0, acquires)) {
//设置锁的Owner
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方法 看当前队列是否是等待队列中最前面的线程,如果是的就持有锁,不是的话,获取锁失败。
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
//这行代码写的太漂亮了,膜拜
((s = h.next) == null || s.thread != Thread.currentThread());
}
可以看出,公平锁的hasQueuedPredecessors保证了排队机制。
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;
}
非公平锁则直接进行CAS操作,破坏排队机制,所以说是非公平的。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
另外,非公平锁在acquire(1)之前就尝试CAS获取锁,这里也可能打破排队机制
await()方法相当于Object.wait()方法,用于阻塞挂起当前线程,当其他线程调用了signal方法(相当于Object.notify()方法)时,被阻塞的线程才会从await处返回。
lock.newCondition()作用是new一个在AQS内部类ConditionObject对象。每个条件变量内部都维护了一个条件队列,用来存放调用该条件变量的await方法时被阻塞的线程。
Demo1
窗口卖票:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class JUCLockUsage implements Runnable{
private static int tickets = 500;
private Lock fairLock = new ReentrantLock(true);
@Override
public void run() {
try {
fairLock.lock();
while (true) {
if (tickets > 0){
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (500 - tickets-- + 1) + "张票");
}else {
break;
}
}
}finally {
fairLock.unlock();
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new JUCLockUsage(), "窗口1");
Thread t2 = new Thread(new JUCLockUsage(), "窗口2");
t1.start();
t2.start();
}
}
窗口1正在出售第481张票
窗口2正在出售第482张票
窗口1正在出售第483张票
窗口2正在出售第484张票
窗口1正在出售第485张票
窗口2正在出售第486张票
窗口1正在出售第487张票
窗口2正在出售第488张票
窗口1正在出售第489张票
窗口2正在出售第490张票
窗口1正在出售第491张票
窗口2正在出售第492张票
窗口1正在出售第493张票
窗口2正在出售第494张票
窗口1正在出售第495张票
窗口2正在出售第496张票
窗口1正在出售第497张票
窗口2正在出售第498张票
窗口1正在出售第499张票
窗口2正在出售第500张票
Demo2
公平锁
package atomicClassUsage;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrentLockUsage implements Runnable{
private static ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while (true){
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + " get lock");
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
ReentrentLockUsage fairLock = new ReentrentLockUsage();
Thread thread1 = new Thread(fairLock);
Thread thread2 = new Thread(fairLock);
thread1.start();
thread2.start();
}
}
Thread-0 get lock
Thread-1 get lock
Thread-0 get lock
Thread-1 get lock
Thread-0 get lock
Thread-1 get lock
Thread-0 get lock
Thread-1 get lock
Demo3
非公平锁
package atomicClassUsage;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrentLockUsage implements Runnable{
private static ReentrantLock lock = new ReentrantLock(false);
@Override
public void run() {
while (true){
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + " get lock");
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
ReentrentLockUsage fairLock = new ReentrentLockUsage();
Thread thread1 = new Thread(fairLock);
Thread thread2 = new Thread(fairLock);
thread1.start();
thread2.start();
}
}
Thread-0 get lock
Thread-1 get lock
Thread-0 get lock
Thread-0 get lock
Thread-0 get lock
Thread-0 get lock