锁的种类
Java中的锁是一种同步机制,用于保护共享资源,防止多个线程同时访问。在Java中,锁可以分为不同的类型,每种类型都有其特定的优缺点和适用场景。
1.重量级锁(Heavy Lock):重量级锁是一种互斥锁,它提供了最高的线程安全性。当一个线程获得了重量级锁时,其他线程必须等待该线程释放锁才能获得锁。因此,重量级锁的开销较大,可能会导致线程阻塞和性能下降。但是,由于重量级锁提供了最高的线程安全性,所以在需要确保数据完整性和一致性的情况下,重量级锁是非常有用的。
2.轻量级锁(Lightweight Lock):轻量级锁是一种比重量级锁更轻量级的同步机制。它使用CAS操作来实现互斥访问,并且可以在没有竞争的情况下快速获取锁。因此,轻量级锁的开销较小,可以提高并发性能。但是,由于轻量级锁不是互斥的,所以如果多个线程同时尝试获取锁,则可能导致数据不一致的问题。
3.互斥锁(Mutex Lock):互斥锁是一种基本的同步机制,它用于保护共享资源,防止多个线程同时访问。互斥锁提供了原子性操作,即在任何时候只能有一个线程访问共享资源。Java中的synchronized关键字就是一种互斥锁。
4.读写锁(Read-Write Lock):读写锁允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。读写锁可以提高并发性能,特别是在读操作比写操作频繁的情况下。Java中的ReentrantReadWriteLock就是一种读写锁。
5.可重入锁(Reentrant Lock):可重入锁允许同一个线程多次获取同一个锁,这意味着线程可以在执行完同步代码后再次获取该锁而不需要重新竞争。可重入锁通常用于递归调用等情况。Java中的ReentrantLock就是一种可重入锁。
6.自旋锁(Spin Lock):自旋锁是一种基于忙等待的同步机制,它在获取锁时会一直循环等待,直到获取到锁为止。自旋锁的开销较大,因为它会占用大量的CPU时间。但是,自旋锁可以提供极高的并发性能,特别是在竞争激烈的情况下。
7.乐观锁(Optimistic Lock):乐观锁是一种不涉及实际锁定的同步机制,它通过版本号或哈希值来检查共享资源是否被其他线程修改过。如果没有修改,则可以更新共享资源。乐观锁可以提高并发性能,但可能会导致数据不一致的问题。Java中的CAS操作就是一种乐观锁实现方式。
8.悲观锁(Pessimistic Lock):悲观锁是一种实际锁定的同步机制,它在访问共享资源之前先将其锁定。悲观锁可以保证数据的一致性,但会导致并发性能下降。Java中的synchronized关键字就是一种悲观锁实现方式。
以下是Java中各种锁的实现代码示例:
1. synchronized关键字实现互斥锁[悲观锁]:
public class SynchronizedDemo { private int count = 0; public synchronized void increment() { count++; } public int getCount() { return count; } }
2. ReentrantLock类实现可重入锁:
import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockDemo { private final ReentrantLock lock = new ReentrantLock(); public void doSomething() { lock.lock(); // 可重入锁必须先获取到锁才能执行同步代码,因此需要使用lock()方法获取锁 try { // 同步代码块 } finally { lock.unlock(); // 在finally块中释放锁,即使发生异常也需要释放锁 } } }
3. ReadWriteLock类实现读写锁:
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockDemo { private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public void readData() { rwl.readLock().lock(); // 只允许读取操作获取到读锁,因此需要使用readLock()方法获取读锁 try { // 读取共享资源的操作 } finally { rwl.readLock().unlock(); // 在finally块中释放读锁,即使发生异常也需要释放读锁 } } public void writeData() { rwl.writeLock().lock(); // 只允许写入操作获取到写锁,因此需要使用writeLock()方法获取写锁 try { // 写入共享资源的操作 } finally { rwl.writeLock().unlock(); // 在finally块中释放写锁,即使发生异常也需要释放写锁 } } }
4.自旋锁示例代码,它使用Java中的ReentrantLock类实现:
import java.util.concurrent.locks.ReentrantLock; public class SpinLockDemo { private final ReentrantLock lock = new ReentrantLock(); public void doSomething() { while (true) { try { lock.lockInterruptibly(); // 尝试获取锁,如果获取失败则进入循环等待 break; } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 抛出异常时中断当前线程 } } try { // 同步代码块 } finally { lock.unlock(); // 在finally块中释放锁,即使发生异常也需要释放锁 } } }
在上面的示例代码中,doSomething()方法会一直循环等待获取锁。当它尝试获取锁时,如果获取失败,则会抛出InterruptedException异常并中断当前线程。一旦它成功获取到锁,就可以执行同步代码块了。最后,无论同步代码块是否正常执行,doSomething()方法都会在finally块中释放锁。这样可以确保即使发生异常,也不会导致锁没有被正确释放的问题。
5. 乐观锁示例代码,它使用Java中的CAS操作实现:
import java.util.concurrent.atomic.AtomicInteger; public class OptimisticLockDemo { private final AtomicInteger count = new AtomicInteger(0); public void increment() { int currentCount = count.get(); while (true) { int nextCount = currentCount + 1; if (count.compareAndSet(currentCount, nextCount)) { break; } else { currentCount = nextCount; } } } }
在上面的示例代码中,increment()方法会不断尝试将共享资源的计数器从当前值加1。如果成功更新了计数器的值,则说明没有其他线程在同一时间点对共享资源进行了修改,因此可以安全地进行同步操作。否则,increment()方法会继续循环等待,直到获取到锁为止。这样可以确保即使发生竞争条件,也不会导致数据的不一致性问题。