Implementarea Concurent, ei în limbaje de
programare
Threads and shared memory — JAVA
Traian Florin S, erbănut, ă
FMI @ UNIBUC
1
Threads and shared memory 1
Concurrency primitives in Java
Thread synchronization mechanism
1
Operating Systems Concepts, 9th edition, by A. Silberschatz, P. B. Galvin, and
G. Gagne
2
Threads and shared memory
Threads
• Most modern applications are multithreaded
• Multiple tasks with the application can be implemented by
separate threads
• Update display
• Fetch data
• Spell checking
• Answer a network request
• Can simplify code, increase responsiveness
3
Example (Service Server Arhitecture)
4
Benefits
• Responsiveness
• may allow continued execution if part of process is blocked
• especially important for user interfaces
• Resource Sharing
• Scalability
• process can take advantage of multiprocessor architectures
5
Concurrency vs parallelism
Concurrent execution on single-core system:
Execut, ie concurentă în paralel pe un sistem multi-core:
6
Single vs Multithreaded process
7
Shared Memory
Benefits
• Efficiency: all threads have direct acecss to the memory
• Simplicity: Accessing shared memory using same model as for
sequential processes
Problems (the need for synchronization)
• Threads may be interruped at any time
• e.g., while performing a data-sensitive operation
• Concurrent access to shared data may result in data
inconsistency
• Maintaining data consistency requires mechanisms to ensure
the orderly execution of cooperating threads
8
Synchronization – Mutex (MUTual EXclusion)
• Special synchronization object
• Protects a “critical section” of code
• (sensitive to concurrent modifications)
• Two zones protected by same mutex cannot interrupt
each-other
Example - mutex m
First Thread Second Thread
do stuff do stuff
synchronized(m): synchronized(m):
output results output results
do other stuff do other stuff
9
Concurrency primitives in Java
Threads
Any executing thread is an instance of class Thread
Thread attributes
• ID: unique thread identifier
• getter: getId, cannot be set
• Name: thread name (String)
• getter/setter: getName, setName
• Priority: thread priority (integer between 1 and 10)
• getter/setter: getPriority, setPriority
• Larger number usually means greater execution priority
• changing priority does not guarantee actual priority
• State: Thread state
• getter: getState, cannot be set
10
Thread state
public static enum Thread.State extends Enum<Thread.State>
• NEW: thread which has not yet started.
• RUNNABLE: thread which can execute/is executing
• BLOCKED: blocked, waiting for a monitor lock
• WAITING: waiting for a signal from another thread
• TIMED_WAITING: waiting thread with a specified waiting time.
• TERMINATED: thread which has finished executing.
A thread can only be in one of these states at any given time.
11
Thread life-cycle (source: HowToDoInJava.com)
Thread states and transitions between them
Figure 1: Thread states and transitions between them
12
Thread creation
Direct
• by deriving Thread class
• by implementing the Runable interface
Abstract
• Using the Executors class
13
Thread creation using Runnable
Standard
public class HelloRunnable implements Runnable {
public void run() { System.out.println("Hello thread!"); }
public static void main(String args[]) {
Thread t = new Thread (new HelloRunnable());
t.start();
} }
Java 8 (funct, ii anonime)
public class HelloThread {
public static void main(String args[]) {
new Thread( () -> System.out.println("Hello thread!")
).start();
} }
14
Definirea unui thread ca subclasa a clasei Thread
public class HelloThread extends Thread {
public void run() {
System.out.println("Hello thread!");
}
public static void main(String args[]) {
new HelloThread().start();
}
}
15
public class Thread implements Runnable
• Dynamic methods (called on a Thread object)
start() A new execution thread is created and the JVM
invokes the run() method
join() waits for the given thread to finish execution
interrupt() asks the given thread to interrupt its execution
boolean isAlive() tests whether the thread is alive
• Static methods (applying to the current thread)
yield() current thread is willing to yield the processor
sleep(long milis) current threads sleeps for given time
Thread currentThread() Returns a reference to the
currently executing thread 16
JVM threads (source: docs.oracle.com)
When a Java Virtual Machine starts up, there is usually a single
non-daemon thread2 , which typically calls the method named main
of some designated class.
The Java Virtual Machine continues to execute threads until either:
• The exit method of class Runtime has been called and the
security manager permits the exit operation to take place.
• All threads that are not daemon threads have died, either
• by returning from the call to the run method or
• by throwing an exception that propagates beyond the run
method.
2
Daemon thread: low priority thread servicing user threads (e.g. garbage
collector thread)
17
Thread.sleep() and InterruptedException
sleep() throws an exception if the thread is interruped while still
sleeping.
public class SleepyMessages {
public static void main(String args[])
throws InterruptedException {
String importantInfo[] =
{ "This", "is", "very", "important"};
for (int i = 0; i < importantInfo.length; i++) {
Thread.sleep(4000);//Pause for 4 seconds
System.out.println(importantInfo[i]);
}
}
}
18
sleep, handling the InterruptedException
public class MessageLoop implements Runnable {
public void run() {
String importantInfo[] =
{"This", "is", "very", "important"};
try {
for (int i = 0; i < importantInfo.length; i++) {
Thread.sleep(4000);//Pause for 4 seconds
threadMessage(importantInfo[i]);
}
} catch (InterruptedException e) {
threadMessage("I wasn't done!");
}
}
19
currentThread, getName
public static void threadMessage(String message) {
String threadName = Thread.currentThread().getName();
System.out.format("%s: %s%n", threadName, message);
}
public static void main(String args[])
throws InterruptedException {
threadMessage("Starting MessageLoop thread");
Thread t = new Thread(new MessageLoop());
t.start();
threadMessage("Waiting for MessageLoop thread to finish");
t.join();
threadMessage("Finally!");
}
}
20
isAlive, join with timeout, and interrupt
public class MessageLoopInterrupted {
public static void main(String args[])
throws InterruptedException {
long patience = 1000 * 10;
long startTime = System.currentTimeMillis();
threadMessage("Starting MessageLoop thread");
Thread t = new Thread(new MessageLoop()); t.start();
threadMessage("Waiting for MessageLoop thread to finish");
while (t.isAlive()) {
threadMessage("Still waiting..."); t.join(2000);
if ((System.currentTimeMillis() - startTime
> patience) && t.isAlive()) {
MessageLoop.threadMessage("Tired of waiting!");
t.interrupt(); t.join();
} }
MessageLoop.threadMessage("Finally!"); } }
21
Rulare MessageLoopInterrupted
main: Starting MessageLoop thread
main: Waiting for MessageLoop thread to finish
main: Still waiting...
main: Still waiting...
Thread-0: This
main: Still waiting...
main: Still waiting...
Thread-0: is
main: Still waiting...
main: Tired of waiting!
Thread-0: I wasn’t done!
main: Finally!
22
ThreadLocal: variables local to the thread
public class ThreadLocalId implements Runnable {
private ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public void run() {
threadLocal.set(Thread.currentThread().getId());
System.out.format("Name: %s Id: %d%n",
Thread.currentThread().getName(), threadLocal.get());
}
public static void main(String[] args) throws InterruptedException {
ThreadLocalId sharedRunnable = new ThreadLocalId();
Thread thread1 = new Thread(sharedRunnable);
Thread thread2 = new Thread(sharedRunnable);
thread1.start(); thread2.start();
thread1.join(); thread2.join();
}
}
23
Thread interference
public class Interference {
static int c = 0;
public static void main(String[] args)
throws InterruptedException {
Thread myThread = new Thread(() -> {
for (int x = 0; x < 5000; ++x) c++;
});
myThread.start();
for (int x = 0; x < 5000; ++x) c--;
myThread.join();
System.out.println("c = " + c);
}
}
24
Thread interference
public class Interference {
static int c = 0;
public static void main(String[] args)
throws InterruptedException {
Thread myThread = new Thread(() -> {
for (int x = 0; x < 5000; ++x) c++;
});
myThread.start();
for (int x = 0; x < 5000; ++x) c--;
myThread.join();
System.out.println("c = " + c);
}
}
++ and -- are not atomic
c = 2343
24
Thread synchronization mechanism
Intrinsic locks
• Every object has an intrinsic lock associated with it
• A thread needing to access an object’s fields should
• acquire the objects intrinsic lock
• access/alter the object’s data
• release the intrinsic lock
• No thread can acquire a lock while another one holds it
• a thread attempting to do so will block in the acquire phase
25
Thread syncronization
Synchronized methods
private synchronized void syncMethod () {
//method body
}
Synchronized code
synchronized (object reference){
// code
}
Synchonized methods are synchronizing the body using this
private void syncMethod () {
synchronized (this){
//method body
}
}
26
When calling a synchronized method
Non-static synchronized methods
• The intrinsic lock of the object is acquired
• The method is executed
• The intrinsic lock of the object is released
• no other non-static, syncronized methods can be called
simultaneously on the same object
Static synchronized methods
• Static methods use the Class object for that class
• No other static synchronized methods belonging to the same
class can be called simultaneously
27
Solving interference by synchronization (on statements)
public class NonInterference {
static int c = 0;
static Object cLock = new Object();
public static void main(String[] args)
throws InterruptedException {
Thread myThread = new Thread(() -> {
for (int x = 0; x < 5000; ++x)
synchronized (cLock) { c++; }
});
myThread.start();
for (int x = 0; x < 5000; ++x)
synchronized (cLock) { c--; }
myThread.join();
System.out.println("c = " + c);
}
}
28
Solving interference through synchronized methods
public class SynchronizedMethod implements Runnable {
private int c = 0;
public static void main(String[] args)
throws InterruptedException {
SynchronizedMethod sm = new SynchronizedMethod();
Thread t1 = new Thread(sm); Thread t2 = new Thread(sm);
t1.start(); t2.start(); t1.join(); t2.join();
System.out.println("c = " + sm.c);
}
@Override public void run() {
for (int x = 0; x < 5000; ++x) incrementC();
}
synchronized void incrementC() { c++; }
}
29
Lock properties
• Only one thread can hold a given lock at any given time
• A thread holds the intrinsic lock of an object if either
• it executes a synchronized method of the object
• it executes a block synchronized by the object
• if the object’s type is Class, and the thead executes a
static synchronized method
30
Warnings
• Access to the non-synchronized methods is not blocked
• Static and non-static synchronized methods do not mutually
exclude each-other
• A thread can re-aquire a lock it is already holding (reentrant
synchronization)
• Thread.sleep() does not release the locks
• ob.wait() releases the intrinsic lock of ob held by the thread
31