关键字

jvm 自带的关键字synchronizedvolatile,可以解决一下简单需要锁的场景。

synchronized

实例锁(修饰实例方法),同步代码块(实例锁),class 锁(修饰静态方法)

synchronized 具有可重入性

classMyClass {
public synchronized void method1() {
method2();
}
public synchronized void method2() {
}
}

volatile

严格意义上,volatile 和锁无关。

在某些情况下优于 synchronized

(1)volatile 保证可见性

被 volatile 修饰之后,保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

(2)volatile 无法保证原子性

但是不能保证原子性,例如用 volatitle 修饰 int,多线程自增,仍然无法保证获取想要的值。

自增操作是不具备原子性的,它包括读取变量的原始值、进行加 1 操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行。

Lock

ReentrantLock 和 ReentrantReadWriteLock 都是可重入锁

ReentrantLock

ReentrantLock 默认是非公平锁,通过构建函数参数 true 可以设置为公平锁。

ReentrantLock 具有可重入性,可支持公平锁与非公平锁,支持响应中断,是一个排他锁。

// 非公平锁
Lock noFairlock = new ReentrantLock();
// 公平锁
Lock fairlock = new ReentrantLock(true);

多线程数字相加 demo

public class ReentrantLockDemo {
private Lock lock = new ReentrantLock();
private long num = 0;
public void increment() {
lock.lock(); // 获取锁
try {
num++;
} finally {
lock.unlock(); // 释放锁
}
}
public static void main(String[] args) {
ReentrantLockDemo demo = new ReentrantLockDemo();
ExecutorService executorService = new ThreadPoolExecutor(10, 10,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(), r -> new Thread(r, "thread_pool_" + r.hashCode()), new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 1_000_000; i++) {
executorService.execute(() -> {
demo.increment();
});
}
executorService.shutdown();
while (!executorService.isTerminated()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(demo.num);
}
}

Lock 接口中有一个方法是可以获得一个 Condition,通过它可以实现等待、通知机制。

Condition condition = lock.newCondition();
condition.await();
condition.notify();

ReentrantReadWriteLock

ReentrantReadWriteLock 读-读能共存,读-写不能共存,写-写不能共存(读锁读写互斥,写锁,写写互斥)

读写锁同时使用,适合读多写少,读写一致的场景

ReentrantReadWriteLock 具有‘写饥饿’情况,即在发生写锁占用的时候,阻塞其他读写锁。

public class ReadWriteLockDemo {
private final Map<String, String> map = new HashMap<>();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void put(String key, String value) {
lock.writeLock().lock();
try {
map.put(key, value);
} finally {
lock.writeLock().unlock();
}
}
public String get(String key) {
lock.readLock().lock();
try {
return map.get(key);
} finally {
lock.readLock().unlock();
}
}
}

StampedLock

StampedLock 实现了“读写锁”的功能,它支持三种访问模式:读、写和乐观读,并且性能比 ReentrantReadWriteLock 更高。StampedLock 还把读锁分为了“乐观读锁”和“悲观读锁”两种。

它的核心思想在于,在读的时候如果发生了写,应该通过重试的方式来获取新的值,而不应该阻塞写操作。这种模式也就是典型的无锁编程思想,和 CAS 自旋的思想一样。

public class StampedLockDemo {
private final Map<String, String> map = new HashMap<>();
private final StampedLock lock = new StampedLock();
public void put(String key, String value) {
long stamp = lock.writeLock();
try {
map.put(key, value);
} finally {
lock.unlockWrite(stamp);
}
}
public String get(String key) {
long stamp = lock.readLock();
try {
return map.get(key);
} finally {
lock.unlockRead(stamp);
}
}
public String optimisticGet(String key) {
long stamp = lock.tryOptimisticRead();
String value = map.get(key);
if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
value = map.get(key);
} finally {
lock.unlockRead(stamp);
}
}
return value;
}
}

AQS

AQS是AbstractQueuedSynchronizer的简称,即抽象队列同步器,从字面意思上理解:

抽象:抽象类,只实现一些主要逻辑,有些方法由子类实现; 队列:使用先进先出(FIFO)队列存储数据; 同步:实现了同步的功能。

AbstractQueuedSynchronizer使用了模板方法,是一个双端链表结构(FIFO双端队列)。

更多原理详见:深入浅出Java多线程 第十一章 AQS