PP Unit V
PP Unit V
Application Thread:
When we execute an application:
The JVM creates a Thread object whose task is defined by the main() method
It starts the thread
The thread executes the statements of the program one by one until the
method returns and the thread dies
Multiple Threads in an Application:
Each thread has its private run-time stack
If two threads execute the same method, each will have its own copy of the local variables the
methods uses
However, all threads see the same dynamic memory (heap)
Two different threads can act on the same object and same static fields concurrently
Java Threads:
Implementation is up to the JVM:
it's not defined whether a Java thread is mapped to a something else at the OS
level (a native thread or process), or whether the JVM does timesharing itself.
Every Java program is a threaded program!
garbage collection runs as a thread.
Threads
Threads enable a single program to run multiple parts of itself at the same time. For
example, one part of a program can display an animation on the screen while another part
builds the next animation to be displayed.
Threads are a lightweight version of a process. Threads are similar to processes in that
they can be executed independently and simultaneously, but are different in that they do not have
all the overhead that a process does.
Threads do not make copies of the entire parent process. Instead, only the code needed is
run in parallel. This means that threads can be started quickly because they don't have all of the
overhead of a complete process. They do, however, have complete access to all data of the parent
process.
Threads can read and/or write data that any other thread can access. This makes
interthread communication simpler, but can lead to multiple threads modifying data in an
unpredictable manner.
Uses of Threads
Threads are useful programming tools for two main reasons.
They enable programs to do multiple things at one time. This is useful for such
activities as letting a user do something while something else happens in the background.
Threads enable the programmer to concentrate on program functionality without
worrying about the implementation of multitasking schemes. Declaring Threads
Creating Threads:
There are two ways to create our own Thread object.
Subclassing the Thread class and instantiating a new object of that class
Implementing the Runnable interface.
In both cases the run() method should be implemented.
To Create a thread:
Instantiate an object with new on the class you defined.
Invoke start() on the thread object.
This initializes the thread and invokes its run() method.
Could exit using the return command.
Could be an infinite loop.
Will run until the entire program stops.
Java will occasionally switch to another thread.
Should use a sleep() or something to keep it from hogging the CPU.
Can still be preempted without sleep(),but the whole system will be less
responsive.
Creating a thread:
Create a java.lang.Thread object.
creates a new thread, but doesn't tell the thread what to do.
In other languages we tell a new thread what method to run - in Java we can't do this (no
pointers to methods!).
In Java we need to tell the new thread what Object to run - the Object
must implement the Runnable interface.
Extending Thread:
public class ThreadExample extends Thread {
public void run () {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread: + i);
}
}
}
Thread Properties:
void start()
Creates a new thread and makes it runnable
323
for (int i = 1; i <= 100; i++) {
System.out.println (Runnable: + i);
}
}
}
A Runnable Object:
The Thread objects run() method calls the Runnable objects run() method
Allows threads to run inside any object, regardless of inheritance
Thread Lifetime:
A thread is done when:
the run() method returns.
uncaught exception occurs.
someone calls the threads stop() method (which is deprecated and shouldn't be used!).
A program is done when all (non-daemon) threads have finished.
Example:
public class PrintThread1 extends Thread {
String name;
public PrintThread1(String name) {
this.name = name;
}
public void run() {
for (int i=1; i<500 ; i++) {
try {
sleep((long)(Math.random() * 100));
} catch (InterruptedException ie) { }
System.out.print(name);
}}
public static void main(String args[]) {
PrintThread1 a = new PrintThread1("*");
PrintThread1 b = new PrintThread1("-");
PrintThread1 c = new PrintThread1("=");
a.start();
b.start();
c.start();
Alive
New Thread Dead Thread
Running
Runnable
new ThreadExample();
run() method returns
while () { }
Blocked
Object.wait()
Thread.sleep()
blocking IO call
waiting on a monitor
thread.start();
}
}
INTERRUPTING THREADS
To create classes that make use of threads, you can extend the class Thread or implement
the interface Runnable. Both yield the same result. By implementing Runnable, existing classes
can be converted to threads without having to change the classes on which they are based.
publicclassMyMain{
publicstaticvoidmain(Stringargs[]){
CntThreadcntThread;//declarethread
cntThread=newCntThread();//createthread
cntThread.start();//startthreadrunning
try{System.in.read();}//waitforkeyboardinput
catch(java.io.IOExceptione){}
cntThread.stop();//stopthread
}
}
classCntThreadextendsThread{
publicvoidrun(){
intix=0;
while(true){
System.out.println("running,ix="+ix++);//writecountto
screen
try{Thread.sleep(1000);}//sleep1second
catch(InterruptedExceptione){}
}
}
}
In this example, a thread is created that will write an incrementing counter to the screen. It will
continue to count until the mainroutine receives a character from the keyboard, at which time
the counting thread stops. This means you can press any key on the keyboard followed by the
Enter key or press only the Enter key to stop the thread from counting.
327
loop = true;
while (loop == true) {
parent.ix++; //incremen t counter
parent.repaint(); //repaint screen
try {Thread.sleep(1000);} //sleep 1 second
catch(InterruptedException e) {}
}
}
}
Instances of threads are created using the standard newkeyword. Arguments to newcan
either use the Threadclass explicitly, as in
mainThread=newThread(this);
In the first example, the current instance, this, of an object is initialized as a thread. Any
object, however, can be passed as an argument to the Threadclass.
Note that the Thread class has few constructors. If additional constructor types are
needed, creating a subclass of Threadwith the needed constructors is quite useful. The second
thread creation example allows for these possibilities.
Destroying a Thread
The execution of a thread can be controlled in several ways using the stop, start, and
destroy methods. The object remains in existence as long as the object is referenced somewhere,
even if stopis invoked.
It is not necessary to explicitly destroy the thread object. Java's garbage collector takes care
of this detail. If it is necessary to give the garbage-collection process a helping hand, make sure
all references are removed to the thread object.
The simplest way to do this is to assign the value nullto all variables containing thread
references, as in the following example:
Thread myThread = new Thread();
myThread.start();
myThread.stop();
myThread = null;
In this example, the thread object is instantiated with new. The thread is then started and
stopped. Finally, null is assigned to the variable containing the thread instance. This last step
ensures that Java's garbage collector will schedule a resource deallocation for that object.
Named Threads
Java provides a means for assigning names to threads. Names can consist of any valid Java
string. Naming threads makes it convenient to distinguish one thread from another and enables
a parent process to query a thread for its name. A thread can be assigned a name at creation
time or at any point thereafter. Threads can also be renamed at any time.
To assign a thread a name at creation time, simply use a Threadconstructor that accepts a
string as an additional argument, like this:
Thread myThread = new Thread(this."My first named thread");
This example prints out the name assigned to the thread object myThread. Using the previous
example, this statement would print the following to the screen:
ThenameofthisthreadisMyfirstnamedthread
A thread can also query for its own name in the same way:
System.out.println("Mynameis"+this.getName());
This example changes the name of the thread from "Myfirstnamedthread"to "Mynewly
renamedfirstthread".
Synchronization of Threads
Multiple threads can access the same object or method because threads are not
independent processes with complete copies of data objects. However, there is no guarantee which
thread will access an object at a given time, which can lead to unpredictable results. In situations
when this is not acceptable, use the Java synchronization logic. The keyword that provides this
logic is synchronized.
Synchronization
Threads communicate primarily by sharing access to fields and the objects reference fields
refer to. This form of communication is extremely efficient, but makes two kinds of errors
possible: thread interference and memory consistency errors. The tool needed to prevent these
errors is synchronization.
Thread Interference describes how errors are introduced when multiple threads access
shared data.
Memory Consistency Errors describes errors that result from inconsistent views of
shared memory.
Synchronized Methods describes a simple idiom that can effectively prevent thread
interference and memory consistency errors.
Implicit Locks and Synchronization describes a more general synchronization idiom,
and describes how synchronization is based on implicit locks.
Atomic Access talks about the general idea of operations that can't be interfered with by
other threads.
Thread Interference
Consider a simple class called Counter
classCounter{
privateintc=0;
publicvoidincrement(){
c++;
}
publicvoiddecrement(){
c;
}
publicintvalue(){
returnc;
}
}
Counteris designed so that each invocation of incrementwill add 1 to c, and each invocation
The counterfield is shared between two threads, A and B. Suppose thread A increments
counter:
counter++;
If the two statements had been executed in the same thread, it would be safe to assume that the
value printed out would be "1". But if the two statements are executed in separate threads, the
value printed out might well be "0", because there's no guarantee that thread A's change to
counterwill be visible to thread B unless the programmer has established a happens-before
relationship between these two statements.
There are several actions that create happens-before relationships. One of them is synchronization,
as we will see in the following sections.
We've already seen two actions that create happens-before relationships.
When a statement invokes Thread.start, every statement that has a happens-before
relationship with that statement also has a happens-before relationship with every
statement executed by the new thread. The effects of the code that led up to the creation
of the new thread are visible to the new thread.
When a thread terminates and causes a Thread.joinin another thread to return, then
all the statements executed by the terminated thread have a happens-before relationship
with all the statements following the successful join. The effects of the code in the
thread are now visible to the thread that performed the join.
For a list of actions that create happens-before relationships, refer to the Summary page of the
java.util.concurrentpackage..
syncronized(index) {
index++;
System.out.println("index = " + index);
}
}
Synchronized Methods
to your constructor. But then other threads can use instancesto access the object before
construction of the object is complete.
Synchronized methods enable a simple strategy for preventing thread interference and memory
consistency errors: if an object is visible to more than one thread, all reads or writes to that
object's variables are done through synchronizedmethods. (An important exception: final
fields, which cannot be modified after the object is constructed, can be safely read through
non-synchronized methods, once the object is constructed) This strategy is effective, but can
present problems with liveness, as we'll see later in this lesson.
Synchronized Statements
Another way to create synchronized code is with synchronized statements. Unlike
synchronized methods, synchronized statements must specify the object that provides the
intrinsic lock:
publicvoidaddName(Stringname){
synchronized(this){
lastName=name;
nameCount++;
}
nameList.add(name);
}
otherwise using the lock associated with this, we create two objects solely to provide locks.
publicclassMsLunch{
privatelongc1=0;
privatelongc2=0;
privateObjectlock1=newObject();
privateObjectlock2=newObject();
publicvoidinc1(){
synchronized(lock1){
c1++;
}
}
publicvoidinc2(){
synchronized(lock2){
c2++;
}
}
}
Use this idiom with extreme care. You must be absolutely sure that it really is safe to interleave
access of the affected fields.
Reentrant Synchronization
Recall that a thread cannot acquire a lock owned by another thread. But a thread can acquire a
lock that it already owns. Allowing a thread to acquire the same lock more than once enables
reentrant synchronization. This describes a situation where synchronized code, directly or
indirectly, invokes a method that also contains synchronized code, and both sets of code use
the same lock. Without reentrant synchronization, synchronized code would have to take many
additional precautions to avoid having a thread cause itself to block.
Atomic Access
In programming, an atomic action is one that effectively happens all at once. An atomic action
cannot stop in the middle: it either happens completely, or it doesn't happen at all. No side
effects of an atomic action are visible until the action is complete.
We have already seen that an increment expression, such as c++, does not describe an atomic
action. Even very simple expressions can define complex actions that can decompose into
other actions. However, there are actions you can specify that are atomic:
Reads and writes are atomic for reference variables and for most primitive variables (all
types except longand double).
Reads and writes are atomic for all variables declared volatile(including longand
doublevariables).
Atomic actions cannot be interleaved, so they can be used without fear of thread interference.
However, this does not eliminate all need to synchronize atomic actions, because memory
consistency errors are still possible. Using volatilevariables reduces the risk of memory
consistency errors, because any write to a volatilevariable establishes a happens-before
relationship with subsequent reads of that same variable. This means that changes to a
volatilevariable are always visible to other threads. What's more, it also means that when a
thread reads a volatilevariable, it sees not just the latest change to the volatile, but also the
side effects of the code that led up the change.
Using simple atomic variable access is more efficient than accessing these variables through
synchronized code, but requires more care by the programmer to avoid memory consistency
errors. Whether the extra effort is worthwhile depends on the size and complexity of the
application.
Some of the classes in the java.util.concurrentpackage provide atomic methods that do
not rely on synchronization. We'll discuss them in the section on High Level Concurrency
Objects
Thread-Safe Collections
public static CollectionsynchronizedCollection(Collectionc)
public static ListsynchronizedList(Listl)
public static MapsynchronizedMap(Mapm)
public static SetsynchronizedSet(Sets)
public static SortedMapsynchronizedSortedMap(SortedMapm)
public static SortedSetsynchronizedSortedSet(SortedSets)
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class MainClass{
public static void main(String[]a){
Sets=new HashSet();
s.add("A");
s.add("B");
s.add("C");
s.add("D");
336
s.add("E");
s.add("F");
s.add("H");
Collections.synchronizedSet(s);
}
}
Executors
In all of the previous examples, there's a close connection between the task being done by a
new thread, as defined by its Runnableobject, and the thread itself, as defined by a Thread
object. This works well for small applications, but in large-scale applications, it makes sense to
separate thread management and creation from the rest of the application. Objects that
encapsulate these functions are known as executors. The following subsections describe
executors in detail.
Executor Interfaces define the three executor object types.
Executor Interfaces
The java.util.concurrentpackage defines three executor interfaces:
Executor, a simple interface that supports launching new tasks.
ExecutorService, a subinterface of Executor, which adds features that help manage
the lifecycle, both of the individual tasks and of the executor itself.
ScheduledExecutorService, a subinterface of ExecutorService, supports future
and/or periodic execution of tasks.
Typically, variables that refer to executor objects are declared as one of these three interface
types, not with an executor class type.
with
e.execute(r);
However, the definition of executeis less specific. The low-level idiom creates a new thread
and launches it immediately. Depending on the Executorimplementation, executemay do
the same thing, but is more likely to use an existing worker thread to run r, or to place rin a
queue to wait for a worker thread to become available. (We'll describe worker threads in the
section on Thread Pools.)
The executor implementations in java.util.concurrentare designed to make full use of the
more advanced ExecutorServiceand ScheduledExecutorServiceinterfaces, although they
also work with the base Executorinterface.
Thread Pools
Most of the executor implementations in java.util.concurrentuse thread pools, which
consist of worker threads. This kind of thread exists separately from the Runnableand
Callabletasks it executes and is often used to execute multiple tasks.
Using worker threads minimizes the overhead due to thread creation. Thread objects use a
significant amount of memory, and in a large-scale application, allocating and deallocating
many thread objects creates a significant memory management overhead.
One common type of thread pool is the fixed thread pool. This type of pool always has a
specified number of threads running; if a thread is somehow terminated while it is still in use, it
is automatically replaced with a new thread. Tasks are submitted to the pool via an internal
queue, which holds extra tasks whenever there are more active tasks than threads.
An important advantage of the fixed thread pool is that applications using it degrade
gracefully. To understand this, consider a web server application where each HTTP request is
handled by a separate thread. If the application simply creates a new thread for every new
HTTP request, and the system receives more requests than it can handle immediately, the
application will suddenly stop responding to all requests when the overhead of all those threads
exceed the capacity of the system. With a limit on the number of the threads that can be
created, the application will not be servicing HTTP requests as quickly as they come in, but it
will be servicing them as quickly as the system can sustain.
A simple way to create an executor that uses a fixed thread pool is to invoke the
newFixedThreadPoolfactory method in java.util.concurrent.ExecutorsThis class also
provides the following factory methods:
The newCachedThreadPoolmethod creates an executor with an expandable thread
pool. This executor is suitable for applications that launch many short-lived tasks.
The newSingleThreadExecutormethod creates an executor that executes a single task
at a time.
Several factory methods are ScheduledExecutorServiceversions of the above
executors.
If none of the executors provided by the above factory methods meet your needs, constructing
instances of java.util.concurrent.ThreadPoolExecutoror
java.util.concurrent.ScheduledThreadPoolExecutorwill give you additional options.
Threading
Event
Driven
Programming
Model
Examples:
DECthreads, Windows NT
Process
Lightweight :
Separate Register Set
Separate Stack
Separately Dispatchable
Preemptable
Low resource
consumption
Method
If no interactions, simple
application implementation. If
there are interactions between
threads, then the interactions
require locking and other
mechanisms to prevent subtle
programming errors from
causing failures.
Implementation
Preemption model
Priority model
Debugging
Application suitability
Extremely inexpensive
Creation
No preemption
Implicit synchronization
Extremely inexpensive
Each event is treated as an
independent transaction by the
program. Pre-emption is not
permitted, so there are no locks
or
synchronization required to
access data structures during
the processing
of a single event.
FIFO
Non-preemptable
Featherweight