同步和锁
在多线程编程中,同步和锁是确保线程安全的关键概念。当多个线程同时访问共享资源时,可能会导致数据不一致或竞态条件。为了解决这些问题,Java 提供了多种同步机制和锁机制。
1. 同步机制
1.1 同步方法
同步方法是最简单的同步机制之一。通过在方法声明中使用 synchronized 关键字,可以确保同一时间只有一个线程可以执行该方法。
public class SynchronizedExample {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
在这个例子中,increment 和 getCount 方法都是同步的,因此同一时间只有一个线程可以执行这些方法。
1.2 同步块
同步块允许你更细粒度地控制同步的范围。你可以选择一个对象作为锁,并在同步块中使用该对象来保护共享资源。
public class SynchronizedBlockExample {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
}
在这个例子中,lock 对象用于同步 increment 和 getCount 方法中的代码块。
2. 锁机制
2.1 ReentrantLock
ReentrantLock 是 Java 提供的一种可重入锁。它比内置的 synchronized 关键字更灵活,支持更多的功能,如公平锁、可中断锁等。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
在这个例子中,ReentrantLock 用于保护 increment 和 getCount 方法中的代码块。注意,unlock 操作必须在 finally 块中执行,以确保锁在任何情况下都能被释放。
2.2 ReadWriteLock
ReadWriteLock 允许多个线程同时读取共享资源,但在写操作时需要独占访问。这种锁机制适用于读多写少的场景。
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private int count = 0;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public void increment() {
lock.writeLock().lock();
try {
count++;
} finally {
lock.writeLock().unlock();
}
}
public int getCount() {
lock.readLock().lock();
try {
return count;
} finally {
lock.readLock().unlock();
}
}
}
在这个例子中,ReadWriteLock 用于区分读操作和写操作。多个线程可以同时读取 count,但在写操作时需要独占访问。
3. 实践练习
3.1 同步方法练习
编写一个程序,创建两个线程,每个线程调用同一个对象的同步方法 increment 1000 次。最后打印 count 的值,确保它是 2000。
public class SynchronizedMethodExercise {
public static void main(String[] args) throws InterruptedException {
SynchronizedExample example = new SynchronizedExample();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + example.getCount());
}
}
3.2 ReentrantLock 练习
编写一个程序,使用 ReentrantLock 实现与上面相同的逻辑。
public class ReentrantLockExercise {
public static void main(String[] args) throws InterruptedException {
ReentrantLockExample example = new ReentrantLockExample();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + example.getCount());
}
}
3.3 ReadWriteLock 练习
编写一个程序,使用 ReadWriteLock 实现一个简单的缓存。缓存支持多个线程同时读取,但在写入时需要独占访问。
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExercise {
private final Map<String, String> cache = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public String get(String key) {
lock.readLock().lock();
try {
return cache.get(key);
} finally {
lock.readLock().unlock();
}
}
public void put(String key, String value) {
lock.writeLock().lock();
try {
cache.put(key, value);
} finally {
lock.writeLock().unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReadWriteLockExercise cache = new ReadWriteLockExercise();
Thread writer = new Thread(() -> {
for (int i = 0; i < 100; i++) {
cache.put("key" + i, "value" + i);
}
});
Thread reader = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println(cache.get("key" + i));
}
});
writer.start();
reader.start();
writer.join();
reader.join();
}
}
4. 总结
同步和锁是多线程编程中的重要概念。通过使用 synchronized 关键字、ReentrantLock 和 ReadWriteLock,你可以有效地控制线程对共享资源的访问,避免数据不一致和竞态条件。
在实际开发中,选择合适的同步机制和锁机制取决于具体的应用场景。希望本教程能帮助你更好地理解和应用这些概念。