0% found this document useful (0 votes)
73 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
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)
73 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
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/ 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