The document discusses various Java concurrency concepts including threads, locks, semaphores, and concurrent collections. It provides examples to illustrate thread synchronization issues like race conditions and deadlocks. It also demonstrates how to use various concurrency utilities from java.util.concurrent package like CountDownLatch, Exchanger, PriorityBlockingQueue to synchronize thread execution and communication between threads. The examples aim to help developers write correct concurrent programs by avoiding common pitfalls in multithreaded programming.
Overview of Java Concurrency by Ganesh Samarthyam.
Questions the correctness of concurrent programming practices.
Example of a simple thread printing messages, highlighting how thread execution works.
Illustrates thread execution order with a countdown example asking about 'Boom!!!' timing.
Shows how to get the state of a thread through a simple example, illustrating various states.
Demonstrates data races using a shared counter with multiple threads incrementing it.
Highlights the difficulties in debugging parallel programs.
Illustrates deadlock scenarios in thread execution with examples of method locking.
Discusses potential pitfalls of excessive locking in concurrent programming.
Introduces the java.util.concurrent package for managing concurrency.
Explains the benefits of atomic variables in multithreaded environments.
Uses an ATM example to demonstrate thread synchronization with locks.
Simulates an ATM room using semaphore for two concurrent accesses.
Demonstrates race conditions using CountDownLatch for synchronized execution.
Introduces Exchanger for synchronizing communication between two threads.
Lists useful concurrency utilities available in Java for thread management.
Discusses concurrent modifications using collections and how to handle them.
Examples of concurrency issues and solutions such as CopyOnWriteArrayList.
Explains the usage of parallel streams for performance improvement in data processing.Lists meetups and upcoming bootcamps for community interaction and learning.Provides contact details and image credits for the presentation.
Do you thinkyou always
write correct concurrent
programs?
3.
Simple Program: Isit Okay?
class MyThread extends Thread {
public void run() {
System.out.println("In run method; thread name is: "
+ Thread.currentThread().getName());
}
public static void main(String args[]) {
Thread myThread = new MyThread();
myThread.run();
System.out.println("In main method; thread name is: "
+ Thread.currentThread().getName());
}
}
Prints:
In run method; thread name is: main
In main method; thread name is: main
4.
Simple Time Bomb
classTimeBomb extends Thread {
String [] timeStr = { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven",
"Eight", "Nine" };
public void run() {
for(int i = 9; i >= 0; i--) {
System.out.println(timeStr[i]);
}
}
public static void main(String []s) {
TimeBomb timer = new TimeBomb();
System.out.println("Starting 10 second count down... ");
timer.start();
System.out.println("Boom!!!");
}
}
Does it print “Boom!!!” after counting
down from Nine to Zero? If not, how
will you make it happen?
5.
Getting Thread State
classBasicThreadStates extends Thread {
public static void main(String []s) throws Exception {
Thread t = new Thread(new BasicThreadStates());
System.out.println("Just after creating thread; n" +
" The thread state is: " + t.getState());
t.start();
System.out.println("Just after calling t.start(); n" +
" The thread state is: " + t.getState());
t.join();
System.out.println("Just after main calling t.join(); n" +
" The thread state is: " + t.getState());
}
}
In rare cases, it prints:
Just after creating thread;
The thread state is: NEW
Just after calling t.start();
The thread state is: TERMINATED
Just after main calling t.join();
The thread state is: TERMINATED
6.
Race Condition /Data Race
class Counter {
public static long count = 0;
}
class UseCounter implements Runnable {
public static void increment() {
Counter.count++;
System.out.print(Counter.count + " ");
}
public void run() {
increment();
increment();
increment();
}
}
public class DataRace {
public static void main(String args[]) {
UseCounter c = new UseCounter();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
Thread t3 = new Thread(c);
t1.start();
t2.start();
t3.start();
}
}
This program suffers from race
condition / data race problem
$ java DataRace
3 4 5 3 6 3 7 8 9
$ java DataRace
2 4 5 3 6 7 2 8 9
$ java DataRace
1 2 3 4 5 6 7 8 9
$
Dead Lock
// Ballsclass has a globally accessible data member to hold the number of balls thrown
class Balls {
public static long balls = 0;
}
// Runs class has a globally accessible data member to hold the number of runs scored
class Runs {
public static long runs = 0;
}
public class DeadLock {
public static void main(String args[]) throws InterruptedException {
Counter c = new Counter();
// create two threads and start them at the same time
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
System.out.println("Waiting for threads to complete execution…");
t1.join();
t2.join();
System.out.println("Done.");
}
}
11.
Dead Lock
// Counterclass has two methods – IncrementBallAfterRun and IncrementRunAfterBall.
// For demonstrating deadlock, we call these two methods in the run method, so that
// locking can be requested in opposite order in these two methods
class Counter implements Runnable {
// this method increments runs variable first and then increments the balls variable
// since these variables are accessible from other threads,
// we need to acquire a lock before processing them
public void IncrementBallAfterRun() {
// since we’re updating runs variable first, first lock the Runs.class
synchronized(Runs.class) {
// lock on Balls.class before updating balls variable
synchronized(Balls.class) {
Runs.runs++;
Balls.balls++;
}
}
}
public void IncrementRunAfterBall() {
// since we’re updating balls variable first; so first lock Balls.class
synchronized(Balls.class) {
// acquire lock on Runs.class before updating runs variable
synchronized(Runs.class) {
Balls.balls++;
Runs.runs++;
}
}
}
public void run() {
// call these two methods which acquire locks in different order
// depending on thread scheduling and the order of lock acquision,
// a deadlock may or may not arise
IncrementBallAfterRun();
IncrementRunAfterBall();
}
}
Using AtomicInteger
import java.util.concurrent.atomic.*;
//Class to demonstrate how incrementing "normal" (i.e., thread unsafe) integers and incrementing
// "atomic" (i.e., thread safe) integers are different: Incrementing a shared Integer object without locks can result
// in a data race; however, incrementing a shared AtomicInteger will not result in a data race.
class AtomicVariableTest {
// Create two integer objects – one normal and another atomic – with same initial value
private static Integer integer = new Integer(0);
private static AtomicInteger atomicInteger = new AtomicInteger(0);
static class IntegerIncrementer extends Thread {
public void run() {
++integer;
}
}
static class AtomicIntegerIncrementer extends Thread {
public void run() {
atomicInteger.incrementAndGet();
}
}
public static void main(String []args) {
// create three threads each for incrementing atomic and "normal" integers
for(int i = 0; i < 10; i++) {
new IntegerIncrementer().start();
new AtomicIntegerIncrementer().start();
}
System.out.printf("final int value = %d and final AtomicInteger value = %d",
integer, atomicInteger.intValue());
}
}
17.
Atomic Variables
❖ AtomicBoolean:Atomically updatable Boolean value.
❖ AtomicInteger: Atomically updatable int value; inherits from the N umber class.
❖ AtomicIntegerArray: An int array in which elements can be updated atomically.
❖ AtomicLong: Atomically updatable long value; inherits from N umber class.
❖ AtomicLongArray: A long array in which elements can be updated atomically.
❖ AtomicReference<V>: An atomically updatable object reference of type V.
❖ AtomicReferenceArray<E>: An atomically updatable array that can hold object
references of type E (E refers to be base type of elements).
How to simulateonly one
thread using an ATM and
others wait for their turn?
20.
Locks
import java.util.concurrent.locks.*;
// Thisclass simulates a situation where only one ATM machine is available and
// and five people are waiting to access the machine. Since only one person can
// access an ATM machine at a given time, others wait for their turn
class ATMMachine {
public static void main(String []args) {
// A person can use a machine again, and hence using a "reentrant lock"
Lock machine = new ReentrantLock();
// list of people waiting to access the machine
new Person(machine, "Mickey");
new Person(machine, "Donald");
new Person(machine, "Tom");
new Person(machine, "Jerry");
new Person(machine, "Casper");
}
}
21.
Locks
// Each Personis an independent thread; their access to the common resource
// (the ATM machine in this case) needs to be synchronized using a lock
class Person extends Thread {
private Lock machine;
public Person(Lock machine, String name) {
this.machine = machine;
this.setName(name);
this.start();
}
public void run() {
try {
System.out.println(getName() + " waiting to access an ATM machine");
machine.lock();
System.out.println(getName() + " is accessing an ATM machine");
Thread.sleep(1000); // simulate the time required for withdrawing amount
} catch(InterruptedException ie) {
System.err.println(ie);
}
finally {
System.out.println(getName() + " is done using the ATM machine");
machine.unlock();
}
}
}
22.
Locks
$ java ATMMachine
Donaldwaiting to access an ATM machine
Donald is accessing an ATM machine
Jerry waiting to access an ATM machine
Mickey waiting to access an ATM machine
Tom waiting to access an ATM machine
Casper waiting to access an ATM machine
Donald is done using the ATM machine
Jerry is accessing an ATM machine
Jerry is done using the ATM machine
Mickey is accessing an ATM machine
Mickey is done using the ATM machine
Tom is accessing an ATM machine
Tom is done using the ATM machine
Casper is accessing an ATM machine
Casper is done using the ATM machine
Semaphore
import java.util.concurrent.Semaphore;
// Thisclass simulates a situation where an ATM room has only two ATM machines
// and five people are waiting to access the machine. Since only one person can access
// an ATM machine at a given time, others wait for their turn
class ATMRoom {
public static void main(String []args) {
// assume that only two ATM machines are available in the ATM room
Semaphore machines = new Semaphore(2);
// list of people waiting to access the machine
new Person(machines, "Mickey");
new Person(machines, "Donald");
new Person(machines, "Tom");
new Person(machines, "Jerry");
new Person(machines, "Casper");
}
}
25.
Semaphore
import java.util.concurrent.Semaphore;
// EachPerson is an independent thread; but their access to the common resource
// (two ATM machines in the ATM machine room in this case) needs to be synchronized.
class Person extends Thread {
private Semaphore machines;
public Person(Semaphore machines, String name) {
this.machines = machines;
this.setName(name);
this.start();
}
public void run() {
try {
System.out.println(getName() + " waiting to access an ATM machine");
machines.acquire();
System.out.println(getName() + " is accessing an ATM machine");
Thread.sleep(1000); // simulate the time required for withdrawing amount
System.out.println(getName() + " is done using the ATM machine");
machines.release();
} catch(InterruptedException ie) {
System.err.println(ie);
}
}
}
26.
Semaphore
Mickey waiting toaccess an ATM machine
Mickey is accessing an ATM machine
Jerry waiting to access an ATM machine
Jerry is accessing an ATM machine
Tom waiting to access an ATM machine
Donald waiting to access an ATM machine
Casper waiting to access an ATM machine
Mickey is done using the ATM machine
Jerry is done using the ATM machine
Tom is accessing an ATM machine
Donald is accessing an ATM machine
Tom is done using the ATM machine
Donald is done using the ATM machine
Casper is accessing an ATM machine
Casper is done using the ATM machine
27.
How can yousimulate a running
race with a count down? Once
“Start” => All threads should run
28.
CountDownLatch
import java.util.concurrent.*;
// thisclass simulates the start of a running race by counting down from 5. It holds
// three runner threads to be ready to start in the start line of the race and once the count down
// reaches zero, all the three runners start running...
class RunningRaceStarter {
public static void main(String []args) throws InterruptedException {
CountDownLatch counter = new CountDownLatch(5);
// count from 5 to 0 and then start the race
// instantiate three runner threads
new Runner(counter, "Carl");
new Runner(counter, "Joe");
new Runner(counter, "Jack");
System.out.println("Starting the countdown ");
long countVal = counter.getCount();
while(countVal > 0) {
Thread.sleep(1000); // 1000 milliseconds = 1 second
System.out.println(countVal);
if(countVal == 1) {
// once counter.countDown(); in the next statement is called,
// Count down will reach zero; so shout "Start"
System.out.println("Start");
}
counter.countDown(); // count down by 1 for each second
countVal = counter.getCount();
}
}
}
29.
CountDownLatch
// this Runnerclass simulates a track runner in a 100-meter dash race. The runner waits till the
// count down timer gets to zero and then starts running
class Runner extends Thread {
private CountDownLatch timer;
public Runner(CountDownLatch cdl, String name) {
timer = cdl;
this.setName(name);
System.out.println(this.getName() + " ready and waiting for the count down to start");
start();
}
public void run() {
try {
// wait for the timer count down to reach 0
timer.await();
} catch (InterruptedException ie) {
System.err.println("interrupted -- can't start running the race");
}
System.out.println(this.getName() + " started running");
}
}
30.
CountDownLatch
Carl ready andwaiting for the count down to start
Joe ready and waiting for the count down to start
Jack ready and waiting for the count down to start
Starting the countdown
5
4
3
2
1
Start
Carl started running
Jack started running
Joe started running
31.
How to exchangedata (or
synchronise communication)
between two threads?
32.
Exchanger
class CoffeeShopThread extendsThread {
private Exchanger<String> sillyTalk;
public CoffeeShopThread(Exchanger<String> args) {
sillyTalk = args;
}
public void run() {
String reply = null;
try {
// exchange the first messages
reply = sillyTalk.exchange("Who's there?");
// print what Duke said
System.out.println("Duke: " + reply);
// exchange second message
reply = sillyTalk.exchange("Duke who?");
// print what Duke said
System.out.println("Duke: " + reply);
// there is no message to send, but to get a message from Duke thread,
// both ends should send a message; so send a "dummy" string
reply = sillyTalk.exchange("");
System.out.println("Duke: " + reply);
} catch(InterruptedException ie) {
System.err.println("Got interrupted during my silly talk");
}
}
}
33.
Exchanger
import java.util.concurrent.Exchanger;
// TheDukeThread class runs as an independent thread. It talks to the CoffeeShopThread that
// also runs independently. The chat is achieved by exchanging messages through a common
// Exchanger<String> object that synchronizes the chat between them.
// Note that the message printed are the "responses" received from CoffeeShopThread
class DukeThread extends Thread {
private Exchanger<String> sillyTalk;
public DukeThread(Exchanger<String> args) {
sillyTalk = args;
}
public void run() {
String reply = null;
try {
// start the conversation with CoffeeShopThread
reply = sillyTalk.exchange("Knock knock!");
// Now, print the response received from CoffeeShopThread
System.out.println("CoffeeShop: " + reply);
// exchange another set of messages
reply = sillyTalk.exchange("Duke");
// Now, print the response received from CoffeeShopThread
System.out.println("CoffeeShop: " + reply);
// an exchange could happen only when both send and receive happens
// since this is the last sentence to speak, we close the chat by
// ignoring the "dummy" reply
reply = sillyTalk.exchange("The one who was born in this coffee shop!");
// talk over, so ignore the reply!
} catch(InterruptedException ie) {
System.err.println("Got interrupted during my silly talk");
}
}
}
34.
Exchanger
// Co-ordinate thesilly talk between Duke and CoffeeShop by instantiating the Exchanger object
// and the CoffeeShop and Duke threads
class KnockKnock {
public static void main(String []args) {
Exchanger<String> sillyTalk = new Exchanger<String>();
new CoffeeShopThread(sillyTalk).start();
new DukeThread(sillyTalk).start();
}
}
Useful Concurrency Utilities
❖Semaphore: Controls access to one or more shared
resources
❖ CountDownLatch: Allows threads to wait for a countdown
to complete
❖ Exchanger: Supports exchanging data between two threads
❖ CyclicBarrier: Enables threads to wait at a predefined
execution point
❖ Phaser: Supports a synchronization barrier
Priority Queue Doesn’t“Block”
import java.util.*;
// Simple PriorityQueue example. Here, we create two threads in which one thread inserts an element,
// and another thread removes an element from the priority queue.
class PriorityQueueExample {
public static void main(String []args) {
final PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
// spawn a thread that removes an element from the priority queue
new Thread() {
public void run() {
// Use remove() method in PriorityQueue to remove the element if available
System.out.println("The removed element is: " + priorityQueue.remove());
}
}.start();
// spawn a thread that inserts an element into the priority queue
new Thread() {
public void run() {
// insert Integer value 10 as an entry into the priority queue
priorityQueue.add(10);
System.out.println("Successfully added an element to the queue ");
}
}.start();
}
}
40.
Priority Queue Doesn’t“Block”
java PriorityQueueExample
Exception in thread "Thread-0"
java.util.NoSuchElementException
at java.util.AbstractQueue.remove(AbstractQueue.java:117)
at PriorityQueueExample$1.run(PriorityQueueExample.java:12)
Successfully added an element to the queue
41.
Using PriorityBlockingQueue
// Illustratesthe use of PriorityBlockingQueue. In this case, if there is no element available in the priority queue
// the thread calling take() method will block (i.e., wait) till another thread inserts an element
import java.util.concurrent.*;
class PriorityBlockingQueueExample {
public static void main(String []args) {
final PriorityBlockingQueue<Integer> priorityBlockingQueue = new PriorityBlockingQueue<>();
new Thread() {
public void run() {
try {
// use take() instead of remove()
// note that take() blocks, whereas remove() doesn’t block
System.out.println("The removed element is: " + priorityBlockingQueue.take());
} catch(InterruptedException ie) {
// its safe to ignore this exception
ie.printStackTrace();
}
}
}.start();
new Thread() {
public void run() {
// add an element with value 10 to the priority queue
priorityBlockingQueue.add(10);
System.out.println("Successfully added an element to the que);
}
}
Modifying a ListConcurrently
import java.util.*;
public class ModifyingList {
public static void main(String []args) {
List<String> aList = new ArrayList<>();
aList.add("one");
aList.add("two");
aList.add("three");
Iterator listIter = aList.iterator();
while(listIter.hasNext()) {
System.out.println(listIter.next());
aList.add("four");
}
}
}
$ java ModifyingList
one
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at ModifyingList.main(ModifyingList.java:14)
44.
CopyOnWriteArrayList
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
publicclass COWList {
public static void main(String []args) {
List<String> aList = new CopyOnWriteArrayList<>();
aList.add("one");
aList.add("two");
aList.add("three");
Iterator listIter = aList.iterator();
while(listIter.hasNext()) {
System.out.println(listIter.next());
aList.add("four");
}
}
}
$ java COWList
one
two
three
import java.util.Arrays;
class StringConcatenator{
public static String result = "";
public static void concatStr(String str) {
result = result + " " + str;
}
}
class StringSplitAndConcatenate {
public static void main(String []args) {
String words[] = "the quick brown fox jumps over the lazy dog".split(" ");
Arrays.stream(words).forEach(StringConcatenator::concatStr);
System.out.println(StringConcatenator.result);
}
}
Gives wrong results with
with parallel() call