修改锁的公平性

ReentrantLock 和 ReentrantReadWriteLock 类的构造函数都允许传递一个名为 fair 的 boolean 类型的参数,来控制类的行为。

当有一些线程在等待一个锁(ReentrantLock 或 ReentrantReadWriteLock)时,要选择其中一个获得临界区的权限。

  • 选择时没有判断标准:被称为 非公平 模式,fair 的值为 false,是默认行为
  • 选择等待最长时间的 : 被称为 公平 模式,fair 的值为 true

上述行为只对 lock() 和 unlock() 方法有意义。因为 tryLock() 方法不会让线程睡眠,所以 fair 属性不影响它的功能。

任务

修改第二章第5节的例子,用 公平模式 来实现

实现

本节的示例代码在 com.elanzone.books.noteeg.chpt2.sect07 package中

在 第二章第5节的例子 的基础上:

  • PrintQueue

    • 修改 Lock 对象的构造代码:
    @@ -6,7 +6,7 @@ import java.util.concurrent.locks.ReentrantLock;
    
     public class PrintQueue {
    
    -    private final Lock queueLock = new ReentrantLock();
    +    private final Lock queueLock = new ReentrantLock(true);
    
    • 修改 printJob 方法,分成2次打印,2次中间释放锁
    @@ -20,6 +20,17 @@ public class PrintQueue {
             } finally {
                 queueLock.unlock();
             }
    +
    +        queueLock.lock();
    +        try {
    +            Long duration = (long) (Math.random() * 10000);
    +            System.out.println(Thread.currentThread().getName() + ": PrintQueue: Printing a Job during " + (duration / 1000) + " seconds");
    +            Thread.sleep(duration);
    +        } catch (InterruptedException e) {
    +            e.printStackTrace();
    +        } finally {
    +            queueLock.unlock();
    +        }
         }
    
  • Main

    • 启动线程时,每个线程启动间隙间隔 100 毫秒,使等待时间更明显
    --- a/src/main/java/com/elanzone/books/noteeg/chpt2/sect07/Main.java
    +++ b/src/main/java/com/elanzone/books/noteeg/chpt2/sect07/Main.java
    @@ -12,6 +12,11 @@ public class Main {
    
             for (int i=0; i<10; i++) {
                 thread[i].start();
    +            try {
    +                Thread.sleep(100);
    +            } catch (InterruptedException e) {
    +                e.printStackTrace();
    +            }
             }
         }
    

工作原理

所有线程创建的间隔时间是 0.1 秒。第一个请求对锁的控制的线程是线程0,然后是线程1,依此类推。

  1. 当线程0运行由锁保护的第一个代码块时,有9个线程在等着执行那个代码块。
  2. 当线程0释放锁时,它立即再次请求此锁,这时我们有10个线程在尝试获得此锁。
  3. 因为采用的是公平模式,Lock接口将选择线程1,因为它等待时间最长。
  4. 然后选择线程2,然后是线程3,依此类推。
  5. 直到所有线程都执行完第一个代码块,它们都不会执行第2个代码块。
  6. 一当所有线程都执行了第一个代码块,再次轮到线程0,然后轮到线程1,依此类推

您可以尝试将传递给锁的构造函数的参数改为 false 切换到 非公平 模式看看区别。

在 非公平 模式下,行为是不保证的,因为锁可以选择任一线程赋予它对受保护代码的权限。 在本例中,JVM不保证线程的执行顺序。

了解更多

读/写锁的构造函数也有 fair 参数,作用相同。