Threads
Threads
Introduction
Interruptions
Synchronization
Atomic
volatile
Wait/Notify
Locks and Monitors
Executors and Thread Pools
Immutable Objects
Thread-Safe Collections
INTRODUCTION
Threads
• A thread is a separate computation process that might execute in
parallel with other processes.
• You use threads all the time:
• Having a video playing while you are typing an email
• Reading a website while you have music playing
• Java programs can have multiple threads.
Java Memory: Heap and Stack
• Stack Memory
• Local variables and object references are stored in stack memory
• Method calls (activation records) are stored on the stack
• Variables on the stack exist as long as the method that created them is
running
• Heap Memory
• The actual objects are stored on the heap
• The reference points to the location on the heap
synchronized(object) { synchronized(object) {
while(conditionThatMustBeTrue) { // work is ready to be done
try { conditionThatMustBeTrue = true;
object.wait(); object.notifyAll();
} catch(InterruptedException ex) {} }
}
object.doTheWork();
}
Waiting
• You have to own the lock in order to invoke wait.
• Putting the wait() call inside a synchronized block is one way to ensure this.
• Invoking wait() releases the lock
• When the thread is notified, it will have to continue waiting until the lock is
released
• Always wait inside of a loop that checks the condition.
• There is no guarantee that the condition has changed as a result of the
notification.
• So you want to recheck it before moving on.
Practice
• Review the deposit/bonus methods of the bank account classes.
• Multiple threads will make random deposits.
• There are “bonus” threads that wait to add a $1 “savings bonus” when the
balance passes a certain amount.
• Classes: WNBankAccount, WNBankThread, WNBankTester
Producer/Consumer Relationship
• An application that shares data between two threads: a producer and a
consumer
• A producer thread creates some data/item/object that a consumer thread
uses.
• Example: read an object from a database and pass it off for processing
• Example: read input from a user and hand it off to be analyzed
• Coordination is essential!
• The consumer cannot use item unless it’s been produced.
• It has to wait.
• The producer cannot create an item if the consumer is still busy or hasn’t
retrieved the old item.
• It has to wait.
Practice
• Review the producer/consumer example.
• The number box holds a single number and can either be filled or empty.
• Classes: ProducerThread, ConsumerThread, ProducerConsumerTest,
NumberBox
Using wait/notify
• Most people don’t directly use wait/notify anymore.
• As Joshua Block (Effective Java) states:
• “using wait and notify directly is like programming in “concurrency assembly
language,” as compared to the higher-level language provided by
java.util.concurrent. There is seldom, if ever, a reason to use wait and notify
in new code.”
• Instead, use high-level constructors, including BlockingQueue.
Blocking Queue
• A special queue that supports the producer/consumer pattern.
• Threads must wait for a queue to be non-empty to retrieve.
• Threads must wait for space to add.
• Different methods based on the action you want to take when an
add/remove cannot be handled immediately.
Practice
• Review the producer/consumer example using a BlockingQueue.
• ProducerThreadBQ, ConsumerThreadBQ, ProducerConsumerTestBQ
LOCKS
Locks
• A lock ensures that only one thread can access a block of code at a
time.
• Use the Lock interface and ReentrantLock class
myLock.lock();
try {
// code to be protected
} finally {
myLock.unlock(); // must be in finally!
}
Locks
• Locks can provide similar functionality to synchronized, and they have
internal wait/notify mechanisms built in.
• They also have additional benefits:
• tryLock method (backs out if a lock is not available)
• lockInterruptibly (backs out if interrupted while waiting)
• Better performance under high thread contention
• Can include multiple condition variables
• Can split locking across methods (e.g., get a lock in one method and release it
in another)
Practice
• Review the example of the BankAccount with fee and bonus, using
locks instead of synchronized
• Classes: LBankAccount, LBankThread, LBankTester
Conditions
• You can add multiple conditions to a lock.
• This can replicate wait/notify, but is more flexible since you can have multiple
conditions.
myCondition = myLock.newCondition();
myCondition.await();
myCondition.signal(); // or signalAll()
Locks and Conditions
private Lock myLock = new ReentrantLock();
private Condition myCondition = myLock.newCondition(); myLock.lock();
try {
myLock.lock(); change someConditionThatMustBeTrue;
try { myCondition.signalAll();
while (!someConditionThatMustBeTrue) {
} finally {
myCondition.await ();
myLock.unlock();
}
// do the work that requires the condition and lock }
} catch (InterruptedException e) {}
} finally {
myLock.unlock();
}
Practice
• Review the BankAccount deposit/bonus example that uses locks and
conditions.
• A bonus is given only when the balance is over $5000.
• Classes: LCBankAccount, LCBankThread, LCBankTester
EXECUTORS AND THREAD POOLS
Executors
• A downside of low-level thread code is that it links our task to our
task-execution policy (e.g,. create one thread for every task).
• Also, it is expensive to create new threads.
• Executors allow you to run multiple threads without manually
managing them.
• They also re-use threads, rather than always creating new ones.
Thread Pools
• Thread pools are groups of threads, known as worker threads.
• Worker threads sit idle and ready to run.
• Using a thread pool can reduce the expense of creating threads.
• Important for large-scale applications
• Tasks are submitted to the pool and queued up if all threads in the pool are
busy.
• Example:
• Consider a website that can handle 10 requests at a time. (Hope they don’t get too
popular!)
• Without a thread pool, when the 11th request comes in, an 11th thread is created,
and the system then freezes- for all threads.
• With a pool, the 11th task is queued up and waits for one of the 10 threads to be
free.
Using Executors
• ExecutorService object created through factory method of the Executors class:
ExecutorService executor =
Executors.newSingleThreadExecutor();
// all tasks execute on a single thread
or
Executors.newFixedThreadPool(poolSize);
// all tasks execute on pool of threads
or
Executors.newCachedThreadPool();
// all tasks execute on pool of threads
Using Executors
• Execute the executor
executor.execute(Runnable r);
executor.execute( () -> runnable code );
• You can call execute multiple times for repeated tasks.
• You can also send in an object of type Callable<V>
• Callable is analogous to Runnable, but it returns a value!
• Stop the executor (must do!!):
executor.shutdown();
Practice
• Review the dice example to compare low-level thread control to
executor control.
• Class: DiceTester
IMMUTABLE OBJECTS
Immutable Objects
• An object is immutable if its state cannot change once it is
constructed.
• Example- the String class
• String s = “Jessica”;
• s.toUpperCase(); // does not change s!
• String s2 = s.toUpperCase();
• s = s.toUpperCase();
Immutable Objects
• An object is immutable if its state cannot change once it is
constructed.
• Immutable objects are great for use in multi-threaded programs.
• Because their state cannot change, you don’t have to be worried
about corruption through thread interference or inconsistent states.
• Immutable objects are thread-safe and can be used outside of
synchronized blocks.
Objects in Memory- Modifying an Object
• NOT POSSIBLE with an immutable object!
variable1
variable2
ref1.setVariable2(…); variable3
// not allowed if MyObject is immutable! ref2
Objects in Memory- Changing a Reference
• Still allowed with an immutable object! You are not
changing an object! variable1
variable2
variable3