0% found this document useful (0 votes)
59 views

w13 Concurrency Curs

This document discusses Java concurrency and threads. It covers key topics such as: - Threads allow multi-threaded software to run parts concurrently using available resources. - There are two main ways to create threads in Java: by implementing Runnable or extending Thread. - Shared memory access between threads must be synchronized to avoid race conditions.

Uploaded by

Cosmin Grosu
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
59 views

w13 Concurrency Curs

This document discusses Java concurrency and threads. It covers key topics such as: - Threads allow multi-threaded software to run parts concurrently using available resources. - There are two main ways to create threads in Java: by implementing Runnable or extending Thread. - Shared memory access between threads must be synchronized to avoid race conditions.

Uploaded by

Cosmin Grosu
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 43

JAVA Development

Concurrency
Threads
Threads

• A multi-threaded software contains two or more parts that can run concurrently and each
part can handle a different task at the same time making optimal use of available resources
• Especially useful when your computer has multiple CPUs (4-8-16..)
Process vs Thread

Process:
- started by OS (when launching an application..)
- typically independent, each has separate memory space
- has considerably more state info (open files, etc)
- interact only through system IPC (expensive, limited)
- hard/expensive to create new one (duplicate parent)

Thread:
- subset of a process (cannot exist without), at least one
- multiple threads of a process share process state, some
shared memory, etc
- each also has its own address space (stack)
- can easily communicate / interact with other threads
- smaller memory overhead and context-switching cost
- easy to create (from other threads)
Java threads

- A Java application is started by running “java.exe” with some params, like main class...

- This creates a java process, which:


- has at least one main thread - auto started, runs .main() method of the main class
- typically has many other JVM-specific threads, like for garbage collector..
- can start and control multiple other threads (as needed)
- ends when all (non-daemon) threads are finished (not just the main one!)

- Java threads correspond to OS threads, and are represented in code by instances of


java.lang.Thread class
Thread class

- Any executing java code has a current thread is running on (by), represented by an
instance of the Thread class

- Thread class allows to:


- get thread properties (like: name, id, priority, is daemon..), and also set some these
properties (name, priority,...)
- get the current execution state of the thred
- call operations which affect this state: start(), sleep(), interrupt(), yield()...

- Getting access to a Thread instance:


- from inside the code executed by thread: by static method Thread.currentThread()
- from outside a thread - returned by constructor call when built: new Thread()
Thread states
Creating threads

2 ways to create a new thread:

- implement the Runnable interface

- extending the Thread class


Creating threads - by implementing Runnable

Steps:

• Create a class implementing Runnable interface, and put all your business logic
code in the run() method (required by the interface)

• Create an instance of the Thread class using the constructor:


Thread(Runnable runnable), passing as runnable an instance of your custom
runnable class

• Start the new thread by calling its start() method - this will start running the run()
method of your runnable in a new execution thread
Creating threads – by implementing Runnable (1)

class CustomRunnable implements Runnable {


private String name;

CustomRunnable(String name) { this.name = name;}

@Override
public void run() {
System.out.println(name + ": Start running...");
try {
for (int i = 1; i <= 3; i++) {
System.out.println(name + ": " + i);
Thread.sleep(50);
}
System.out.println(name + ": ...finished");
} catch (InterruptedException e) {
System.out.println(name + ": thread interrupted");
}
}
}
Creating threads – by implementing Runnable (2)

public class RunnableDemo {


public static void main(String[] args) {

//create instances of our custom runnable class


Runnable r1 = new CustomRunnable("Runnable-1");
Runnable r2 = new CustomRunnable("Runnable-2");

//create instances of Thread to run each runnable


Thread t1 = new Thread(r1, "thread-for-r1");
Thread t2 = new Thread(r2, "thread-for-r2");

//start the threads


t1.start();
t2.start();
}
}
Creating threads – by extending Thread class

Steps:

• Create a custom class extending the Thread class, and override its run() method,
putting all your business logic there

• Create an instance of this custom Thread class and start it by calling its start()
method, which will run the run() method in a new execution thread

Note: you must call the start() method on the thread instance to start a new thread;
avoid the common mistake of calling directly the run() method, as this will just run
that code in the current thread! (not a new one)
Creating threads – by extending Thread class (1)

class CustomThread extends Thread {

CustomThread(String name) { super(name); } //pass name to super constructor

@Override
public void run() {
System.out.println(getName() + ": Running thread...");
try {
for (int i = 1; i <= 3; i++) {
System.out.println(getName() + ": " + i);
Thread.sleep(50);
}
System.out.println(getName() + ": ...finished");
} catch (InterruptedException e) {
System.out.println(getName() + ": Thread interrupted");
}
}
}
Creating threads – by extending Thread class (2)

public class ThreadDemo {


public static void main(String[] args) {

//create instances of our custom thread class


Thread t1 = new CustomThread("thread-1");
Thread t2 = new CustomThread("thread-2");

//start them
t1.start();
t2.start();
}
}
Concurrent threads

- code of a thread may create and then start other new threads,
with “new Thread(..)” and then calling .start() on them

- this is an async operation - the call to start() method of other


thread returns immediately and the first thread moves on,
independent of how/when the code of new thread is actually
executed (in parallel)
- after the start moment, threads execute independently

- but a thread may still send ‘signals’ to other running threads,


by calling methods like .interrupt(), notify(), or by releasing
locked resources waited by them
- a thread may also choose to pause and wait for another thread
to finish, by calling .join() method on that thread
Concurrency
Issues
Concurrency = problems?

• No problems running multiple threads concurrently (as they are normally independent,
have own memory space, own slice of time on CPU) ...

• … until they start accessing shared resources (read+write memory/objects, files, etc)
○ they start to interfere and produce unpredictable results
Example:
- multiple threads trying to write to the same file may corrupt the data because one of the
threads can override data written by the other
- multiple threads updating same variable may overwrite each other’s changes

• “critical section” = a section of code where multiple threads access some shared resources, in an
unsafe way so that the results are unpredictable / depend on the timing of each thread
○ “race condition” - name of such a situation of threads ‘racing’ to access same resources
http://tutorials.jenkov.com/java-concurrency/race-conditions-and-critical-sections.html
Shared memory

Java memory model - 2 areas of memory:


- thread stack -> separate pe each thread, holds
local variables (primitives, references to objects),
info about current call stack, etc
- heap -> hold instances of Objects (not primitives),
shared between all threads
- Each thread starts with it’s own separate stack
- Any objects (not primitives) they create in their code
will still be placed on shared heap!
- Such objects are still safe to use, as long as no
reference to them is passed to other threads!
- How can a reference be shared between threads?
- store it in a static member of a class
- pass it to each thread’s constructor (at creation)
Fixing concurrency?

• Some possible solutions:


○ just avoid sharing - independent threads, no shared resources at all!
○ have only read-only / immutable shared resources (safer)
○ use some synchronisation mechanism - to ensure a thread has exclusive
access to some critical section of code (avoiding interference from others)
■ keywords: monitors, synchronized blocks
■ usually blocking - ensures safety, but leads to slower code, and a new
set of possible problems (thread starvation, deadlocking,..)
○ use non-blocking algorithms / data structures, which expect and
detect/correctly handle interference from other threads (faster, no locking
problems, but quite complex / hard to get right)
Thread synchronization

How it works:
• each Java object is associated with a monitor, which a thread can lock/unlock
• only one thread at a time may hold a lock on a specific monitor!

synchronized
- marks that a block of code is protected by a lock / can be accessed by only one thread at a time!
- other threads which want to access a block (same or other) synchronized on same lock will
be blocked until first thread exists the synchronized block (and releases the lock)
- can be applied to:
- methods - instance or static -> the lock object is implicit, either “this” (for instance methods)
or the class definition itself (for static)
- blocks of code - instance or static -> the lock object needs to be specified explicitly

synchronized (objectForLock) { //locks on the monitor of objectForLock


//access shared variables or other resources
}
Thread synchronization – Example 1

class MyLogger {

//synchronized method (static, so uses the class itself to lock)


//ensure that line1,line2 are always written together (no other line between them)
static synchronized void log_syncMethod(String line1, String line2) {
System.out.println(line1);
System.out.println(line2);
}

//equivalent method, using a synchronized block (with explicit lock on class)


static void log_withSyncBlock(String line1, String line2) {
synchronized (MyLogger.class) {
System.out.println(line1);
System.out.println(line2);
}
}
}
Thread synchronization – Example 2 (1)

class Counter {
long count = 0;

//synchronized, to avoid problems with ‘+=’ operation


//when count value is updated by multiple concurrent threads
public synchronized void add(long value) {
count += value;
}
}
Thread synchronization – Example 2 (2)

class CounterThread extends Thread {


private Counter counter;

public CounterThread(Counter counter) { this.counter = counter; }

@Override
public void run() {
for (int i = 0; i < 100; i++) {
counter.add(1);
}
}
}
Deadlocks

Happens when:
- different threads want to lock multiple resources at
the same time
- if some of them obtain only some of the locks, but
others obtain the rest, they may end in a deadlock -
none can proceed, none gives up already take
locks, so they all remain blocked forever

Solutions to avoid it:


- force all threads to take the locks to multiple
resources in the same fixed order!
- ex: T1 took L1, wants to take L2; but T2 is only at the
step of taking L1, so it needs for T1 to finish with it,
before taking L2 (to respect the fixed order L1-L2)

- use more advanced locks with timeout support


- ex: T1 took L1, waits for L2 (taken by T2) for some
time, but then gives up and releases L1 too, and will
retry later to take L1+L2..
Atomic Updates
Atomic Updates

Sharing data between multiple threads is hard:

static int counter = 0;

public static void main(String[] args) throws InterruptedException {


for (int thread = 0; thread < 100; thread++)
new Thread(() ->
IntStream.range(0, 10000)
.forEach(iter -> counter += 1))
.start();

Thread.sleep(1000);

System.out.println(counter); //what will this display?


}
Atomic Updates

First run: 347519


Second run: 786762
Third run: 579058

Turns out, incrementing a variable is a compound operation (read-and-write), so


it’s not an atomic operation:
● multiple threads read the old value (in parallel)
● increments are “missing” (threads miss the updates from other threads)

- atomic operation = an operation which cannot be further divided / is seen by


other threads either as fully executed or not executed (never partially executed)
Atomic Updates

Locks (like with synchronized) are one solution:

static int counter = 0;

for (int thread = 0; thread < 100; thread++)


new Thread(() ->
IntStream.range(0, 10000)
.forEach(iter -> {
synchronized (CountDemo.class) {
counter += 1;
}
}))
.start();

Atomic Updates

Atomic variables are faster, easier to work with, less risk of “forgetting” to lock

AtomicInteger, AtomicLong, AtomicBoolean, etc.

Have compound methods that are execute atomically:


● incrementAndGet
● addAndGet
● compareAndSet
● getAndSet
● …

Guaranteed to be thread safe!


Atomic Updates - Example

The same code becomes:

static AtomicInteger atomicCounter = new AtomicInteger(0);

public static void main(String[] args) throws InterruptedException {


for (int thread = 0; thread < 100; thread++)
new Thread(() ->
IntStream.range(0, 10000)
.forEach(iter ->
atomicCounter.incrementAndGet())) //atomic op!
.start();

Thread.sleep(1000);
System.out.println(atomicCounter.get()); //-> 1000000 (as expected)
}
Concurrent Collections
Concurrent Collections

Regular collections are not thread-safe:

List<Integer> list = new ArrayList<>();


for (int t = 0; t < 100; t++) { //start 100 threads
new Thread(() -> {

for (int i = 0; i < 100; i++) //each adds 100 values to same list
list.add(i);

}).start();
}
System.out.println("List size: " + list.size() + ", expected: 10000");

What do you think will the list contain after executing this code?
Concurrent Collections

If you guessed “it will crash”, you were right:

Exception in thread "Thread-3" java.lang.ArrayIndexOutOfBoundsException: 366


at java.util.ArrayList.add(ArrayList.java:463)
...
List size: actual: 9035, expected: 10000

Turns out sometimes we get a wrong result, otherwise it simply crashes.

Thread safety is critical!


Concurrent Collections

Concurrent collections:
● special collections that can be used safely by multiple threads
● they do have a performance impact, but lower than manually using locks
● java.util.concurrent package (see summary here)

Examples:
● ConcurrentHashMap
● CopyOnWriteArrayList
● CopyOnWriteArraySet
Concurrent Collections

Important:
● All methods in concurrent collections are thread safe (individually!)
● But multiple method calls are not atomic!

if(!concurrentMap.containsKey(5)) {
//← another thread may have changed the map exactly here!
concurrentMap.put(5, "somevalue");
}

The block is not atomic: another thread might have added a value at precisely the
time between the call to .containsKey() and the call to .put()
Concurrent Collections

Solution:
look for single operations that combine multiple basic operation in the way
you need them, to run them an atomic way.
- methods like: putIfAbsent(), computeIfPresent(),...

concurrentMap.putIfAbsent(5, "somevalue");

But sometimes you just have to use locks...


Concurrent Queues
Concurrent Queues

Thread types, by their logical role:


- Producers - threads which produce some data:
● Downloading HTTP pages
● Reading from a database
● Incoming network requests
- Consumers - threads which process that data

We want to process the produced data in parallel - using multiple consumer threads.
Possible solution:
● Create a thread pool for processing
● Send data from the first set of threads (or thread pool) to the other set of
“worker” threads (pool)
Concurrent Queues

Queues are perfect for this purpose:


● The “producer” thread(s) add elements to the queue
● The “consumer” thread(s) read elements from the queue
Concurrent Queues

java.util.concurrent.ConcurrentLinkedQueue
● Simplest option
● Unbound: cannot control how much the queue can grow
● Usable when the consumers are guaranteed* to be faster than the producers,
otherwise might grow indefinitely!

java.util.concurrent.ArrayBlockingQueue
● Backed by a fixed array
● Once it reaches the maximum size, producers are blocked until some space
becomes available again

*this rarely happens in programming


Concurrent Queues

java.util.concurrent.DelayQueue
● Elements can only be consumed after a certain delay

java.util.concurrent.TransferQueue
● Producers may wait until the element is consumed

java.util.concurrent.PriorityBlockingQueue
● Each element can have a priority
● Highest priority elements are consumed first

More concurrent collections are available in the java.util.concurrent package.


Questions?
Extra reading

● https://beginnersbook.com/2013/03/java-threads/
● https://beginnersbook.com/2015/01/what-is-the-difference-between-a-process-an
d-a-thread-in-java/
● https://www.baeldung.com/java-thread-lifecycle
● http://tutorials.jenkov.com/java-concurrency/index.html
● https://beginnersbook.com/2013/03/multithreading-in-java/
● https://www.ibm.com/developerworks/java/tutorials/j-threads/j-threads.html
● https://dzone.com/articles/producer-consumer-pattern
● https://www.codejava.net/java-core/collections/java-producer-consumer-example
s-using-blockingqueue

You might also like