锁类型选择
- 乐观锁
- 适用场景:适用于读多写少的场景,因为乐观锁假设在大部分情况下数据不会发生冲突,只有在更新数据时才会检查是否有冲突。
- 实现方式:通常使用版本号(version)或者时间戳(timestamp)来实现。在读取数据时,同时获取数据的版本号,在更新数据时,将当前版本号与数据库中存储的版本号进行比较,如果相同则更新数据并将版本号加1,否则重试操作。
- 代码示例:
// 假设这是数据库中的数据对象
class DataObject {
private int value;
private int version;
public DataObject(int value) {
this.value = value;
this.version = 0;
}
public int getValue() {
return value;
}
public int getVersion() {
return version;
}
public void setValue(int value, int expectedVersion) {
if (this.version == expectedVersion) {
this.value = value;
this.version++;
} else {
throw new RuntimeException("Version conflict");
}
}
}
// 模拟更新操作
public class OptimisticLockExample {
public static void main(String[] args) {
DataObject data = new DataObject(10);
int expectedVersion = data.getVersion();
int newValue = 20;
try {
data.setValue(newValue, expectedVersion);
System.out.println("Update successful, new value: " + data.getValue());
} catch (RuntimeException e) {
System.out.println("Update failed: " + e.getMessage());
}
}
}
- 悲观锁
- 适用场景:适用于写多读少的场景,悲观锁认为数据在被访问时很可能会发生冲突,所以在访问数据前就加锁。
- 实现方式:在Java中,synchronized关键字就是一种悲观锁的实现。当一个线程进入synchronized块时,会获取对象的锁,其他线程无法进入,直到该线程释放锁。
- 代码示例:
class PessimisticLockExample {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) {
PessimisticLockExample example = new PessimisticLockExample();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + example.getCount());
}
}
- 读写锁
- 适用场景:适用于读多写少的场景,读写锁允许多个线程同时进行读操作,但只允许一个线程进行写操作,并且写操作时不允许读操作。
- 实现方式:在Java中,可以使用
ReentrantReadWriteLock
来实现读写锁。
- 代码示例:
import java.util.concurrent.locks.ReentrantReadWriteLock;
class ReadWriteLockExample {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private int data = 0;
public void read() {
lock.readLock().lock();
try {
System.out.println("Reading data: " + data);
} finally {
lock.readLock().unlock();
}
}
public void write(int newData) {
lock.writeLock().lock();
try {
data = newData;
System.out.println("Writing data: " + data);
} finally {
lock.writeLock().unlock();
}
}
}
public class Main {
public static void main(String[] args) {
ReadWriteLockExample example = new ReadWriteLockExample();
Thread readThread1 = new Thread(() -> example.read());
Thread readThread2 = new Thread(() -> example.read());
Thread writeThread = new Thread(() -> example.write(10));
readThread1.start();
readThread2.start();
writeThread.start();
}
}
锁粒度确定
- 粗粒度锁
- 特点:对整个资源或者较大范围的资源加锁。优点是实现简单,缺点是并发性能差,因为同一时间只有一个线程能访问整个资源,可能导致其他线程长时间等待。
- 适用场景:适用于资源较少且访问频率不高的场景,例如在系统启动时初始化一些全局配置信息,对全局配置对象加一个粗粒度锁即可。
- 细粒度锁
- 特点:对资源进行细分,每个小部分都可以独立加锁。优点是并发性能好,因为不同线程可以同时访问不同部分的资源。缺点是实现复杂,可能会因为锁的数量过多而增加管理成本,并且可能会增加死锁的风险。
- 适用场景:适用于资源较多且访问频率高的场景,例如在一个大型的缓存系统中,可以对每个缓存分区使用细粒度锁,不同线程可以同时操作不同的缓存分区。
使用Java并发包优化锁的使用
- 使用
ConcurrentHashMap
替代HashMap
- 原因:
HashMap
不是线程安全的,在高并发环境下使用需要手动加锁,而ConcurrentHashMap
是线程安全的,并且采用了分段锁的机制,允许多个线程同时访问不同的段,提高了并发性能。
- 代码示例:
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key1", 1);
map.put("key2", 2);
System.out.println(map.get("key1"));
}
}
- 使用
CountDownLatch
进行线程同步
- 作用:
CountDownLatch
可以让一个或多个线程等待其他线程完成操作后再继续执行。
- 代码示例:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) {
int threadCount = 3;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
// 模拟一些操作
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " has finished");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}).start();
}
try {
latch.await();
System.out.println("All threads have finished, main thread can continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 使用
Semaphore
控制并发访问数量
- 作用:
Semaphore
可以控制同时访问某个资源的线程数量。
- 代码示例:
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
int permits = 2;
Semaphore semaphore = new Semaphore(permits);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " has acquired the semaphore");
// 模拟一些操作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
System.out.println(Thread.currentThread().getName() + " has released the semaphore");
}
}).start();
}
}
}