目录
三. AQS(AbstractQueuedSynchronizer)
一. 前言
通过《J.U.C家族》这篇文章的介绍,我们可以知道Lock是J.U.C中很重要的一部分,它非常灵活,可以根据具体需求选择不同的锁类型,并提供了更多的扩展功能,但需要注意加锁和解锁的位置,以免出现死锁等问题。


Synchronized 和 Lock 都是 Java 中用于实现线程同步的机制。它们之间的区别如下:
1. Synchronized 是 Java 的关键字,而 Lock 是一个接口;
2. Synchronized 在执行完相应的同步代码后会自动释放锁,而 Lock 则需要手动释放锁;
3. Synchronized 无法进行尝试获取锁的操作,而 Lock 可以通过 tryLock() 方法来尝试获取锁;
4. Synchronized 只能实现非公平锁,而 Lock 可以实现公平锁或非公平锁。
二. 实现类
2.1. LockSupport
LockSupport用来创建锁和其他同步类的基本线程阻塞原语。在分析LockSupport函数之前,先引入sun.misc.Unsafe类中的park和unpark函数,因为LockSupport的核心函数都是基于Unsafe类中定义的park和unpark函数,下面给出两个函数的定义:
public native void park(boolean isAbsolute, long time);
public native void unpark(Thread thread);
核心函数:
park:阻塞线程
unpark:释放线程的许可,即激活调用park后阻塞的线程。这个函数不是安全的,调用这个函数时要确保线程依旧存活。
parkNanos:此函数表示在许可可用前禁用当前线程,并最多等待指定的等待时间。
parkUntil:此函数表示在指定的时限前禁用当前线程,除非许可可用。
2.2. StampedLock无障碍锁
StampedLock 是 JDK1.8 中新增的一个读写锁,没有 Re 开头,是不可重入锁。支持三种模式:写锁、悲观读锁、乐观读tryOptimisticRead。
读/写锁可以保证并发访问下的数据写入安全与读取性能,但是在读线程非常多的情况下,有可能造成写线程的长时间阻塞,从而减少写线程的调度次数。为此JUC中针对读/写锁提出了改进方案,提供了无障碍锁(StampedLock),使用这种锁的特点在于:若干个读线程彼此之间不会互相影响,但是依然可以保证多个写线程的独占操作。
三. AQS(AbstractQueuedSynchronizer)
AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
AbstractQueuedSynchronizer类底层的数据结构是使用CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。其中Sync queue,即同步队列,是双向链表,包括head结点和tail结点,head结点主要用作后续的调度。而Condition queue不是必须的,其是一个单向链表,只有当使用Condition时,才会存在此单向链表。并且可能会有多个Condition queue。
AQS核心方法:
isHeldExclusively(); // 该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int); // 独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int); // 独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int); // 共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int); // 共享方式。尝试释放资源,成功则返回true,失败则返回false。
名词解释:
可重入锁:又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。
独占(只有一个线程能访问执行,又根据是否按队列的顺序分为公平锁和非公平锁,如ReentrantLock)。
共享(多个线程可同时访问执行,如Semaphore、CountDownLatch、 CyclicBarrier )。ReentrantReadWriteLock可以看成是组合式,允许多个线程同时对某一资源进行读。
3.1. ReentrantLock
ReentrantLock是一种可重入的独占锁,它允许同一个线程多次获取同一个锁而不会被阻塞。它的功能类似于synchronized是一种互斥锁,可以保证线程安全。
ReentrantLock支持公平锁和非公平锁两种模式:
公平锁:线程在获取锁时,按照等待的先后顺序获取锁。
非公平锁:线程在获取锁时,不按照等待的先后顺序获取锁,而是随机获取锁。ReentrantLock默认是非公平锁。
代码示例:
import java.util.concurrent.locks.ReentrantLock;
/**
* 模拟抢票场景
*/
public class ReentrantLockDemo {
/**
* 数默认false,不公平锁
*/
private final ReentrantLock lock = new ReentrantLock();
/**
* 总票数
*/
private static int tickets = 8;
public void buyTicket() {
// 获取锁
lock.lock();
try {
if (tickets > 0) {
// 还有票 读
try {
// 休眠10ms
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 写
System.out.println(Thread.currentThread().getName() + "购买了第" + tickets-- + "张票");
// 可重入
buyTicket();
} else {
System.out.println("票已经卖完了," + Thread.currentThread().getName() + "抢票失败");
}
} finally {
// 释放锁
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockDemo ticketSystem = new ReentrantLockDemo();
for (int i = 1; i <= 10; i++) {
Thread thread = new Thread(() -> {
ticketSystem.buyTicket(); // 抢票
}, "线程" + i);
// 启动线程
thread.start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("剩余票数:" + tickets);
}
}
3.2. ReentrantReadWriteLock
一把锁分为读与写两部分,读锁允许多个线程同时获得,因为读操作本身是线程安全的。而写锁是互斥锁,不允许多个线程同时获得写锁。并且读与写操作也是互斥的。读写锁适合多读少写的业务场景。
ReentrantReadWriteLock是可重入的读写锁实现类。在它内部,维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 Writer 线程,读锁可以由多个 Reader 线程同时持有。也就是说,写锁是独占的,读锁是共享的。
ReentrantReadWriteLock可以用于实现缓存,因为它可以有效地处理大量的读操作,同时保护缓存数据的一致性。例:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Cache {
private static Map<String, Object> map = new HashMap<String, Object>();
private static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private static Lock r = rwl.readLock();
private static Lock w = rwl.writeLock();
// 获取一个key对应的value
public static final Object get(String key) {
r.lock();
try {
return map.get(key);
} finally {
r.unlock();
}
}
// 设置key对应的value,并返回旧的value
public static final Object put(String key, Object value) {
w.lock();
try {
return map.put(key, value);
} finally {
w.unlock();
}
}
// 清空所有的内容
public static final void clear() {
w.lock();
try {
map.clear();
} finally {
w.unlock();
}
}
}
文章介绍了Java并发编程中的Lock机制,包括LockSupport的park和unpark函数在阻塞和激活线程中的作用,StampedLock作为无障碍锁的特性,以及AQS(AbstractQueuedSynchronizer)如何实现线程同步,特别是ReentrantLock和ReentrantReadWriteLock的使用及其公平性和非公平性。
2109

被折叠的 条评论
为什么被折叠?



