在 Java 多线程编程中,为了保证数据的一致性和线程安全,需要使用同步机制来协调多个线程对共享资源的访问。锁和信号量是两种常用的同步工具,下面将对它们进行详细解析。
锁
- 概念:锁是一种用于控制多个线程对共享资源进行访问的机制。当一个线程获取到锁后,其他线程就必须等待,直到该线程释放锁,才能有机会获取锁并访问共享资源。
- 实现方式
- synchronized 关键字:这是 Java 中最基本的锁实现方式。可以用来修饰方法或代码块。当修饰方法时,锁对象是当前实例对象(对于实例方法)或类对象(对于静态方法);当修饰代码块时,需要指定一个锁对象。例如:
java
public class SynchronizedExample {
private int count = 0;
public synchronized void increment() {
count++;
}
public void decrement() {
synchronized (this) {
count--;
}
}
}
- ReentrantLock 类:这是 Java 5 之后提供的一种更灵活的锁实现。它具有可重入性,即同一个线程可以多次获取同一个锁。与
synchronized
关键字相比,它提供了更多的功能,如可中断锁获取、公平锁等。例如:
java
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public void decrement() {
lock.lock();
try {
count--;
} finally {
lock.unlock();
}
}
}
- 锁的优化:Java 虚拟机对锁的实现进行了一系列的优化,如偏向锁、轻量级锁和锁膨胀等机制。偏向锁是为了优化只有一个线程访问同步块的情况,通过在对象头中记录偏向的线程 ID,避免不必要的锁获取和释放操作;轻量级锁则是在多个线程交替访问同步块时,使用 CAS 操作来尝试获取锁,减少重量级锁的使用,提高性能。
信号量
- 概念:信号量是一种用于控制同时访问某个资源的线程数量的同步工具。它维护了一个许可计数器,线程在访问资源前需要获取许可,只有当许可数量大于 0 时,线程才能获取许可并访问资源,否则线程将被阻塞。当线程访问完资源后,需要释放许可,以便其他线程可以获取许可。
- 实现方式:在 Java 中,通过
Semaphore
类来实现信号量。例如,以下代码实现了一个简单的信号量示例,限制同时有两个线程可以访问共享资源:
java
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static Semaphore semaphore = new Semaphore(2);
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 获取到许可,开始访问资源");
// 模拟访问资源的操作,休眠一段时间
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 访问资源结束,释放许可");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
- 应用场景:信号量常用于控制资源的并发访问数量,例如数据库连接池的管理,通过信号量可以限制同时获取数据库连接的线程数量,避免过多的线程同时访问数据库导致性能下降。
锁与信号量的比较
- 功能侧重点:锁主要用于保护共享资源,确保在同一时刻只有一个线程能够访问;而信号量更侧重于控制并发访问的数量,可以允许多个线程同时访问共享资源,但有数量限制。
- 实现机制:锁通常是基于对象的监视器实现的,通过获取和释放锁来控制线程的访问;信号量则是通过维护许可计数器来实现对线程的控制。
- 使用场景:当需要确保共享资源的互斥访问时,使用锁比较合适;当需要控制并发访问的数量,例如限制资源的并发使用量时,信号量是更好的选择。
锁和信号量都是 Java 多线程编程中重要的同步机制,开发人员需要根据具体的业务需求和场景来选择合适的同步工具,以确保多线程程序的正确性和性能。