歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

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();
        }

輸出結果:

Copyright © Linux教程網 All Rights Reserved