Java: 使用信号量(Semaphore)保护多个共享资源的访问

信号量(semaphore)机制是一种常用的同步机制,在现代OS中被广泛采用。semaphore是一个非0值,当它的值大于0时表示系统目前还有足够的资源分配给请求线程,每分配出去一个资源,值递减。当值等于0时表示当前已无资源可分配。JDK提供了Semaphore类来实现信号量。

假如我们一共有3台打印机可用,当前有N个线程都请求使用打印机,要实现对打印机这种资源的访问保护,有以下两种方式:

  1. 每当一个线程请求使用打印机时,检查当前是否有空闲打印机可用,如果有则分配给请求线程并使用打印机。分配和使用打印机的过程都要加锁(同一把锁)。
  2. 创建一个初值为3的semaphore, 允许3个线程同时使用打印机。

显然,方式一在某个时间点只能有一个线程在使用打印机,那么另外2台打印机资源就被浪费掉了。有人可能会想,如果只对检查并分配打印机的代码进行加锁,而在使用打印机的过程中不加锁,这样当第一个线程使用打印机时,第二个线程依然能够请求到打印机,不就能允许3个线程都用上打印机了吗?

这样做是非常危险的,我们称之为资源逃逸。如果不对使用打印机的过程加锁,那么如果请求线程又将这台逃逸出的打印机分配给了其它线程,那么线程同步就失去了意义。

对于方式二,当线程A使用打印机时,线程B依然可以请求并使用打印机,线程C也可以。当线程A使用完后必须释放semaphore, 这样其它线程又可以使用这台打印机了。

首先定义打印机对象:

class Printer {
    private String name;
    private int index;

    public Printer(String name, int index) {
        this.name = name;
        this.index = index;
    }

    public void print() {
        System.out.println("printer " + name + " is working for " + Thread.currentThread().getName());

        try {
            Thread.currentThread().sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("printer " + name + " for " + Thread.currentThread().getName() + " is done");
    }

    public int getIndex() {
        return this.index;
    }
}

然后,我们定义一个打印机池(Pool)。利用这个pool实现打印机资源的安全分配:

class PrinterPool {
    /**
     * store all available devices
     */
    private Printer[] printers;
    private boolean[] freePrinters;

    private Lock lock = new ReentrantLock();

    /**
     * create a semaphore for 3 resources
     */
    private Semaphore semaphore = new Semaphore(3);

    /**
     * 3 printers available in this pool.
     */
    public PrinterPool() {
        printers = new Printer[] {
                new Printer("A", 0),
                new Printer("B", 1),
                new Printer("C", 2)
        };

        freePrinters = new boolean[] {
                true,
                true,
                true
        };
    }

    /**
     * use printer
     */
    public Printer printData() {
        Printer printer = null;

        try {
            semaphore.acquire();

            while (true) {
                printer = getFreePrinter();

                if (printer != null) {
                    break;
                }
            }

            // use printer
            printer.print();

            // reclaim printer
            reclaimPrinter(printer);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();
        }

        return printer;
    }

    private Printer getFreePrinter() {
        Printer printer = null;

        lock.lock();
        for (int ix = 0 ; ix < freePrinters.length ; ++ix) {
            if (true == freePrinters[ix]) {
                freePrinters[ix] = false;
                printer = printers[ix];
                break;
            }
        }
        lock.unlock();

        return printer;
    }

    private void reclaimPrinter(Printer p) {
        lock.lock();
        this.freePrinters[p.getIndex()] = true;
        lock.unlock();
    }

}

最后编写测试代码,启动10个线程请求使用打印机:

// create printer pool
        PrinterPool pool = new PrinterPool();

        Runnable job = () -> {
            pool.printData();
        };

        // create 10 threads
        Thread[] threads = new Thread[10];
        for (int ix = 0 ; ix < 10 ; ++ix) {
            threads[ix] = new Thread(job);
        }

        // start all threads
        for (int ix = 0 ; ix < 10 ; ++ix) {
            threads[ix].start();
        }

输出结果:
技术分享

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。