使用Semaphore控制对资源的多个副本的并发访问

Semaphores 怎样工作?

您可以将信号量看做可以递增或递减的计数器。用一个数字即5来初始化信号量。现在这个信号量可以连续最多递减五次,直到计数器达到0.一旦计数器为零,你可以将它增加到最多五次使其成为5。计数器值信号量必须始终在内部限制0> = n> = 5(在我们的例子中)。

显然,信号量不仅仅是计数器。当计数器值为零时,它们能够使线程等待,即它们充当具有计数器功能的锁。

谈到多线程,当一个线程想要访问一个共享资源(由信号量保护)时,首先,它必须获取信号量。如果信号量的内部计数器大于0,则信号量递减计数器并允许访问共享资源。否则,如果信号量的计数器为0,则信号量将线程置于休眠状态,直到计数器大于0。计数器中的值0表示所有共享资源都被其他线程使用,因此,如果线程想要使用共享资源中的一个,就必须等待到其中一个空闲。

 注意:当线程完成共享资源的使用时,它必须释放信号量,以便其他线程可以访问共享资源。 该操作增加信号量的内部计数器。

怎样使用semaphore?

为了演示这个概念,我们将使用信号量来控制可以同时打印多个文档的3台打印机。

PrintingJob.java

此类表示可以提交到打印机队列的独立打印作业。 从队列中,它可以被任何打印机拾取并执行打印作业。 这个类实现了Runnable接口,这样打印机就可以在轮到它时执行它。

class PrintingJob implements Runnable {
    private PrinterQueue printerQueue;
 
    public PrintingJob(PrinterQueue printerQueue) {
        this.printerQueue = printerQueue;
    }
 
    @Override
    public void run() {
        System.out.printf("%s: Going to print a document\n", Thread
                .currentThread().getName());
        printerQueue.printJob(new Object());
    }
}

PrinterQueue.java

此类表示打印机队列/打印机。 该类有3个主要属性,用于控制从3台打印机中选择一台空闲打印机的逻辑,并将其锁定以打印作业。 打印文档后,将释放打印机,使其再次空闲并可用于从打印队列中打印新作业。

此类有两个方法getPrinter()和releasePrinter(),它们负责获取空闲打印机和将其放回空闲打印机池中。

另一个方法printJob()实际上做核心工作,即获取打印机,执行打印作业,然后释放打印机。

它使用以下两个变量来完成工作:

semaphore:这个变量记录跟踪在任何时间正在使用的打印机数量。
printerLock:用于在从三台可用打印机中检查/获取免费打印机之前锁定打印机池。

class PrinterQueue
{
    //This Semaphore will keep track of no. of printers used at any point of time.
    private final Semaphore semaphore;
     
    //While checking/acquiring a free printer out of three available printers, we will use this lock.
    private final Lock printerLock;
     
    //This array represents the pool of free printers.
    private boolean freePrinters[];
 
    public PrinterQueue()
    {
        semaphore = new Semaphore(3);
        freePrinters = new boolean[3];
        for (int i = 0; i < 3; i++) {
            freePrinters[i] = true;
        }
        printerLock = new ReentrantLock();
    }
 
    public void printJob(Object document)
    {
        try
        {
            //Decrease the semaphore counter to mark a printer busy
            semaphore.acquire();
             
            //Get the free printer
            int assignedPrinter = getPrinter();
             
            //Print the job
            Long duration = (long) (Math.random() * 10000);
            System.out.println(Thread.currentThread().getName()
                    + ": Printer " + assignedPrinter
                    + " : Printing a Job during " + (duration / 1000)
                    + " seconds :: Time - " + new Date());
            Thread.sleep(duration);
             
            //Printing is done; Free the printer to be used by other threads.
            releasePrinter(assignedPrinter);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            System.out.printf("%s: The document has been printed\n", Thread
                    .currentThread().getName());
             
            //Increase the semaphore counter back
            semaphore.release();
        }
    }
 
    //Acquire a free printer for printing a job
    private int getPrinter()
    {
        int foundPrinter = -1;
        try {
            //Get a lock here so that only one thread can go beyond this at a time
            printerLock.lock();
             
            //Check which printer is free
            for (int i = 0; i < freePrinters.length; i++)
            {
                //If free printer found then mark it busy
                if (freePrinters[i])
                {
                    foundPrinter = i;
                    freePrinters[i] = false;
                    break;
                }
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        } finally
        {
            //Allow other threads to check for free priniter
            printerLock.unlock();
        }
        return foundPrinter;
    }
     
    //Release the printer
    private void releasePrinter(int i) {
        printerLock.lock();
        //Mark the printer free
        freePrinters[i] = true;
        printerLock.unlock();
    }
}

 我们来测试打印机程序:

public class SemaphoreExample
{
    public static void main(String[] args)
    {
        PrinterQueue printerQueue = new PrinterQueue();
        Thread thread[] = new Thread[10];
        for (int i = 0; i < 10; i++)
        {
            thread[i] = new Thread(new PrintingJob(printerQueue), "Thread " + i);
        }
        for (int i = 0; i < 10; i++)
        {
            thread[i].start();
        }
    }
}
 
Output:
 
Thread 1: Going to print a document
Thread 4: Going to print a document
Thread 9: Going to print a document
Thread 8: Going to print a document
Thread 6: Going to print a document
Thread 7: Going to print a document
Thread 2: Going to print a document
Thread 5: Going to print a document
Thread 3: Going to print a document
Thread 0: Going to print a document
Thread 9: PrintQueue 2 : Printing a Job during 2 seconds :: Time - Tue Jan 13 16:28:58 IST 2015
Thread 4: PrintQueue 1 : Printing a Job during 7 seconds :: Time - Tue Jan 13 16:28:58 IST 2015
Thread 1: PrintQueue 0 : Printing a Job during 1 seconds :: Time - Tue Jan 13 16:28:58 IST 2015
Thread 1: The document has been printed
Thread 8: PrintQueue 0 : Printing a Job during 1 seconds :: Time - Tue Jan 13 16:29:00 IST 2015
Thread 9: The document has been printed
Thread 6: PrintQueue 2 : Printing a Job during 0 seconds :: Time - Tue Jan 13 16:29:01 IST 2015
Thread 6: The document has been printed
Thread 7: PrintQueue 2 : Printing a Job during 4 seconds :: Time - Tue Jan 13 16:29:01 IST 2015
Thread 8: The document has been printed
Thread 2: PrintQueue 0 : Printing a Job during 5 seconds :: Time - Tue Jan 13 16:29:02 IST 2015
Thread 7: The document has been printed
Thread 5: PrintQueue 2 : Printing a Job during 8 seconds :: Time - Tue Jan 13 16:29:05 IST 2015
Thread 4: The document has been printed
Thread 3: PrintQueue 1 : Printing a Job during 4 seconds :: Time - Tue Jan 13 16:29:06 IST 2015
Thread 2: The document has been printed
Thread 0: PrintQueue 0 : Printing a Job during 4 seconds :: Time - Tue Jan 13 16:29:08 IST 2015
Thread 3: The document has been printed
Thread 0: The document has been printed
Thread 5: The document has been printed

在上面的示例中,使用3作为构造函数的参数创建信号量对象。 调用acquire()方法的前三个线程将获得对打印机的访问权限,而其余线程将被阻塞。 当一个线程完成关键部分并释放信号量时,另一个线程将获取它。

在printJob()方法中,线程获取分配用于打印此作业的打印机的索引。

这就是这个简单但重要的概念。 如果有的话,请把你的问题和评论留下。

原文链接: howtodoinjava 翻译: 随波不逐流- wavemelody
译文链接: https://www.cnblogs.com/mymelody/p/9556037.html

posted @ 2018-08-29 18:01  wavemelody  阅读(401)  评论(0编辑  收藏  举报