• 沒有找到結果。

Controlling concurrent access to a resource

在文檔中 Java 7 Concurrency Cookbook (頁 97-102)

In this recipe, you will learn how to use the semaphore mechanism provided by the Java language. A semaphore is a counter that protects the access to one or more shared resources.

The concept of a semaphore was introduced by Edsger Dijkstra in 1965 and was used for the first time in the THEOS operating system.

When a thread wants to access one of these shared resources, first, it must acquire the semaphore. If the internal counter of the semaphore is greater than 0, the semaphore decrements the counter and allows access to the shared resource. A counter bigger than 0 means there are free resources that can be used, so the thread can access and use one of them.

Otherwise, if the counter of the semaphore is 0, the semaphore puts the thread to sleep until the counter is greater than 0. A value of 0 in the counter means all the shared resources are used by other threads, so the thread that wants to use one of them must wait until one is free.

When the thread has finished the use of the shared resource, it must release the semaphore so that the other thread can access the shared resource. That operation increases the internal counter of the semaphore.

In this recipe, you will learn how to use the Semaphore class to implement special kinds of semaphores called binary semaphores. These kinds of semaphores protect the access to a unique shared resource, so the internal counter of the semaphore can only take the values 1 or 0. To show how to use it, you are going to implement a print queue that can be used by concurrent tasks to print their jobs. This print queue will be protected by a binary semaphore, so only one thread can print at a time.

Getting ready

The example of this recipe has been implemented using the Eclipse IDE. If you use Eclipse or other IDE such as NetBeans, open it and create a new Java project.

How to do it...

Follow these steps to implement the example:

1. Create a class named PrintQueue that will implement the print queue.

public class PrintQueue {

2. Declare a Semaphore object. Call it semaphore. private final Semaphore semaphore;

3. Implement the constructor of the class. It initializes the semaphore object that will protect the access from the print queue.

public PrintQueue(){

semaphore=new Semaphore(1);

}

4. Implement the printJob() method that will simulate the printing of a document. It receives Object called document as a parameter.

public void printJob (Object document){

5. Inside the method, first of all, you must acquire the semaphore calling the acquire() method. This method can throw an InterruptedException exception, so you must include some code to process it.

try {

semaphore.acquire();

6. Then, implement the lines that simulate the printing of a document waiting for a random period of time.

long duration=(long)(Math.random()*10);

System.out.printf("%s: PrintQueue: Printing a Job during %d seconds\n",Thread.currentThread().getName(),duration);

Thread.sleep(duration);

7. Finally, free the semaphore by calling the release() method of the semaphore.

} catch (InterruptedException e) { e.printStackTrace();

} finally {

semaphore.release();

}

8. Create a class called Job and specify that it implements the Runnable interface.

This class implements a job that sends a document to the printer.

public class Job implements Runnable { 9. Declare a PrintQueue object. Call it printQueue.

private PrintQueue printQueue;

10. Implement the constructor of the class. It initializes the PrintQueue object declared in the class.

public Job(PrintQueue printQueue){

this.printQueue=printQueue;

}

11. Implement the run() method.

@Override

public void run() {

12. First, the method writes a message to the console that shows that the job has started its execution.

System.out.printf("%s: Going to print a job\n",Thread.

currentThread().getName());

13. Then, it calls the printJob() method of the PrintQueue object.

printQueue.printJob(new Object());

14. Finally, the method writes a message to the console that shows that it has finished its execution.

System.out.printf("%s: The document has been printed\

n",Thread.currentThread().getName());

}

15. Implement the main class of the example by creating a class named Main and implement the main() method.

public class Main {

public static void main (String args[]){

16. Create a PrintQueue object named printQueue. PrintQueue printQueue=new PrintQueue();

17. Create 10 threads. Each one of those threads will execute a Job object that will send a document to the print queue.

Thread thread[]=new Thread[10];

for (int i=0; i<10; i++){

thread[i]=new Thread(new Job(printQueue),"Thread"+i);

}

18. Finally, start the 10 threads.

for (int i=0; i<10; i++){

thread[i].start();

}

How it works...

The key to this example is in the printJob() method of the PrintQueue class. This method shows the three steps you must follow when you use a semaphore to implement a critical section, and protect the access to a shared resource:

1. First, you acquire the semaphore, with the method.

Another important point in this example is the constructor of the PrintQueue class and the initialization of the Semaphore object. You pass the value 1 as the parameter of this constructor, so you are creating a binary semaphore. The initial value of the internal counter is 1, so you will protect the access to one shared resource, in this case, the print queue.

When you start the 10 threads, the first one acquires the semaphore and gets the access to the critical section. The rest are blocked by the semaphore until the thread that has acquired it, releases it. When this occurs, the semaphore selects one of the waiting threads and gives it the access to the critical section. All the jobs print their documents, but one by one.

There's more...

The Semaphore class has two additional versions of the acquire() method:

f acquireUninterruptibly(): The acquire() method; when the internal counter of the semaphore is 0, blocks the thread until the semaphore is released. During this blocked time, the thread may be interrupted and then this method throws an InterruptedException exception. This version of the acquire operation ignores the interruption of the thread and doesn't throw any exceptions.

f tryAcquire(): This method tries to acquire the semaphore. If it can, the method returns the true value. But if it can't, the method returns the false value instead of being blocked and waits for the release of the semaphore. It's your responsibility to take the correct action based on the return value.

Fairness in semaphores

The concept of fairness is used by the Java language in all classes that can have various threads blocked waiting for the release of a synchronization resource (for example, a semaphore). The default mode is called the non-fair mode. In this mode, when the synchronization resource is released, one of the waiting threads is selected to get this resource, but it's selected without any criteria. The fair mode changes this behavior and forces to select the thread that has been waiting for more time.

As occurs with other classes, the Semaphore class admits a second parameter in its constructor. This parameter must take a Boolean value. If you give it the false value, you are creating a semaphore that will work in non-fair mode. You will get the same behavior if you don't use this parameter. If you give it the true value, you are creating a semaphore that will work in fair mode.

See also

f The Monitoring a Lock interface recipe in Chapter 8, Testing Concurrent Applications

f The Modifying Lock fairness recipe in Chapter 2, Basic Thread Synchronization

Controlling concurrent access to multiple

在文檔中 Java 7 Concurrency Cookbook (頁 97-102)