Threading and Concurrency Issues Creating Threads In Java














- Slides: 14

Threading and Concurrency Issues Creating Threads ● In Java ● Subclassing Thread ● Implementing Runnable ● Synchronization ● Immutable ● Synchronized Methods ● Synchronized Blocks ● Performance Issues ● Thread Pooling ● Transactions and Threading ● Thread safety issues ● Thread-safe ● Immutable ● Thread-hostile ●

Creating Threads ● In Java, there are two options: ● Subclass the Thread class ● Implement Runnable Subclassing is usually the poorest choice because you force threaded class into an inheritance hierarchy. ● ● The most flexible solution is to implement Runnable.

Subclassing Thread public class Test extends Thread { // The start method is defined in the // Thread class. When called, it creates // a new thread and the new thread invokes // “run” on the current object public void run() { // This method runs in its // own thread } } Thread 1 start() Thread Object run() Thread 2

Implementing Runnable public class Test implements Runnable { private Thread the. Thread; public void start() { if (the. Thread == null) { the. Thread = new Thread(this); the. Thread. start(); } } public void run() { // This method runs in its // own thread } Thread(Runnable) start() Thread Object run() create start() Runnable Object Thread 2 run() Thread 1

Terminating Threads When Java was first introduced, the multi-threading library offered a method called stop(). ● This method was invoked to stop a thread. ● However, it turned out that use of the stop method could lead to deadlock conditions ● When the stop method was invoked the thread would stop immediately and it wasn't given the opportunity to release any resources that it held. ● Uncontrolled stopping of a thread would give rise to unsafe conditions. ● The correct way to terminate a thread is for the thread to return from the “run” method ● This is usually accomplished through setting a boolean variable which is periodically checked within the run method. ●

Terminating Threads public class Test implements Runnable { private Thread the. Thread; private boolean stop. Thread = false; public void start() { if (the. Thread == null) { the. Thread = new Thread(this); the. Thread. start(); } } public void set. Stop. Thread(boolean a. Value) { stop. Thread = a. Value; } public void run() { while(!stop. Thread) { //. . . } }

Synchronization As you learned in CPSC 457, multi-threaded applications are subject to concurrency problems ● When two threads attempt to update the same data at the same time, the execution of the threads can become interleaved which gives rise to race conditions ● Race conditions can cause erroneous results in computation ● ● To prevent corruption, the programmer must identify shared variables. ● Access to these variables must be mutually exclusive in time between threads ● ie. only one thread may update a shared variable at any given point in time Java provides a “synchronized” keyword which is used to guarantee mututal exclusion in time. ●

Object monitors In order to accomplish mutual exclusion, each object in java is provided with a monitor (or lock). ● The lock cannot be directly accessed, it can only be access through the use of the synchronized keyword. ● If a method or block is synchronized, then a thread may not enter the method or block until it has obtained the monitor. ● If a thread cannot obtain the lock for the given method or block, it becomes blocked by the scheduler until such time that the monitor becomes available ● ● The testing and setting of the monitor is guaranteed to be atomic.

Synchronized methods The easiest way of obtaining mutual exclusion in Java is by defining methods as synchronized. ● If a method is synchronized, the monitor of the target object is obtained. ● ● It is possible to define multiple synchronized methods within a class. ● This guarantees that only one thread may be executing any of the synchronized methods at a give point in time. public class Savings. Account { private float balance; public synchronized void withdraw(float an. Amount) { if ((an. Amount>0. 0) && (an. Amount<=balance)) balance = balance - an. Amount; } public synchronized void deposit(float an. Amount) { if (an. Amount>0. 0) balance = balance + an. Amount; }

Synchronization and performance ● Unfortunately, synchronization comes with a price. ● Performance suffers because threads which could be executing are blocked if another thread is currently executing a synchronized method on the target object. The solution to this problem is to make the area which is synchronized as small as possible ● Methods do not offer fine enough granularity. ● It is possible to synchronize blocks instead of whole methods. ●

Synchronized blocks public class Savings. Account { private float balance; public void withdraw(float an. Amount) { if (an. Amount<0. 0) throw new Illegal. Argument. Exception("Withdraw amount negative"); synchronized(this) { if (an. Amount<=balance) balance = balance - an. Amount; } } public void deposit(float an. Amount) { if (an. Amount<0. 0) throw new Illegal. Argument. Exception("Deposit amount negative"); synchronized(this) { balance = balance + an. Amount; } }

Synchronized blocks and methods: ● This method: public synchronized void withdraw(float an. Amount) { if ((an. Amount>0. 0) && (an. Amount<=balance)) balance = balance - an. Amount; } is equivalent to this method: public void withdraw(float an. Amount) { synchronized(this) { if ((an. Amount>0. 0) && (an. Amount<=balance)) balance = balance – an. Amount; } }

Other performance considerations While the creation of threads is a relatively inexpensive operation (particularly when compared to creating processes), there is still an overhead should an application be creating and terminating a large number of threads. ● ● One general solution is to create a thread pool: ● References to the Runnable objects are kept in a collection and pulled out when needed ● Runnable objects obtain their work (in the form of transactions) from a queue. If no work is available, the thread blocks.

General Threading Considerations Avoid over-synchronization ● Don't write code which depends on how the scheduler is going to behave ● Always invoke wait within a loop ● Never use thread groups ● Never use stop() or resume() ● Document Thread Safety ● Immutable: Data cannot change. No external synchronization is necessary ● Thread-safe: instance of the class are mutable, but the methods contain sufficient internal synchronization so that the methods can be used without any external synchronization ● Conditionally thread-safe: Some methods must be invoked in a specific order or external synchronization must be provided. ● Thread-compatible: Instances can be used concurrently provided that each method is protected by external synchronization ● Thread-hostile: cannot be used concurrently, even if external synchronization is provided. ●