[go: up one dir, main page]

0% found this document useful (0 votes)
100 views92 pages

Threads

The document discusses threads and synchronization in Java. It covers: - Threads allow separate processes that can run concurrently. Each thread has its own stack but shares heap memory with other threads. - Synchronization is needed to prevent race conditions when multiple threads access shared resources. The synchronized keyword and atomic variables help ensure only one thread accesses critical sections of code at a time. - Interrupting threads allows one thread to signal another to stop running. Threads should check for and respond to interrupts periodically.

Uploaded by

k
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
100 views92 pages

Threads

The document discusses threads and synchronization in Java. It covers: - Threads allow separate processes that can run concurrently. Each thread has its own stack but shares heap memory with other threads. - Synchronization is needed to prevent race conditions when multiple threads access shared resources. The synchronized keyword and atomic variables help ensure only one thread accesses critical sections of code at a time. - Interrupting threads allows one thread to signal another to stop running. Threads should check for and respond to interrupts periodically.

Uploaded by

k
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 92

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

• Good article/pic here: https://www.journaldev.com/4098/java-heap-


space-vs-stack-memory
Java Threads
• Each thread has its own stack memory:
• method activation records
• local variables
• parameters
• But threads share the heap.
• objects are shared!
• Threads share resources (variables and data).
• This happens so fast that it appears that the threads are operating in parallel.
• This is more efficient and allows for easy communication…
• But of course it’s risky to have shared resources.
Single Threaded Programs
• All Java programs use threads.
• By default, each program uses a single thread.
• The main thread
• If you’ve never used threads directly… then you’ve been writing one-
thread programs all along!
static Thread Methods
• Thread.sleep
• Pauses the execution of the current thread for a specified number of
milliseconds (ish)
• Throws a checked exception (InterruptedException) that must be caught (or
propagated)
• Thread.activeCount()
• Thread.enumerate(Thread[] activeThreadArray)
• Thread.currentThread()
• Can set a name on a Thread using super(name) or setName method,
which is useful when distinguishing between threads
Practice
• Review the example that invokes methods on the main thread.
CREATING THREADS
Creating Your Own Threads
• You can create and manage threads in your program
• Two ways: extend Thread or implement Runnable
The Thread Class
• Your class extends Thread
• Overrides the public void run() method
• Can call super(name) to set a name for the thread

• You start by invoking the start method:


myThreadObject.start()
• Do not call run- call start.
• The start method has setup code it needs to execute before it invokes run.
• Invoking run doesn’t actually create a new thread!!
The Runnable Interface
• Your class implements Runnable
• Overrides the public void run() method
• You start by sending an instance of your class to the Thread
constructor and invoking start:
Thread thread = new Thread(myRunnableObject)
thread.start()
• Again, do not call run- call start.
• The start method has setup code it needs to execute before it invokes run.
• Invoking run doesn’t actually create a new thread!!
The Runnable Interface
• Runnable is a functional interface!
• Queue the lambda entrance music!
• If you have a simple run method, you can skip creating an entire class
that implements Runnable and just use a lambda:
new Thread(
() -> myRunningMethod();
).start()
Practice
• Review the program that prints dates using Thread and Runnable.
When should I use concurrency?
• Tasks that will take a long time to execute.
• Any program where you want one thing to be able to run in the
background and do other things too.

• GUI programs- you want the program to remain responsive while it is


performing tasks.
• NOTE: You cannot use the Thread and Runnable setup with JavaFX.
• The scene graph is not thread-safe and can only be accessed and modified
from the UI thread called the JavaFX Application thread.
• The javafx.concurrent package supposed multithreaded GUIs.
INTERRUPTIONS
Ending Threads
• A thread terminates when the run method terminates by:
• returning,
• reaching the end, or
• throwing an uncaught exception
• You can also request a thread to terminate with an interrupt
• You can’t force it! But you can request it.
Thread Interruptions
• Each thread has a boolean flag that represents whether it is
interrupted
Requesting a Thread Interruption
• void interrupt()
• Sends an interrupt request to the thread
• The flag of the thread is set to true
• If the thread is blocked (it is sleeping or waiting), an InterruptedException is
thrown
Responding to Thread Interruptions
• A thread could just ignore interruptions! But this isn’t convention.
• Threads should occasionally check to see if they’ve been interrupted.
• This code is not automatic- you have to write it!
• It’s common to stop a thread when it’s been interrupted.
• But again, this isn’t automatic!
Responding to Thread Interruptions
• static boolean interrupted()
• Tests whether the current thread has been interrupted
• Side effect: resets the flag to false (clears the interruption status)
• boolean isInterrupted
• Tests whether a thread has been interrupted (invoke on a Thread object)
• No side effects
• Often use with: Thread.currentThread()
• Thread.currentThread().isInterrupted()
Supporting Interruptions
• Inside a run that uses sleeps:
try {
while(moreWorkToDo) {
// do the work
// sleep
}
} catch(InterruptedException ex) {
// thread interrupted during sleep/wait
return;
}
Supporting Interruptions
• Inside a run that doesn’t sleep:
try {
while(!Thread.currentThread().isInterrupted())
&& moreWorkToDo)
// do the work
} catch(InterruptedException ex) {
// thread interrupted during sleep/wait
}
// exit the run method
Supporting Interruptions
• Inside a run that doesn’t sleep:
try {
while(moreWorkToDo) {
if(Thread.currentThread().isInterrupted()) {
// throw new InterruptedException();
} else {
// do the work
}
}
} catch(InterruptedException ex) {
// react to all interruption (perhaps return)
}
// exit the run method
Practice
• Interrupt the time print thread.
• When it’s asleep
• While it’s running
• Change the behavior of how the thread responds to the interrupt
Join
• Invoking join makes the current thread wait until another thread
finishes to continue.
• Example: invoking myThread.join() from main will make the main
method wait until myThread finishes execution.
Practice
• Wait until the time programs are done to print a final message.
SYNCHRONIZATION
Shared Resources
• Multiple threads share access to the same data.
• Objects are on the shared heap!
• If two threads each modify the state of the object, unpredictable or
incorrect results can occur.
• This is called a race condition.
Race Conditions
• Race conditions can occur on any statement that is not atomic
(meaning happening all at once).
• Even simple statements have multiple steps:
• i++
• i = i + 1;
• get the current value of i
• add one to it
• store the new value of i back to the variable
• You could have different threads working at different steps!
Practice
• Review the simple counter example.
• Counter, RaceConditionTest
• Review the bank account example (bonus/fee).
• BankAccount, BankThread, BankTester
Synchronized
• The most straightforward way to prevent a race condition is to synchronize.
• Synchronizing allows only one thread to access a block of code at a time.
• You can synchronize an entire method:
public synchronized void myMethod() {
… critical code
}
• Or a block of code:
public void myMethod() {
… non critical code
synchronized(someObject) { // could be “this”
… critical code
}
}
• You can also synchronize static methods.
Synchronized
• Note that this:
public synchronized void myMethod() {
… critical code
}
• Is equivalent to this:
public void myMethod() {
synchronized(this) {
… critical code
}
}
Synchronized
• When a thread reaches synchronized code, the JVM checks to see if
anyone holds the lock associated with the sync object.
• If no one does, the current thread gets the lock.
• If someone else does, the current thread is placed in the waiting area.
• When a thread is done with synchronized code, it releases the lock.
• The JVM then checks the waiting area to see if someone else needs the lock.

• Synchronizing works great… but it can lead to inefficiency, so it should


be used cautiously.
Practice
• Update the bank example to avoid race conditions.
Shortcomings of synchronized
• It can be easy to make mistakes that make code appear to be
synchronized, when it’s really not
• To avoid this, make sure to choose an object common to all threads.
• Deadlock
• Avoid this by being careful about how synchronized methods invoke other
synchronized methods (or nested synchronized blocks).
Practice
• Review an incorrect bank account examples that synchronizes the
thread’s method, not the Bank Account methods.
• Review the deadlock example
• MyObject, DeadlockThread
VARIABLES
Atomic
• An atomic action happens all at once
• It cannot stop in the middle
• It either happens completely or not at all
• i++ is not atomic- there are multiple steps
Atomic Variables
• java.util.concurrent.atomic contains classes that support atomic
actions on variables
• Example: AtomicInteger
• get
• set
• incrementAndGet
• decrementAndGet
• updateAndGet( IntUnaryOperator ) (IntUnaryOperator: param int, return int)
• Using an atomic variable can help avoid unnecessary synchronization
Practice
• Review the revised Counter example that uses atomic integer
• AtomicCounter, AtomicTest
LongAdder
• Java 8 added a new class LongAdder designed represent a collective
sum.
• Methods
• add(long)
• increment
• sum
volatile
• If one thread changes the value of a variable, you expect other
threads to see that change.
• But that’s not always the case!
• The JVM allows values to be stored temporarily in working memory (like a
cache), rather than main memory (the heap).
• There is no guarantee that the heap version has been updated- the updated
value could still be stored in cache.
• So one thread could change the value and that change is stored in
cache. But then another thread reads from the heap and gets the
wrong value.
volatile
• Declaring a variable volatile enforces that all updates will be written
back to heap memory immediately.

• It also ensure that reads/writes are atomic (all at once).


• Reads and writes are atomic for reference variables and for most primitives
(NOT for long and double).
• Reads and writes are atomic for all variables declared volatile (including long
and double).
volatile- NOT synchronized!!
• volatile does not mean atomic overall!
• myVolatileVariable++ is still not atomic!
• volatile is not the same as synchronized
• volatile may be good enough if you have only one thread updating
the variable and many others only reading it
• if you have multiple threads updating, you need to synchronize!
WAIT/NOTIFY
Locks
• Every object has an intrinsic lock.
• Also called a monitor lock or just monitor.
• A thread can acquire a lock.
• It then owns the lock.
• As long as it owns the lock, no other thread can acquire the lock.
• When the thread it done, it can release the lock.
Synchronization
• If thread A tries to enter a synchronized block and thread B is already
executing that block (meaning it holds the lock on the synchronized
object), thread A is placed into a waiting area.
• When thread B finishes, it releases the lock.
• The JVM then removes A from the waiting area and assigns the lock
to thread A.

• This is one specific application of the wait/notify mechanism.


wait/notify/notifyAll
• A thread waits on a condition to continue executing.
• Some other thread updates that condition (notifies the thread) when
it can execute.

• notify wakes up a single thread (decided by the ThreadScheduler)


• We don’t automatically know which one will be notified- only good for
“massively parallel” operations where all threads doing the same job.
• notifyAll wakes up all threads
Waiting and Notifying

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.

private Condition myCondition;

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!

MyObject ref1 = new MyObject(…);


MyObject ref2 = ref1; ref1

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

MyObject ref1 = new MyObject(…); ref1

MyObject ref2 = ref1; variable1


ref1 = new MyObject(…); variable2
variable3
ref2
Objects in Memory
• Invoking a method with the dot operator most likely changes the
object itself
• The state or characteristic of the actual object changes.
• Assigning a new value wchanges what the variable/reference points
to
• ith the equals sign just This “changes the memory arrows” but does
not change the actual object
How to Create Immutable Classes
• Make the class final
• Or make the constructor private and provide static factory method
• No setter methods
• Make all instance data “variables” final and private
• For instance variables that are mutable objects:
• Ensure there are no methods that would modify those objects
• Do not share references to those objects!
• When passed an object, make a copy before storing it as instance data
• Example: in a constructor!
• When returning an object, make a copy to return
• Example: in getter methods!
Practice
• Review the EmployeeIM example.
COLLECTIONS
Multiple Threads and Collections
• It is easy to corrupt a data structure if accessed by multiple threads.
• You should not use a collection with no thread support in a
concurrent program.
• Instead, use a:
• Synchronized Collection
• Concurrent Collection
Practice
• Look at an example of filling and emptying a list with threads using a
normal non-thread-safe collection.
• Classes: ListAdder, ListRemover, ConcurrentCollectionsTest
Synchronized Collection Classes
• All methods in the class are synchronized.
• Classes
• Vector
• Hashtable (not HashMap or TreeMap)
• Create your own (preferred to above):
• Collections.synchronizedList(myList)
• Collections.synchronizedMap(myMap)
• Collections.synchronizedSet(mySet)
Practice
• Re-run the add/remove list example with a synchronized list.
Thread Safety- Important!!
• Using a synchronized collection assures no corruption from invoking a
method.
• But it doesn’t mean you’re off the hook for thread safety overall!
• Combinations of methods are not atomic!
• Example:
if(!testList.isEmpty())
testList.remove(0);
• Fix:
synchronized(testList) {
if(!testList.isEmpty())
testList.remove(0);
}
Practice
• Re-run the add/remove list example with a synchronized list AND
thread-safe code.
Iterators
• A ConcurrentModificationException is thrown if one thread modifies
a synchronized collection while an iterator is retrieving an item.
• You’d have to synchronize access to the block of code iterating to
prevent this.
• Costly!
Concurrent Collection Classes
• More sophisticated support
• Thread safe and scalable!
• These classes allow concurrent access to different parts of a single
data structure.
• Classes
• ConcurrentHashMap
• More efficient than Collections.sychronizedMap(myMap)
• ConcurrentLinkedQueue
Iterators
• The concurrent collection classes return weakly consistent iterators
• These iterators will not throw a ConcurrentModificationException
• The iterator may or may not reflect all modifications made since
construction
• It will not return an item twice
• If an element was removed before iteration starts, it won’t be returned
• If an element is added after iteration starts, it may or may not be returned
ConcurrentHashMap
• Retrieval operations do not block
• Supports multiple concurrent write operations
• Use traditional Map methods, plus atomic versions of compound
operations:
• putIfAbsent(key, value)
• compute(key, BiFunction<K, V> remapFunction)
• BiFunction: parameter K and V, returns V
• computeIfPresent(key, BiFunction)
• computeIfAbsent(key, BiFunction)
ConcurrentHashMap
• Bulk operations
• search- search elements
• reduce- accumulate elements
• forEach- perform an action on each element
• Each can be applied to Keys, Values, both, or Map.Entry
• Each takes a parallelismThreshold and a function or bi function
• Threshold: if the map has more elements than the threshold, it will be
parallelized
Thread Safety- Important!!
• Again, using a concurrent collection only assures no corruption from
invoking a method.
• But it doesn’t mean you’re off the hook for thread safety overall!
• Combinations of methods are still not atomic!
Practice
• Review the scrabble words example.
• Make a map of the number of words that start with each letter.
• Make a map of the sum of the score of words that start with each letter.
• Classse: LetterStartThread, LetterStartThreadSafe, WordScoreSumThreadSafe,
ConcurrentMapTestingScrabble

You might also like