目录
一、悲观锁
悲观锁(Pessimistic Lock)是一种传统的线程同步方式,它假设在整个数据处理过程中,其他线程可能会修改数据,因此在访问共享资源之前,需要先对其进行加锁,这样可以避免其他线程修改数据,从而保证数据的安全性。
悲观锁的实现方式有很多,最常见的是使用synchronized关键字或ReentrantLock类来实现。这些机制会将整个代码块或方法锁定,确保只有一个线程能够访问共享资源,其他线程必须等待锁释放后才能进行访问。
悲观锁的缺点是效率较低,因为每个线程在访问共享资源时都需要获得锁,如果锁的竞争比较激烈,就会导致大量的线程被阻塞,从而降低程序的并发能力。因此,在并发编程中,应该尽量使用乐观锁等非阻塞算法来提高程序的效率和可伸缩性。
下面是一个使用Java悲观锁(synchronized)的样例代码:
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
在这个例子中,我们定义了一个Counter类来统计数字。在increment()方法和getCount()方法中,我们都使用了synchronized关键字来对整个方法进行加锁,确保只有一个线程能够访问count变量。
由于synchronized关键字会对整个方法进行加锁,因此该实现方式并不是很高效。如果多个线程同时调用increment()方法或getCount()方法,它们可能会被阻塞,从而导致程序的吞吐量降低。因此,在实际开发中,我们应该尽量避免使用悲观锁,而采用乐观锁等非阻塞算法来提高程序的效率和可伸缩性。
二、乐观锁
乐观锁是一种并发控制的方式,它假设数据冲突的概率比较小,因此在访问共享资源时不会加锁,而是在更新数据时检查数据是否被其他线程进行了修改,如果没有则更新成功,否则需要重新尝试。
乐观锁通常通过版本号或时间戳等机制来实现。当一个线程读取数据时,会记录下当前的版本号或时间戳;当另一个线程对同样的数据进行修改时,会增加版本号或更新时间戳。在更新数据时,线程会将自己记录的版本号或时间戳与数据库中的进行比较,如果相同则说明没有其他线程对数据进行过修改,可以更新成功;否则需要重新读取数据后再次尝试更新。
乐观锁相对于悲观锁(即加锁)来说,能够提高系统的并发性和吞吐量,但也存在一定的风险,如果数据冲突的概率比较大,则可能导致大量的重试和失败操作。因此,在使用乐观锁时需要评估数据冲突的概率和重试的代价,以及选择适当的版本号或时间戳机制来确保正确性。
乐观锁是一种非阻塞算法,它假设在数据操作过程中不会有其他线程对数据进行修改,因此不需要加锁,可以提高程序的并发能力和效率。Java中常用的乐观锁实现方式是基于CAS(Compare and Swap)机制,下面是一个简单的Java代码样例:
import java.util.concurrent.atomic.AtomicInteger;
public class OptimisticLockExample {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
int expectedValue, newValue;
do {
expectedValue = count.get();
newValue = expectedValue + 1;
} while (!count.compareAndSet(expectedValue, newValue));
}
public static void main(String[] args) throws InterruptedException {
OptimisticLockExample example = new OptimisticLockExample();
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();
thread1.join();
thread2.join();
System.out.println("Count: " + example.count);
}
}
在这个示例中,我们使用AtomicInteger类作为计数器,并且使用CAS机制来实现乐观锁。increment()方法首先获取当前值和期望值,如果二者相等,则更新计数器的值,否则继续尝试更新直到成功。在main()方法中,我们启动两个线程来访问increment()方法,并且分别对计数器进行1000次递增操作。最后,输出计数器的值,可以看到输出结果为2000,说明乐观锁实现成功。
三、悲观锁和乐观锁的区别
Java中的悲观锁和乐观锁是两种不同的并发控制方式。
- 悲观锁
悲观锁是指在访问共享资源时,采用加锁方式来保证同一时间只有一个线程可以访问该资源。Java中的synchronized关键字和ReentrantLock类都是悲观锁的实现方式。
悲观锁的优点是简单易用,并且可以避免数据冲突问题,但是它需要加锁和释放锁,会影响程序的性能,特别是当多个线程频繁竞争同一个锁时,容易导致死锁和性能瓶颈。
- 乐观锁
乐观锁是指在访问共享资源时,假定数据不会产生冲突,因此不加锁,而是在更新数据时检查数据是否被其他线程修改过。Java中的CAS(Compare and Swap)机制是乐观锁的一种实现方式。
乐观锁的优点是没有锁的开销,可以提高程序的性能,但是需要根据具体业务场景选择适当的版本号或时间戳机制来确保数据的正确性,如果数据冲突的概率比较大,则可能导致很多重试和失败操作。
总之,在Java中,悲观锁和乐观锁都是实现并发控制的重要手段,需要根据具体情况来选择适当的机制。如果共享资源访问比较频繁或数据冲突的概率比较大,可以考虑使用悲观锁;如果共享资源访问比较少或数据冲突的概率比较小,可以考虑使用乐观锁。