在Java中,锁可以分为公平锁(Fair Lock)和非公平锁(Nonfair Lock),它们的区别在于线程获取锁的顺序是否遵循公平性原则。
公平锁
公平锁是指多个线程按照它们发出请求的顺序获取锁,即先到先得的原则。当一个线程释放锁时,等待时间最长的线程将有更大的机会获取到锁。公平锁的优点是保证了资源的公平分配,并且避免饥饿现象。但是,由于需要维护一个等待队列,因此公平锁的性能通常相对较低。
非公平锁
非公平锁是指多个线程获取锁的顺序没有明确的规定,线程获取锁的机会是随机分配的。即使有新的线程等待获取锁时,当前持有锁的线程有可能再次获取到锁。非公平锁的优点是相对较高的吞吐量,因为它省去了维护等待队列的开销。但是,非公平锁可能会导致一些线程长时间地等待,从而产生“插队”现象,不遵循公平性原则。
ReentrantLock非公平案例
有一个资源类(Ticket ),包含一个number变量表示50张票,还有一个lock变量保证“票”资源被售票员有序的售出。
/**
* 资源类
*/
class Ticket {
private Integer number = 30;
private ReentrantLock lock = new ReentrantLock();
public void sale() {
lock.lock();
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出第 " + (number--) + " 张票,还剩下:" + number);
}
} finally {
lock.unlock();
}
}
}
三个售票员卖完30张票:
public static void main(String[] args) {
Ticket ticket = new Ticket();
//模拟三个售票员卖完50张票
new Thread(() -> {
for (int i = 0; i < 31; i++) {
ticket.sale();
}
}, "售票员A").start();
new Thread(() -> {
for (int i = 0; i < 31; i++) {
ticket.sale();
}
}, "售票员B").start();
new Thread(() -> {
for (int i = 0; i < 31; i++) {
ticket.sale();
}
}, "售票员C").start();
}
结果打印:
售票员A卖出第 30 张票,还剩下:29
售票员A卖出第 29 张票,还剩下:28
售票员A卖出第 28 张票,还剩下:27
售票员A卖出第 27 张票,还剩下:26
售票员A卖出第 26 张票,还剩下:25
售票员A卖出第 25 张票,还剩下:24
售票员A卖出第 24 张票,还剩下:23
售票员C卖出第 23 张票,还剩下:22
售票员C卖出第 22 张票,还剩下:21
售票员C卖出第 21 张票,还剩下:20
售票员C卖出第 20 张票,还剩下:19
售票员C卖出第 19 张票,还剩下:18
售票员C卖出第 18 张票,还剩下:17
售票员C卖出第 17 张票,还剩下:16
售票员C卖出第 16 张票,还剩下:15
售票员C卖出第 15 张票,还剩下:14
售票员C卖出第 14 张票,还剩下:13
售票员C卖出第 13 张票,还剩下:12
售票员C卖出第 12 张票,还剩下:11
售票员C卖出第 11 张票,还剩下:10
售票员C卖出第 10 张票,还剩下:9
售票员C卖出第 9 张票,还剩下:8
售票员C卖出第 8 张票,还剩下:7
售票员C卖出第 7 张票,还剩下:6
售票员C卖出第 6 张票,还剩下:5
售票员C卖出第 5 张票,还剩下:4
售票员C卖出第 4 张票,还剩下:3
售票员C卖出第 3 张票,还剩下:2
售票员C卖出第 2 张票,还剩下:1
售票员C卖出第 1 张票,还剩下:0
可以看到前面7张票是售票员A卖出去的,但是后面的27张票都是售票员C卖出去的。原因是我们在资源类中的ReentrantLock使用其默认的构造方法new出来的锁对象,ReentrantLock默认是非公平锁:
//ReentrantLock构造方法创建的是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
ReentrantLock公平案例
我们把资源类(Ticket)中的lock对象设置成公平锁。
/**
* 资源类
*/
class Ticket {
private Integer number = 30;
private ReentrantLock lock = new ReentrantLock(true);
public void sale() {
lock.lock();
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出第 " + (number--) + " 张票,还剩下:" + number);
}
} finally {
lock.unlock();
}
}
}
三个售票员卖完30张票:
public static void main(String[] args) {
Ticket ticket = new Ticket();
//模拟三个售票员卖完50张票
new Thread(() -> {
for (int i = 0; i < 31; i++) {
ticket.sale();
}
}, "售票员A").start();
new Thread(() -> {
for (int i = 0; i < 31; i++) {
ticket.sale();
}
}, "售票员B").start();
new Thread(() -> {
for (int i = 0; i < 31; i++) {
ticket.sale();
}
}, "售票员C").start();
}
结果打印:
售票员A卖出第 30 张票,还剩下:29
售票员A卖出第 29 张票,还剩下:28
售票员A卖出第 28 张票,还剩下:27
售票员B卖出第 27 张票,还剩下:26
售票员A卖出第 26 张票,还剩下:25
售票员C卖出第 25 张票,还剩下:24
售票员B卖出第 24 张票,还剩下:23
售票员A卖出第 23 张票,还剩下:22
售票员C卖出第 22 张票,还剩下:21
售票员B卖出第 21 张票,还剩下:20
售票员A卖出第 20 张票,还剩下:19
售票员C卖出第 19 张票,还剩下:18
售票员B卖出第 18 张票,还剩下:17
售票员A卖出第 17 张票,还剩下:16
售票员C卖出第 16 张票,还剩下:15
售票员B卖出第 15 张票,还剩下:14
售票员A卖出第 14 张票,还剩下:13
售票员C卖出第 13 张票,还剩下:12
售票员B卖出第 12 张票,还剩下:11
售票员A卖出第 11 张票,还剩下:10
售票员C卖出第 10 张票,还剩下:9
售票员B卖出第 9 张票,还剩下:8
售票员A卖出第 8 张票,还剩下:7
售票员C卖出第 7 张票,还剩下:6
售票员B卖出第 6 张票,还剩下:5
售票员A卖出第 5 张票,还剩下:4
售票员C卖出第 4 张票,还剩下:3
售票员B卖出第 3 张票,还剩下:2
售票员A卖出第 2 张票,还剩下:1
售票员C卖出第 1 张票,还剩下:0
可以看到30张票的出售情况,后面都是售票员A、售票员B、售票C个出售一张的情况。我们创建ReentrantLock实例通过指定构造方法中传入true创建了一个公平锁对象。
//通过传入参数(false/ture)可以指定是公平锁还是非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
选择使用公平锁还是非公平锁取决于具体的场景和需求。如果对线程之间的公平性要求比较高,或者需要避免饥饿现象,可以选择公平锁。如果对吞吐量更关注,并且能够容忍某些线程“插队”,可以选择非公平锁。