• Tidak ada hasil yang ditemukan

Synchronising Threads

Multithreading and Multiplexing

3.5 Synchronising Threads

Locking is achieved by placing the keyword synchronized in front of the method defi nition or block of code that does the updating.

Example

public synchronized void updateSum(int amount) {

sum+=amount;

}

If sum is not locked when the above method is invoked, then the lock on sum is obtained, preventing any other thread from executing updateSum . All other threads attempting to invoke this method must wait. Once the method has fi nished execu- tion, the lock is released and made available to other threads. If an object has more than one synchronized method associated with it, then only one may be active at any given time.

In order to improve thread effi ciency and to help avoid deadlock, the following methods are used:

wait();

notify();

notifyAll().

If a thread executing a synchronized method determines that it cannot pro- ceed, then it may put itself into a waiting state by calling method wait . This releases the thread’s lock on the shared object and allows other threads to obtain the lock.

A call to wait may lead to an InterruptedException , which must either be caught or declared to be thrown by the containing ( synchronized ) method.

When a synchronized method reaches completion, a call may be made to notify , which will ‘wake up’ a thread that is in the waiting state. Since there is no way of specifying which thread is to be woken, this is only really appropriate if there is only one waiting thread. If all threads waiting for a lock on a given object are to be woken, then we use notifyAll . However, there is still no way of determining which thread gets control of the object. The JVM will make this decision.

Methods wait , notify and notifyAll may only be called when the current thread has a lock on the object (i.e., from within a synchronized method or from within a method that has been called by a synchronized method). If any of these methods is called from elsewhere, an IllegalMonitorStateException is thrown.

Example

This example is the classical producer-consumer problem, in which a producer is generating instances of some resource (cars on a production line, chocolate bars on a conveyor belt, wooden chairs in a carpenter’s workshop or whatever) and a con- sumer is removing instances of the resource. Though this is largely a theoretical example, rather than a practical example of a service that might be provided by a server program, it could be modifi ed to involve a server providing some network resource, such as a printing facility (though the server would probably be working with a fi xed ‘pool’ of printers, rather than creating new ones).

The resource will be modelled by a Resource class, while the producer and consumer will be modelled by a Producer class and a ConsumerClient class respec- tively. The Producer class will be a thread class, extending class Thread . The server program, ResourceServer , will create a Resource object and then a Producer thread, passing the constructor for this thread a reference to the Resource object.

The server will then start the thread running and begin accepting connections from ConsumerClient s. As each client makes connection, the server will create an instance of ClientThread (another Thread class), which will be responsible for handling all subsequent dialogue with the client. The code for ResourceServer is shown below.

import java.io.*;

import java.net.*;

public class ResourceServer {

private static ServerSocket serverSocket;

private static fi nal int PORT = 1234;

public static void main(String[] args)

throws IOException {

try {

serverSocket = new ServerSocket(PORT);

}

catch (IOException e) {

System.out.println("\nUnable to set up port!");

System.exit(1);

}

//Create a Resource object with //a starting resource level of 1…

Resource item = new Resource(1);

//Create a Producer thread, passing a reference //to the Resource object as an argument to the //thread constructor…

Producer producer = new Producer(item);

//Start the Producer thread running…

producer.start();

do {

//Wait for a client to make connection…

Socket client = serverSocket.accept();

System.out.println("\nNew client accepted.\n");

//Create a ClientThread thread to handle all //subsequent dialogue with the client, passing //references to both the client's socket and //the Resource object…

ClientThread handler =

new ClientThread(client,item);

//Start the ClientThread thread running…

handler.start();

}while (true); //Server will run indefi nitely.

} }

Method addOne of Resource will be called by a Producer object and will attempt to add one item to the resource level. Method takeOne of Resource will be called by a ConsumerClient object and will attempt to remove/consume one item.

Both of these methods will return the new resource level. Since each of these meth- ods will modify the resource level, they must both be declared with the keyword synchronized .

The code for the Producer class is shown below. As in previous examples, a randomising feature has been included. This causes the producer to wait 0–5 s

between successive (attempted) increments of the resource level, so that it does not produce so quickly that it is always at maximum (or, very briefl y, one below maximum).

class Producer extends Thread {

private Resource item;

public Producer(Resource resource) {

item = resource;

}

public void run() {

int pause;

int newLevel;

do { try {

//Add 1 to level and return new level…

newLevel = item.addOne();

System.out.println(

"<Producer> New level: " + newLevel);

pause = (int)(Math.random() * 5000);

//'Sleep' for 0-5 seconds…

sleep(pause);

}

catch (InterruptedException interruptEx) {

System.out.println(interruptEx);

}

}while (true);

} }

Just as a factory may not produce more than it can either sell or store, so the producer normally has some maximum resource level beyond which it must not produce. In this simple example, the resource level will not be allowed to exceed 5.

Once the resource level has reached 5, production must be suspended. This is done from method addOne by calling wait from within a loop that continuously checks whether the resource level is still at maximum. The calling of wait suspends the Producer thread and releases the lock on the shared resource level variable, allow- ing any ConsumerClient to obtain it. When the resource level is below the maxi- mum, addOne increments the level and then calls method notify to ‘wake up’ any waiting ConsumerClient thread.

At the other extreme, the consumer must not be allowed to consume when there is nothing to consume (i.e., when the resource level has reached zero). Thus, if the resource level is at zero when method takeOne is executed, wait is called from within a loop that continuously checks that the level is still at zero. The calling of wait suspends the ConsumerClient thread and releases the lock on the shared resource level variable, allowing any Producer to obtain it. When the resource level is above zero, takeOne decrements the level and then calls method notifyAll to

‘wake up’ any waiting Producer thread.

The code for class Resource is shown below. Note that ResourceServer must have access to the code for both Producer and Resource .

class Resource {

private int numResources;

private fi nal int MAX = 5;

public Resource(int startLevel) {

numResources = startLevel;

}

public int getLevel() {

return numResources;

}

public synchronized int addOne() {

try {

while (numResources >= MAX) wait();

numResources++;

//'Wake up' any waiting consumer…

notifyAll();

}

catch (InterruptedException interruptEx) {

System.out.println(interruptEx);

}

return numResources;

}

public synchronized int takeOne() {

try {

while (numResources == 0)

wait();

numResources--;

//'Wake up' waiting producer…

notify();

}

catch (InterruptedException interruptEx) {

System.out.println(interruptEx);

}

return numResources;

} }

The ClientThread objects created by ResourceServer handle all resource requests from their respective clients. In this simplifi ed example, clients will be allowed to request only one item at a time from the resource ‘pile’, which they will do simply by sending a ‘1’. When a client wishes to disconnect from the service, it will send a

‘0’. The code for ClientThread is shown below. Just as for classes Producer and Resource , this code must be accessible by ResourceServer . Note that, although ClientThread calls takeOne to ‘consume’ an item of resource on behalf of the client, the only thing that is actually sent to the client is a symbolic message of confi rma- tion that the request has been granted. Only when the material on serialisation has been covered at the end of the next chapter will it be clear how general resource

‘objects’ may actually be sent to a client.

import java.io.*;

import java.net.*;

import java.util.*;

class ClientThread extends Thread {

private Socket client;

private Resource item;

private Scanner input;

private PrintWriter output;

public ClientThread(Socket socket, Resource resource) {

client = socket;

item = resource;

try {

//Create input and output streams //on the socket…

input = new Scanner(client.getInputStream());

output = new PrintWriter(

client.getOutputStream(),true);

}

catch(IOException ioEx) {

ioEx.printStackTrace();

} }

public void run() {

String request = "";

do {

request = input.nextLine();

if (request.equals("1")) {

item.takeOne();//If none available, //wait until resource(s) //available (and thread is //at front of thread queue).

output.println("Request granted.");

}

}while (!request.equals("0"));

try {

System.out.println(

"Closing down connection…");

client.close();

}

catch(IOException ioEx) {

System.out.println(

"Unable to close connection to client!");

} } }

All that remains to be done now is to produce the code for the ConsumerClien t class. However, the required code for this class is very similar in structure to that of MultiEchoClient from Sect. 3.3 (as, indeed, it would be to most client programs), and so production of this code is one of the exercises at the end of the chapter. In the meantime, the screenshots in Figs. 3.6 and 3.7 show example output from ResourceServer and two ConsumerClient s.

Fig. 3.7 Output from two ConsumerClient s connected to ResourceServer of Fig. 3.6 Fig. 3.6 Example output from ResourceServer (with two clients connecting)

3.6 Non-blocking Servers