Lecture 24 Concurrency 2
(D&D 23)
Date
Goals
By the end of this lesson, you should:
1. Understand the implications of producer-consumer relationships
2. Be able to use wait() and notifyAll() in order to synchronize
threads across methods.
CompSci 230: 2017
2
synchronized is not enough
1. synchronized
is not enough
2. Producers and
consumers:
Library
example
3. Is it really
threadsafe?
4. Notes
The synchronized keyword lets us ensure that only one
method at a time can enter the protected block of code,
e.g., a method.
This does not protect shared data from inconsistency.
Consider a shared instance variable being accessed by two
different synchronized methods.
Even if each method can only be executed by one thread at
a time, this still allows one thread to execute the first
method and the other to execute the second method, at
the same time.
5. Summary
This means that the shared instance variable can be
accessed by both threads, with potentially unwanted
results.
CompSci 230: 2017
3
synchronized is not enough
1. synchronized
is not enough
2. Producers and
consumers:
Library
example
3. Is it really
threadsafe?
4. Notes
5. Summary
Often, this situation arises in the context of buffers.
For example, a FIFO buffer (first-in-first-out) has data added
to its end and removed from the front.
•
FIFO buffers are also known as (simple) queues and are usually
implemented as a linked list (-> COMPSCI105/107)
FIFO buffers are often used with threaded applications: One
thread adds data to the end of the queue, one thread reads
the data from the front of the queue. Example:
In an audio player, one thread reads audio data from disk. This thread
typically blocks while it reads data from the disk and then writes a lot of
data to the FIFO in big chunks at irregular times. The other thread reads
data at regular intervals in small quantities off the front of the queue for
immediate replay.
Data in buffer
CompSci 230: 2017
Data read from here
Data added here
4
synchronized is not enough
1. synchronized
is not enough
2. Producers and
consumers:
Library
example
3. Is it really
threadsafe?
4. Notes
Data in buffer
Data added here
Data read from here
Two problems can occur here:
1. The reading thread may consume data faster than the
writing thread is able to produce data, causing the buffer
to run empty.
2. The writing thread may produce data faster than the
reading thread can consume it, causing the writing
thread to overfill the buffer beyond capacity.
5. Summary
CompSci 230: 2017
5
Library example
1. synchronized
is not enough
This is a simplified example of a producer-consumer relationship.
Consider a library that owns a certain number of books.
2. Producers and
consumers:
Library
example
3. Is it really
threadsafe?
Each library patron may borrow a certain number of books. The
patron keeps the books for a while and then returns them.
The library cannot loan out more books than it owns.
In other words: In real life, a patron can’t come and borrow more
books than the library currently has on its shelves.
4. Notes
5. Summary
CompSci 230: 2017
6
Library example
1. synchronized
is not enough
2. Producers and
consumers:
Library
example
3. Is it really
threadsafe?
4. Notes
5. Summary
In a program:
•
•
•
•
•
•
•
•
CompSci 230: 2017
The number of books currently in the library is an int.
Each patron is a thread.
The library is an object shared by all patron threads.
The library has methods for borrowing and returning books. These
methods are called by the patrons, passing the number of books
they wish to borrow or return.
These methods are synchronized, meaning that only one
patron thread at a time can borrow books, or return books.
Note: Many, even all patrons, can hold borrowed books at any
given time. They just can’t take them out simultaneously, or return
them simultaneously.
Each patron chooses the number of books to take out (a number
smaller than the number of books that the library owns).
The library patrons act as both consumers (they take books out)
and producers (they bring books back) of data (books) in the buffer
(the library).
7
The Library interface + class
1. synchronized
is not enough
2. Producers and
consumers:
Library
example
3. Is it really
threadsafe?
// Common interface for the thread-safe and non-thread-safe
// library classes
public interface Library {
public void borrowBooks(String borrower, int numberOfBooks);
public void returnBooks(String borrower, int numberOfBooks);
}
Implementation (for now):
public class NonThreadSafeLibrary implements Library {
private int books = 40;
public synchronized void borrowBooks(String patron, int numberToBorrow) {
books -= numberToBorrow;
System.out.println(patron + " borrowed " + numberToBorrow + " books");
System.out.println("Library has " + books);
}
4. Notes
public synchronized void returnBooks(String patron, int numberToReturn) {
books += numberToReturn;
System.out.println(patron + " returned " + numberToReturn + " books");
System.out.println("Library has " + books);
}
5. Summary
}
CompSci 230: 2017
8
LibraryPatron class
1. synchronized
is not enough
2. Producers and
consumers:
Library
example
import java.security.SecureRandom;
public class LibraryPatron implements Runnable {
private static final SecureRandom randomGenerator = new SecureRandom();
public String name;
public Library library;
public LibraryPatron(String name, Library library) {
this.name = name;
this.library = library;
}
public void run() {
for (int i=0; i<20; i++) {
// Borrow some books (up to 14)
int numberOfBooks = randomGenerator.nextInt(15);
library.borrowBooks(name, numberOfBooks);
// Hang onto them for a while
try {
Thread.sleep(randomGenerator.nextInt(20));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// Return them
library.returnBooks(name, numberOfBooks);
}
}
3. Is it really
threadsafe?
4. Notes
5. Summary
}
CompSci 230: 2017
9
LibraryTest main()
1. synchronized
is not enough
public static void main(String[] args) {
Library library = new NonThreadSafeLibrary();
LibraryPatron
LibraryPatron
LibraryPatron
LibraryPatron
LibraryPatron
LibraryPatron
2. Producers and
consumers:
Library
example
alice = new LibraryPatron("Alice", library);
bob = new LibraryPatron("Bob", library);
charlie = new LibraryPatron("Charlie", library);
donna = new LibraryPatron("Donna", library);
eve = new LibraryPatron("Eve", library);
fred = new LibraryPatron("Fred", library);
ExecutorService es = Executors.newCachedThreadPool();
3. Is it really
threadsafe?
es.execute(alice);
es.execute(bob);
es.execute(charlie);
es.execute(donna);
es.execute(eve);
es.execute(fred);
4. Notes
5. Summary
es.shutdown();
}
CompSci 230: 2017
10
Library test results
1. synchronized
is not enough
2. Producers and
consumers:
Library
example
3. Is it really
threadsafe?
4. Notes
5. Summary
CompSci 230: 2017
…
Donna returned 5 books
Library has 12
Donna borrowed 10 books
Library has 2
Bob returned 6 books
Library has 8
Bob borrowed 11 books
Library has -3
Charlie returned 5 books
Library has 2
Charlie borrowed 8 books
Library has -6
Donna returned 10 books
Library has 4
…
11
What can we do?
1. synchronized
is not enough
2. Producers and
consumers:
Library
example
3. Is it really
threadsafe?
4. Notes
5. Summary
Normally:
• The library would ask a patron wait until books become available.
• The library would notify waiting patrons that books have become
available.
We can do the same here. Note:
• A patron would be told to wait() when they try and borrow
books.
• Since only one patron can enter the borrowBooks() method at
any time, only that patron can be told to wait. All other patrons are
blocked until that patron has vacated borrowBooks().
• The library could either notify() that patron specifically, or
notifyAll() waiting patrons – in this case, it makes little
difference because the only patron that does actually wait() is the
one that executes borrowBooks().
CompSci 230: 2017
12
The ThreadSafeLibrary class
1. synchronized
is not enough
public class ThreadSafeLibrary implements Library {
private int books = 40;
public synchronized void borrowBooks(String patron, int numberToBorrow) {
// While the library is short of books, let the thread wait
while (numberToBorrow > books) {
try {
wait();
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
books -= numberToBorrow;
System.out.println(patron + " borrowed " + numberToBorrow + " books");
System.out.println("Library has " + books);
}
2. Producers and
consumers:
Library
example
3. Is it really
threadsafe?
public synchronized void returnBooks(String patron, int numberToReturn) {
books += numberToReturn;
System.out.println(patron + " returned " + numberToReturn + " books");
System.out.println("Library has " + books);
// Let all waiting threads know that we have had books come back in
notifyAll();
}
4. Notes
5. Summary
Don’t forget to change the class in LibraryTest!
}
CompSci 230: 2017
13
Is it really threadsafe?
1. synchronized
is not enough
2. Producers and
consumers:
Library
example
3. Is it really
threadsafe?
4. Notes
5. Summary
Running our example with the “thread-safe” class will generally not
cause problems.
But: Consider the following two lines:
books -= numberToBorrow;
books += numberToReturn;
Each of these involve reading the value of books, subtracting or adding the
value of numberToBorrow or numberToReturn, and then saving the
result back to books. This will generally happen very, very quickly.
Which guarantee do we have that these operations are atomic, i.e., cannot be
completed by two different threads at the same time? None.
Could both threads read the number of books, with one thread then changing
it upwards without the other knowing it? Unlikely, but yes.
Have a look at the ReallyThreadSafeLibrary example for a solution.
CompSci 230: 2017
14
Adding complexity
1. synchronized
is not enough
Consider adding an AcquisitionsLibrarian who occasionally adds books
to the library, and a shelf capacity that the library cannot exceed.
2. Producers and
consumers:
Library
example
You could also add a Librarian tasked with throwing out old books on a
regular basis.
3. Is it really
threadsafe?
Challenge: Implement these classes. Will the library class remain
thread-safe under these circumstances? E.g., what would happen if
you had a separate method to dispose of books? What might happen
if Librarian tried to dispose of books via this method, and a borrower
thread took some out at the same time?
4. Notes
5. Summary
CompSci 230: 2017
15
Notes
1. synchronized
is not enough
2. Producers and
consumers:
Library
example
3. Is it really
threadsafe?
4. Notes
5. Summary
Terminology:
• A situation as in our incrementor example where threads use the
value of a shared variable that may since have been updated by
another thread is known as a dirty read.
• If several threads write to a variable an unsynchronized fashion, as
in our incrementor and library examples, we have a race condition
– the threads are in a “race” to write to the variable (and the
thread scheduling, which we can’t predict, determines who “wins”)
• It’s possible for two threads to end up waiting such that neither
cannot return to runnable state without notification from the
other. This is called a deadlock.
Topics we haven’t dealt with here but that are worth exploring (and
which you’re ready to explore) include: timed waiting, explicit
acquisition/release of locks, data structures that assist in multithreading, etc.
CompSci 230: 2017
16
What do we know
1. synchronized
is not enough
2. Producers and
consumers:
Library
example
3. Is it really
threadsafe?
4. Notes
5. Summary
CompSci 230:
1. Thread write access to shared memory can lead to
unpredictable behaviour of multi-threaded software.
2. The use of synchronized in Java does not offer complete
protection.
3. A common scenario in which multiple threads access the
same data is when a producer thread writes data to a
buffer from which a consumer thread reads. This happens,
e.g., in audio or video players where one thread reads the
data from disk or network, and the other thread replays.
This can result in buffer underruns or buffer overruns.
4. We can tell a Java thread to wait(). This puts the thread
into the waiting state. A waiting thread can be interrupted
to resume execution. A call to the notify() method of the
waiting thread object, or to notifyAll() interrupts the/all
waiting thread(s) and causes it/them to return to runnable
state.
20175. Writing truly thread-safe software is challenging!
17
Resources & Homework
1. synchronized
is not enough
https://docs.oracle.com/javase/tutorial/essential/concurrency/
https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html
https://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html
2. Producers and
consumers:
Library
example
3. Is it really
threadsafe?
https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html
https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html (wait(), notify(),
notifyAll())
https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
D & D Chapter 23
4. Notes
5. Summary
CompSci 230: 2017
18
Next
Exam. Good luck everyone!
© Copyright 2026 Paperzz