jvm 自带的关键字synchronized
和volatile
,可以解决一下简单需要锁的场景。
实例锁(修饰实例方法),同步代码块(实例锁),class 锁(修饰静态方法)
synchronized 具有可重入性
classMyClass {public synchronized void method1() {method2();}public synchronized void method2() {}}
严格意义上,volatile 和锁无关。
在某些情况下优于 synchronized
(1)volatile 保证可见性
被 volatile 修饰之后,保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
(2)volatile 无法保证原子性
但是不能保证原子性,例如用 volatitle 修饰 int,多线程自增,仍然无法保证获取想要的值。
自增操作是不具备原子性的,它包括读取变量的原始值、进行加 1 操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行。
ReentrantLock 和 ReentrantReadWriteLock 都是可重入锁
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 具有‘写饥饿’情况,即在发生写锁占用的时候,阻塞其他读写锁。
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 实现了“读写锁”的功能,它支持三种访问模式:读、写和乐观读,并且性能比 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是AbstractQueuedSynchronizer的简称,即抽象队列同步器,从字面意思上理解:
抽象:抽象类,只实现一些主要逻辑,有些方法由子类实现; 队列:使用先进先出(FIFO)队列存储数据; 同步:实现了同步的功能。
AbstractQueuedSynchronizer使用了模板方法,是一个双端链表结构(FIFO双端队列)。
更多原理详见:深入浅出Java多线程 第十一章 AQS