深入探讨Java并发:可重入锁、读写锁、(非)公平锁的全面解析
2023-10-30 19:51:23
在专栏前面的文章——《线程并发的同步控制》中,我们已经学过如何用synchronized加锁进行同步控制了。不过,在Java中除了synchronized外,还有可重入锁(ReentrantLock)、读写锁(ReadWriteLock)等锁类型,它们具有不同的特性,能够满足不同的并发编程需求。本文将对这些锁进行全面解析,帮助读者深入理解并掌握Java并发编程技术。
可重入锁
可重入锁(ReentrantLock)是一种互斥锁,它允许同一个线程对同一个锁对象进行多次加锁。当线程再次试图对已加锁的锁对象进行加锁时,不会被阻塞,而是会递增锁的持有计数。当线程完成锁定的操作后,需要对锁对象进行相同的次数的解锁操作,使持有计数减为0,才能释放锁。可重入锁的优势在于它避免了线程死锁的风险,因为它允许同一个线程对同一个锁对象进行多次加锁。
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private ReentrantLock lock = new ReentrantLock();
private int counter = 0;
public void incrementCounter() {
lock.lock();
try {
counter++;
} finally {
lock.unlock();
}
}
public int getCounter() {
lock.lock();
try {
return counter;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockExample example = new ReentrantLockExample();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.incrementCounter();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.incrementCounter();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final counter value: " + example.getCounter());
}
}
读写锁
读写锁(ReadWriteLock)是一种允许多个线程同时读写共享数据的锁。读写锁分为读锁和写锁,读锁允许多个线程同时获取,而写锁只能被一个线程获取。当一个线程获取写锁后,其他线程不能获取读锁或写锁,直到写锁释放。读写锁的优势在于它提高了并发读取的性能,同时保证了写操作的独占性。
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private ReadWriteLock lock = new ReentrantReadWriteLock();
private int counter = 0;
public void incrementCounter() {
lock.writeLock().lock();
try {
counter++;
} finally {
lock.writeLock().unlock();
}
}
public int getCounter() {
lock.readLock().lock();
try {
return counter;
} finally {
lock.readLock().unlock();
}
}
public static void main(String[] args) {
ReadWriteLockExample example = new ReadWriteLockExample();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.incrementCounter();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.getCounter();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final counter value: " + example.getCounter());
}
}
公平锁与非公平锁
公平锁和非公平锁都是指可重入锁的两种实现方式。公平锁保证了线程获取锁的顺序与它们请求锁的顺序一致,而非公平锁则没有这样的保证。当多个线程同时请求同一个锁时,公平锁会按照它们请求锁的顺序来获取锁,而非公平锁则可能让后请求锁的线程先获取锁。公平锁的优势在于它避免了饥饿现象的发生,而非公平锁的优势在于它在某些场景下具有更好的性能。
import java.util.concurrent.locks.ReentrantLock;
public class FairLockExample {
private ReentrantLock lock = new ReentrantLock(true);
// true表示公平锁,false表示非公平锁
private int counter = 0;
public void incrementCounter() {
lock.lock();
try {
counter++;
} finally {
lock.unlock();
}
}
public int getCounter() {
lock.lock();
try {
return counter;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
FairLockExample example = new FairLockExample();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.incrementCounter();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.incrementCounter();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final counter value: " + example.getCounter());
}
}
总结
可重入锁、读写锁、公平锁和非公平锁都是Java并发编程中的重要锁类型,它们具有不同的特性和适用场景。可重入锁允许同一个线程对同一个锁对象进行多次加锁,避免了线程死锁的风险。读写锁允许多个线程同时读写共享数据,提高了并发读取的性能,同时保证了写操作的独占性。公平锁和非公平锁都是指可重入锁的两种实现方式,公平锁保证了线程获取锁的顺序与它们请求锁的顺序一致,而非公平锁则没有这样的保证。