Java 中的锁(Locking)机制主要是为了解决多线程环境下,对共享资源并发访问时的同步和互斥控制,以确保共享资源的安全访问。
锁的作用主要体现在以下几个方面:
在 Java 中有很多锁策略,用于对锁进行分类和指导锁的(具体)实现,这些锁策略包括以下内容:
在 Java 中也有一些具体的锁实现,用于代码层面的锁操作以此来保证线程安全的,这些常见的锁实现有以下几个:
synchronized 可以用来修饰普通方法、静态方法和代码块。
public synchronized void method() {
// .......
}
当 synchronized 修饰普通方法时,被修饰的方法被称为同步方法,其作用范围是整个方法,作用的对象是调用这个方法的对象。
public static synchronized void staticMethod() {
// .......
}
当 synchronized 修饰静态的方法时,其作用的范围是整个方法,作用对象是调用这个类的所有对象。
为了减少锁的粒度,我们可以选择在一个方法中的某个部分使用 synchronized 来修饰(一段代码块),从而实现对一个方法中的部分代码进行加锁,实现代码如下:
public void classMethod() throws InterruptedException {
// 前置代码...
// 加锁代码
synchronized (SynchronizedExample.class) {
// ......
}
// 后置代码...
}
以上代码在执行时,被修饰的代码块称为同步语句块,其作用范围是大括号“{}”括起来的代码块,作用的对象是调用这个代码块的对象。
ReentrantLock 基本使用:
// 1. 创建ReentrantLock对象
ReentrantLock lock = new ReentrantLock();
// 2.获取锁
lock.lock();
try {
// 3.得到锁,执行需要同步的代码块
} finally {
// 4.释放锁
lock.unlock();
}
进阶使用:尝试获取锁并设定超时时间(可选):
ReentrantLock lock = new ReentrantLock();
// 尝试获取锁,等待2秒,超时返回false
boolean locked = lock.tryLock(2, TimeUnit.SECONDS);
if (locked) {
try {
// 执行需要同步的代码块
} finally {
lock.unlock();
}
}
ReentrantReadWriteLock 特点如下:
也就是说:读读不互斥、读写互斥、写写互斥。
ReentrantReadWriteLock 基础使用如下:
// 创建 ReentrantReadWriteLock 对象
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
// 创建读锁
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
// 获取读锁
readLock.lock();
try {
// 读取共享资源的操作
} finally {
// 释放读锁
readLock.unlock();
}
// 创建写锁
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
// 获取写锁
writeLock.lock();
try {
// 写入共享资源的操作
} finally {
// 释放写锁
writeLock.unlock();
}
StampedLock 有三种读写方法:
其中 readLock() 和 writeLock() 方法与 ReentrantReadWriteLock 的用法类似,而 tryOptimisticRead() 方法则是 StampedLock 引入的新方法,它用于非常短的读操作,它是使用如下:
// 创建 StampedLock 实例
StampedLock lock = new StampedLock();
// 获取乐观读锁
long stamp = lock.tryOptimisticRead();
// 读取共享变量
if (!lock.validate(stamp)) { // 检查乐观读锁是否有效
stamp = lock.readLock(); // 如果乐观读锁无效,则获取悲观读锁
try {
// 重新读取共享变量
} finally {
lock.unlockRead(stamp); // 释放悲观读锁
}
}
// 获取悲观读锁
long stamp = lock.readLock();
try {
// 读取共享变量
} finally {
lock.unlockRead(stamp); // 释放悲观读锁
}
// 获取写锁
long stamp = lock.writeLock();
try {
// 写入共享变量
} finally {
lock.unlockWrite(stamp); // 释放写锁
}
使用乐观读锁的特性可以提高读操作的并发性能,适用于读多写少的场景。如果乐观读锁获取后,在读取共享变量前发生了写入操作,则 validate 方法会返回 false,此时需要转换为悲观读锁或写锁重新访问共享变量。
StampedLock 底层是如何实现的?什么是 AQS?
本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。