您好,登录后才能下订单哦!
在Java编程中,锁(Lock)是一种用于控制多个线程对共享资源访问的机制。锁的使用可以避免多个线程同时访问共享资源而导致的数据不一致性问题。Java提供了多种锁机制,包括内置锁(synchronized关键字)、显式锁(ReentrantLock类)、读写锁(ReentrantReadWriteLock类)等。本文将详细介绍Java中锁的相关知识点。
synchronized
是Java中最基本的锁机制,它可以用来修饰方法或代码块,确保同一时间只有一个线程可以执行被修饰的代码。
public synchronized void method() {
// 线程安全的代码
}
当一个线程调用一个被synchronized
修饰的实例方法时,它会获取该实例对象的锁,其他线程必须等待该锁释放后才能执行该方法。
public static synchronized void staticMethod() {
// 线程安全的代码
}
当一个线程调用一个被synchronized
修饰的静态方法时,它会获取该类的Class
对象的锁,其他线程必须等待该锁释放后才能执行该方法。
public void method() {
synchronized (this) {
// 线程安全的代码
}
}
synchronized
代码块可以指定一个对象作为锁,当线程进入代码块时,它会尝试获取该对象的锁,其他线程必须等待该锁释放后才能进入代码块。
synchronized
关键字使用的是内置锁(也称为监视器锁),它是一种可重入锁(Reentrant Lock)。可重入锁意味着同一个线程可以多次获取同一个锁,而不会导致死锁。
public class ReentrantExample {
public synchronized void outer() {
inner();
}
public synchronized void inner() {
// 线程安全的代码
}
}
在上面的例子中,outer
方法和inner
方法都被synchronized
修饰。当一个线程调用outer
方法时,它会获取该实例对象的锁,然后在outer
方法中调用inner
方法时,它会再次获取同一个锁。由于synchronized
是可重入锁,因此不会发生死锁。
synchronized
关键字虽然简单易用,但它也有一些局限性:
synchronized
无法设置获取锁的超时时间,如果锁一直被占用,其他线程将一直等待。synchronized
无法保证等待锁的线程按照申请锁的顺序获取锁,可能会导致某些线程长时间无法获取锁。为了克服synchronized
的局限性,Java提供了java.util.concurrent.locks
包中的ReentrantLock
类。ReentrantLock
是一种显式锁,它提供了比synchronized
更灵活的锁机制。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final Lock lock = new ReentrantLock();
public void method() {
lock.lock(); // 获取锁
try {
// 线程安全的代码
} finally {
lock.unlock(); // 释放锁
}
}
}
ReentrantLock
的使用方式与synchronized
类似,但它需要显式地调用lock()
方法获取锁,并在finally
块中调用unlock()
方法释放锁,以确保锁一定会被释放。
与synchronized
一样,ReentrantLock
也是一种可重入锁。同一个线程可以多次获取同一个锁,而不会导致死锁。
public class ReentrantLockExample {
private final Lock lock = new ReentrantLock();
public void outer() {
lock.lock();
try {
inner();
} finally {
lock.unlock();
}
}
public void inner() {
lock.lock();
try {
// 线程安全的代码
} finally {
lock.unlock();
}
}
}
在上面的例子中,outer
方法和inner
方法都使用了同一个ReentrantLock
对象。当一个线程调用outer
方法时,它会获取锁,然后在outer
方法中调用inner
方法时,它会再次获取同一个锁。由于ReentrantLock
是可重入锁,因此不会发生死锁。
ReentrantLock
提供了比synchronized
更高级的特性,包括:
ReentrantLock
提供了lockInterruptibly()
方法,允许线程在等待锁的过程中响应中断。
public void method() throws InterruptedException {
lock.lockInterruptibly(); // 可中断的获取锁
try {
// 线程安全的代码
} finally {
lock.unlock(); // 释放锁
}
}
如果一个线程在等待锁的过程中被中断,它将抛出InterruptedException
,从而可以响应中断。
ReentrantLock
提供了tryLock()
方法,允许线程在指定的时间内尝试获取锁,如果超时仍未获取到锁,则返回false
。
public void method() {
if (lock.tryLock(1, TimeUnit.SECONDS)) { // 尝试在1秒内获取锁
try {
// 线程安全的代码
} finally {
lock.unlock(); // 释放锁
}
} else {
// 超时未获取到锁的处理逻辑
}
}
tryLock()
方法可以避免线程长时间等待锁,从而提高程序的响应性。
ReentrantLock
提供了公平锁的机制,即按照线程申请锁的顺序来获取锁。公平锁可以避免某些线程长时间无法获取锁的问题。
private final Lock fairLock = new ReentrantLock(true); // 创建公平锁
公平锁虽然可以避免线程饥饿问题,但由于需要维护一个等待队列,因此性能通常比非公平锁要低。
特性 | synchronized | ReentrantLock |
---|---|---|
可重入性 | 是 | 是 |
可中断锁 | 否 | 是 |
超时锁 | 否 | 是 |
公平锁 | 否 | 是 |
性能 | 较高 | 较低 |
使用复杂度 | 简单 | 较复杂 |
在某些场景下,读操作远远多于写操作,如果使用普通的锁机制,会导致读操作和写操作互斥,从而降低并发性能。为了解决这个问题,Java提供了ReentrantReadWriteLock
类,它允许多个读线程同时访问共享资源,但在写线程访问时,所有读线程和其他写线程都会被阻塞。
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
public void readMethod() {
rwLock.readLock().lock(); // 获取读锁
try {
// 读操作
} finally {
rwLock.readLock().unlock(); // 释放读锁
}
}
public void writeMethod() {
rwLock.writeLock().lock(); // 获取写锁
try {
// 写操作
} finally {
rwLock.writeLock().unlock(); // 释放写锁
}
}
}
在上面的例子中,readMethod
方法获取读锁,允许多个线程同时执行读操作;writeMethod
方法获取写锁,确保同一时间只有一个线程执行写操作。
ReentrantReadWriteLock
支持锁降级,即一个线程在持有写锁的情况下可以获取读锁,然后在释放写锁后继续持有读锁。public void lockDowngrade() {
rwLock.writeLock().lock(); // 获取写锁
try {
// 写操作
rwLock.readLock().lock(); // 获取读锁(锁降级)
} finally {
rwLock.writeLock().unlock(); // 释放写锁
}
try {
// 读操作
} finally {
rwLock.readLock().unlock(); // 释放读锁
}
}
锁降级可以确保在写操作完成后,读操作仍然可以继续持有锁,从而避免其他写线程修改数据。
读写锁适用于读多写少的场景,可以显著提高并发性能。然而,如果写操作频繁,读写锁的性能可能会低于普通的锁机制,因为写锁的获取会导致所有读线程和其他写线程阻塞。
除了synchronized
、ReentrantLock
和ReentrantReadWriteLock
之外,Java还提供了其他一些锁机制,用于特定的并发场景。
StampedLock
是Java 8引入的一种新的锁机制,它提供了三种模式的锁:写锁、悲观读锁和乐观读锁。StampedLock
的性能通常比ReentrantReadWriteLock
更高,尤其是在读多写少的场景下。
import java.util.concurrent.locks.StampedLock;
public class StampedLockExample {
private final StampedLock stampedLock = new StampedLock();
public void writeMethod() {
long stamp = stampedLock.writeLock(); // 获取写锁
try {
// 写操作
} finally {
stampedLock.unlockWrite(stamp); // 释放写锁
}
}
public void readMethod() {
long stamp = stampedLock.readLock(); // 获取悲观读锁
try {
// 读操作
} finally {
stampedLock.unlockRead(stamp); // 释放读锁
}
}
public void optimisticReadMethod() {
long stamp = stampedLock.tryOptimisticRead(); // 获取乐观读锁
// 读操作
if (!stampedLock.validate(stamp)) { // 检查乐观读锁是否有效
stamp = stampedLock.readLock(); // 获取悲观读锁
try {
// 读操作
} finally {
stampedLock.unlockRead(stamp); // 释放读锁
}
}
}
}
StampedLock
的乐观读锁允许在不阻塞写线程的情况下进行读操作,从而提高并发性能。然而,乐观读锁的有效性需要通过validate()
方法进行检查,如果发现数据已被修改,则需要重新获取悲观读锁。
Condition
是ReentrantLock
提供的一种线程等待/通知机制,类似于Object
的wait()
和notify()
方法。Condition
允许线程在特定条件下等待,并在条件满足时被唤醒。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionExample {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private boolean flag = false;
public void await() throws InterruptedException {
lock.lock();
try {
while (!flag) {
condition.await(); // 等待条件满足
}
// 条件满足后的操作
} finally {
lock.unlock();
}
}
public void signal() {
lock.lock();
try {
flag = true;
condition.signal(); // 唤醒等待的线程
} finally {
lock.unlock();
}
}
}
Condition
的使用方式与wait()
和notify()
类似,但它提供了更灵活的线程等待/通知机制,可以创建多个Condition
对象,从而实现更复杂的线程同步。
在高并发场景下,锁的性能对系统的整体性能有着重要影响。以下是一些常见的锁性能优化策略:
锁的粒度越小,锁的竞争就越少,从而提高并发性能。例如,可以将一个大的同步代码块拆分为多个小的同步代码块,或者使用细粒度的锁(如ConcurrentHashMap
中的分段锁)。
在某些场景下,可以使用无锁数据结构(如AtomicInteger
、AtomicReference
等)来替代锁,从而避免锁的开销。
在读多写少的场景下,使用读写锁可以显著提高并发性能。
死锁是指多个线程互相等待对方释放锁,从而导致所有线程都无法继续执行。为了避免死锁,可以遵循以下原则:
tryLock()
方法设置获取锁的超时时间,避免线程长时间等待锁。Java提供了多种锁机制,包括内置锁(synchronized
)、显式锁(ReentrantLock
)、读写锁(ReentrantReadWriteLock
)等。每种锁机制都有其适用的场景和优缺点。在实际开发中,应根据具体的需求选择合适的锁机制,并通过优化锁的粒度、使用无锁数据结构等策略来提高并发性能。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。