控制对一个资源的多个复制品的并发访问

信号灯也能被用于保护一个资源的多个复制品或能被多于一个线程同时执行的临界区。

任务

实现一个打印队列可以同时在3台打印机上打印。

实现

本节的示例代码在 com.elanzone.books.noteeg.chpt3.sect03 package中

在上一节代码的基础上做以下修改:

  • PrintQueue

    • 声明 boolean 类型数组属性 freePrinters
    • 声明一个 Lock 对象属性 lockPrinters,用来控制对数组 freePrinters 的访问
    • 修改构造函数初始化各属性:数组有3个元素,初始值为true; 信号灯的初始值为 3
        private Semaphore semaphore;
    
        public static int FreePrinterCount = 3;
        private boolean freePrinters[];
        private Lock lockPrinters;
    
        public PrintQueue() {
            semaphore = new Semaphore(FreePrinterCount);
            freePrinters = new boolean[FreePrinterCount];
            for (int i = 0; i < FreePrinterCount; i++) {
                freePrinters[i] = true;
            }
            lockPrinters = new ReentrantLock();
        }
    
    • printJob() 方法
      1. 获得信号
      2. 使用 getPrinter 方法获得空闲可用的打印机序号
      3. 睡上一段时间模拟打印操作
      4. 最终释放信号并 freePrinters 数组对应索引的值为 设置为 true 表示该打印机可用
        public void printJob(Object document) {
            try {
                semaphore.acquire();
    
                int assignedPrinter = getPrinter();
    
                long duration = (long) (Math.random() * 10);
                System.out.printf("%s: PrintQueue: Printing a Job in Printer %d during %d seconds\n",
                        Thread.currentThread().getName(), assignedPrinter, duration);
                TimeUnit.SECONDS.sleep(duration);
    
                freePrinters[assignedPrinter] = true;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semaphore.release();
            }
        }
    
    • getPrinter() 方法
      1. 获得 lockPrinters 对象的访问权
      2. 查找第一个标记为可用的打印机
      3. 最终释放对 lockPrinters 的控制
        private int getPrinter() {
            int ret = -1;
    
            try {
                lockPrinters.lock();
    
                for (int i = 0; i < freePrinters.length; i++) {
                    if (freePrinters[i]) {
                        ret = i;
                        freePrinters[i] = false;
                        break;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lockPrinters.unlock();
            }
    
            return ret;
        }
    
  • 其他类无变动

工作原理

本例的关键在 PrintQueue 类中。Semaphore对象的构造函数的参数值为3。 最先调用 acquire() 方法的 3 个线程将获得对本例的临界区的访问,其他的将被阻塞。 当一个线程结束临界区访问并是否信号,另一个线程将获得信号。

在临界区内,线程获取被分配打印任务的打印机的索引。这部分只是为了更逼真,没有用到信号灯。

了解更多

acquire()、acquireUninterruptibly()、tryAcquire()和release()方法有一个整型参数的其他版本。 此参数表示允许线程能获取或释放的权限数,也就是说,线程想删除或添加到此信号灯的内部计数器的单位数。