详细介绍java中的锁机制
Java中的锁机制是并发编程中用来确保多个线程在访问共享资源时不发生数据竞争的关键工具。Java提供了多种锁机制来帮助开发者管理线程同步,主要包括以下几种:
1. synchronized 锁
synchronized
是Java中最基本的锁机制,它可以用来修饰方法或者代码块,从而确保同一时间只有一个线程可以执行被锁定的代码。根据使用场景的不同,synchronized
可以分为以下两类:
-
同步方法:通过在方法声明中使用
synchronized
关键字,使得该方法只能被一个线程访问。例如:public synchronized void synchronizedMethod() { // 方法内容 }
当一个线程调用该方法时,其他线程必须等待直到该线程完成方法的执行。
-
同步代码块:使用
synchronized
锁定某一块代码,而不是整个方法。这样可以更精细地控制锁的粒度,提升性能。例如:private Object lock=new Object(); public void method() { synchronized(lock) { // 需要同步的代码 } }
这里锁的是当前对象
lock
,也可以指定其他对象作为锁。
在 Java 中,任何对象都可以作为锁。Object
是所有 Java 类的基类,这意味着任何对象都隐式地继承了Object
类的方法,包括用于同步的wait()
、notify()
和notifyAll()
方法。 因此,使用Object
作为锁可以方便地利用这些内置的同步机制。
注意:synchronized
是一个阻塞式的锁,当一个线程获取了锁,其他线程必须等待它释放锁后才能进入同步代码。这种机制适合小规模的同步需求,但在大规模并发下性能会受到影响。
2. ReentrantLock 锁
ReentrantLock
是Java中一个更灵活的锁机制,它在java.util.concurrent.locks
包中,是一种可重入锁。与synchronized
相比,ReentrantLock
提供了更多的功能,例如:
-
手动释放锁:在
synchronized
中,锁会自动释放,而ReentrantLock
需要手动释放锁,通常使用try-finally
结构来确保锁最终被释放:ReentrantLock lock = new ReentrantLock(); lock.lock(); try { // 需要同步的代码 } finally { lock.unlock(); }
-
公平锁与非公平锁:可以创建公平锁(根据请求锁的顺序来获取锁)和非公平锁(默认,可能会“插队”)。例如:
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
-
可中断的锁请求:
ReentrantLock
支持线程在等待锁时可以响应中断操作,避免无限等待。 -
锁尝试机制:提供
tryLock()
方法,在尝试获取锁时可以指定等待时间。如果在指定时间内获取不到锁,就放弃,避免了阻塞。例如:if (lock.tryLock(1000, TimeUnit.MILLISECONDS)) { try { // 需要同步的代码 } finally { lock.unlock(); } }
3. ReadWriteLock 读写锁
ReadWriteLock
接口和它的实现类ReentrantReadWriteLock
,提供了一种分离读锁和写锁的机制。其核心思想是:
- 读锁:允许多个线程同时读取共享资源,适合读多写少的场景。
- 写锁:在一个线程写数据时,不允许其他线程读或写。
这种机制可以有效提高并发性能。例如:
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
rwLock.readLock().lock();
try {
// 读操作
} finally {
rwLock.readLock().unlock();
}
rwLock.writeLock().lock();
try {
// 写操作
} finally {
rwLock.writeLock().unlock();
}
4. StampedLock
StampedLock
是ReadWriteLock
的改进版,主要用于高性能的读多写少场景。它引入了一个称为“戳”的概念,可以在读操作中乐观地获取锁。
- 乐观读锁:不会阻止写操作,可以提升读操作的性能。在读操作时,若没有写锁,可以快速读取。如果写锁在读操作时发生,乐观读锁会自动失效,变成悲观读锁。
- 悲观读锁和写锁:类似于
ReentrantReadWriteLock
中的锁。
使用示例:
StampedLock stampedLock = new StampedLock();
long stamp = stampedLock.readLock();
try {
// 读操作
} finally {
stampedLock.unlockRead(stamp);
}