Java公平锁和非公平锁的用法(附带实例)
公平锁和非公平锁是并发编程中用来控制多线程对共享资源访问的两种锁策略。
公平锁的优点在于等待锁的线程不会饿死,因为每个线程最终都会获取锁。这避免了所谓的“线程饥饿”问题。但是,在实现公平策略时通常会降低吞吐量,因为需要维护一个有序队列来确保锁分配的顺序,这会导致线程切换和响应时间增加。
在 Java 中,ReentrantLock 类实现了公平锁和非公平锁,下面我们看一下公平锁的使用示例:
非公平锁的优点是在多数情况下它的性能比公平锁的要好,因为线程可以直接抢占锁而不需要在队列中等待。但是它的缺点是在高负载下,线程可能会遭遇饥饿,即等待很长时间都无法获取锁。
在 Java 中,ReentrantLock 默认情况下使用的是非公平锁。通过传递 false 或不传递任何参数给 ReentrantLock 的构造函数,可以创建一个非公平的 ReentrantLock。
synchronized 也是一种非公平锁,由于它的线程调度机制与 ReentrantLock 的不同,不是通过 AQS 实现的,所以没有办法使其变成公平锁。
下面我们看一下使用 ReentrantLock 实现非公平锁的示例:
在上面的公平锁和非公平锁的两个示例中,lock() 方法在公平锁状态时将确保线程获取锁的顺序,而在非公平锁状态时则不会确保顺序。我们可以通过多次运行两个示例,观察它们在获取锁上的行为差异。在公平锁的情况下,线程将严格按照请求锁的顺序来获取锁;而在非公平锁的情况下,新到的线程有可能“插队”获取锁。
总的来说,公平锁保证了锁的获取按照请求的顺序进行,而非公平锁可能会导致某些线程获取锁的概率更高。在选择使用时,需要根据实际的应用场景和性能需求来决定使用哪种类型的锁。
Java公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁。在 Java 中,ReentrantLock 是一种可选的公平锁,可以通过传递 true 到其构造函数来启用它。公平锁的优点在于等待锁的线程不会饿死,因为每个线程最终都会获取锁。这避免了所谓的“线程饥饿”问题。但是,在实现公平策略时通常会降低吞吐量,因为需要维护一个有序队列来确保锁分配的顺序,这会导致线程切换和响应时间增加。
在 Java 中,ReentrantLock 类实现了公平锁和非公平锁,下面我们看一下公平锁的使用示例:
import java.util.concurrent.locks.ReentrantLock; public class FairLockExample { private ReentrantLock lock = new ReentrantLock(true); // 公平锁 public void fairLockMethod() { lock.lock(); try { // 访问或修改共享资源 System.out.println(Thread.currentThread().getName() + " has acquired the lock in a fair manner"); // 执行任务 } finally { lock.unlock(); } } public static void main(String[] args) { FairLockExample example = new FairLockExample(); Runnable task = () -> example.fairLockMethod(); Thread t1 = new Thread(task, "Thread-1"); Thread t2 = new Thread(task, "Thread-2"); t1.start(); t2.start(); } }在上述代码中,我们使用 ReentrantLock 的构造函数设置锁为公平锁。这意味着线程将会按照请求锁的顺序来获得锁。
Java非公平锁
非公平锁是指在释放锁时,所有等待锁的线程将竞争锁,但是新到的线程可以加入锁的竞争,因此可能会出现先到的线程还未获取锁,而后到的线程已经运行完毕的情况,类似于插队。非公平锁的优点是在多数情况下它的性能比公平锁的要好,因为线程可以直接抢占锁而不需要在队列中等待。但是它的缺点是在高负载下,线程可能会遭遇饥饿,即等待很长时间都无法获取锁。
在 Java 中,ReentrantLock 默认情况下使用的是非公平锁。通过传递 false 或不传递任何参数给 ReentrantLock 的构造函数,可以创建一个非公平的 ReentrantLock。
synchronized 也是一种非公平锁,由于它的线程调度机制与 ReentrantLock 的不同,不是通过 AQS 实现的,所以没有办法使其变成公平锁。
下面我们看一下使用 ReentrantLock 实现非公平锁的示例:
import java.util.concurrent.locks.ReentrantLock; public class NonFairLockExample { private ReentrantLock lock = new ReentrantLock(); // 非公平锁,默认 public void nonFairLockMethod() { lock.lock(); try { // 访问或修改共享资源 System.out.println(Thread.currentThread().getName() + " has acquired the lock in a non-fair manner"); // 执行任务 } finally { lock.unlock(); } } public static void main(String[] args) { NonFairLockExample example = new NonFairLockExample(); Runnable task = () -> example.nonFairLockMethod(); Thread t1 = new Thread(task, "Thread-1"); Thread t2 = new Thread(task, "Thread-2"); t1.start(); t2.start(); } }在上述代码中,我们没有在 ReentrantLock 的构造函数中指定公平性,因此,它默认设置锁为非公平锁。这意味着当锁可用时,任何线程都可以获取锁,而不管它们等待时间的长短。
在上面的公平锁和非公平锁的两个示例中,lock() 方法在公平锁状态时将确保线程获取锁的顺序,而在非公平锁状态时则不会确保顺序。我们可以通过多次运行两个示例,观察它们在获取锁上的行为差异。在公平锁的情况下,线程将严格按照请求锁的顺序来获取锁;而在非公平锁的情况下,新到的线程有可能“插队”获取锁。
总的来说,公平锁保证了锁的获取按照请求的顺序进行,而非公平锁可能会导致某些线程获取锁的概率更高。在选择使用时,需要根据实际的应用场景和性能需求来决定使用哪种类型的锁。