Sei sulla pagina 1di 17

JAVA MULTITHREADING

INSTRUCTOR JOHN PURCELL SOFTWARE DEV.TRAINER


1. Starting Threads
2. Basic Thread Synchronization
- 2 problems if you have shared data between threads : data being cached
& threads interleaving
- purpose of volatile keyword in Java
- view exercise apps in Eclipse !!
- under some conditions proc1 Thread might decide to cache the value of
running (boolean var) so that it may never see the changed value of it
- proc1 thread is spawning off another thread , the main thread
- proc1.shutdown() is called from the main thread
solution : make running a volatile variable
stackoverflow about volatile keyword :
volatile has semantics for memory visibility. Basically, the value of
a volatile field becomes visible to all readers (other threads in particular) after
a write operation completes on it. Without volatile, readers could see some

388down
vote

non-updated value.
To answer your question: Yes, I use a volatile variable to control whether
some code continues a loop. The loop tests the volatile value and continues if
it is true. The condition can be set to false by calling a "stop" method. The loop
sees false and terminates when it tests the value after the stop method
completes execution.
The book "Java Concurrency in Practice," which I highly recommend, gives a
good explanation of volatile. This book is written by the same person who
wrote the IBM article that is referenced in the question (in fact, he cites his
book at the bottom of that article). My use of volatile is what his article calls
the "pattern 1 status flag."
If you want to learn more about how volatile works under the hood, read up
on the Java memory model. If you want to go beyond that level, check out a
good computer architecture book likeHennessy & Patterson and read about
cache coherence and cache consistency.

accepted

92down
vote
-

the volatile modifier guarantees that any thread that reads a field will see
the most recently written value. - Josh Bloch
If you are thinking about using volatile, read up on the
package java.util.concurrent which deals with atomic behaviour.

volatile is very useful to stop threads.

3. The Synchronized keyword

count++ is not an atomic operation, count = count + 1 , these are 3


operations ! threads are interleaving, accesing count at the same time
sometimes
we could declare count of type AtomicInteger
every object in Java has an intrinsic lock, a mutex
if you have a synchronized method you have to aquire the intrinsic lock
before you can call that
its not always ideal to use the one intrinsic lock of your object because
often you want multiple locks to protect multiple things threads are doing

4. Multiple locks, Using Synchronized Code Blocks


public synchronized void stageOne(){
try {
//simulate going away getting some info from somewhere
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
list1.add(random.nextInt(100));
}
public synchronized void stageTwo(){
try {
//simulate going away getting some info from somewhere
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
list2.add(random.nextInt(100));
}

if one thread aquires the lock for Worker object & enters stageOne() the
other thread will have to wait to aquire lock to be able to run stageTwo()!!!
notice that the 2 methods are operating on different data: stageOne() on
list1, stageTwo() on list2
ideally we want to prohibit the threads to run the same method at the
same time, but let them run different methods (t1 stageOne(), t2
stageTwo()) at the same time
one way : ReentrantLock class
solution : create 2 locks for the 2 methods, use synchronized blocks

dont lock on the lists ! Declare separate lock objects to avoid confusion !

private Object lock1 = new Object();


private Object lock2 = new Object();

public void stageOne(){


synchronized(lock1){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

list1.add(random.nextInt(100));

}
public void stageTwo(){
synchronized(lock2){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
list2.add(random.nextInt(100));
}
}

running time decreases like this, it takes 50% less time (2 ms) as opposed
to the previous synchronized declared methods (4 ms)

public void main() {


System.out.println("Starting....");
long start = System.currentTimeMillis();
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
process();
}
});
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
process();
}
});
t1.start();
t2.start();
try {

t1.join();
t2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

long end = System.currentTimeMillis();


System.out.println("Time taken: " + (end-start));
System.out.println("List1: " + list1.size());
System.out.println("List2: " + list2.size());

5. Thread Pools
- a thread pool = having a number of workers in a factory and you have a
load of tasks you want them to get through(ex. 5 tasks)
- when worker thread finishes processing a task, we want it to start
processing another

ExecutorService executor = Executors.newFixedThreadPool(2); // 2


worker threads

ExecutorService runs its own managerial thread that manages the worker
threads
submit to executorService the tasks you want handled by the worker
threads:
Processor class describes a task (by implementing Runnable)

//Runnable is a task/job
class Processor implements Runnable{
private int id;
public Processor(int id){
this.id = id ;
}
public void run() {
System.out.println("Starting: " + id);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Finished: " + id);
}
}

each task has an id


-

advantage of ThreadPool : theres a lot of overhead in starting threads &


by recycling the threads in this ThreadPool you avoid that overhead

public class App {


public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2); // 2
worker threads
for(int i = 0 ; i < 5; i++){
executor.submit(new Processor(i));
}
executor.shutdown();// returns immediately
System.out.println("All tasks submitted.");
//wait for tasks to actually complete :
try {
//wait for 1 day
executor.awaitTermination(1, TimeUnit.DAYS);

}
}

} catch (InterruptedException e) {
}
System.out.println("All tasks completed.");

output :

All tasks
Starting:
Starting:
Finished:
Finished:
Starting:
Starting:
Finished:
Finished:
Starting:
Finished:
All tasks

submitted.
0
1
0
1
2
3
3
2
4
4
completed.

6. Countdown Latches
- there are fortunately many classes in Java that are threadsafe, one of
them is a CountDownLatch, basically a counter that counts down and can
be decremented in different places in the code .
- code :
class Processor implements Runnable{
private CountDownLatch latch;
public Processor(CountDownLatch latch){
this.latch = latch;
}
public void run() {
System.out.println("Started.");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();//latch counts down
}
}
public class App {
public static void main(String[] args) {
//one or more threads wait for the latch to count down from 5 time
units
CountDownLatch latch = new CountDownLatch(5);
ExecutorService executor = Executors.newFixedThreadPool(7);
//7 worker threads
for(int i = 0 ; i < 5 ; i++)

executor.submit(new Processor(latch)); //submit 5 tasks to


executor
try {

latch.await(); // it waits until the CountDownLatch has


counted down to 0
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Completed.");

}
output : Started.
Started.
Started.
Started.
Started.
Completed.

7. Producer-Consumer
-

using thread-safe constructs from the concurrent package :

private BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(10);


- producer is putting items on the queue as fast as it can
- consumer is taking items in a irregular way : sleeping 100 ms and
only in 1/10th of the times taking an item
public class App {
//data structure which can hold data items the type of which u can
choose
//you can add & remove items from it -> FIFO
private static BlockingQueue<Integer> queue = new
ArrayBlockingQueue<Integer>(10);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
try {
producer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
try {
consumer();
} catch (InterruptedException e) {

e.printStackTrace();

}
});
t1.start();
t2.start();
t1.join();
t2.join();
}
private static void producer() throws InterruptedException{
Random random = new Random();
while(true){
//suppose the queue is full, put() method
//will patiently wait until items are removed from the queue
queue.put(random.nextInt(100));
}
}
private static void consumer() throws InterruptedException {
Random random = new Random();
while(true){
Thread.sleep(100);//takes time to process item
if(random.nextInt(10) == 0){
Integer value = queue.take(); // if there's nothing in
the queue it waits without consuming a lot
// of resources

}
}

System.out.println("Taken value: " + value


+ "; Queue size is: " + queue.size());

8. Wait and Notify


public class Processor {
public void produce() throws InterruptedException{
synchronized (this) {
System.out.println("Producer thread running ...");
wait();// very resource efficient : waits without consuming
resources
// it hands over the lock
//it can only be called from inside an synchronized block
System.out.println("Producer resumed.");//this will not be
resumed
//until notify is called on same lock
}
}

public void consume() throws InterruptedException{


Scanner scanner = new Scanner(System.in);
Thread.sleep(2000); //sleep 2 sec. so that produce() & consume()
don't go off at same time
synchronized (this) {
System.out.println("Waiting for return/enter key.");
scanner.nextLine();
System.out.println("Return key pressed.");
notify(); //can only be called within a sync block
//notify will notify one other thread, the first of threads to
lock on the objects
//notify to wake up if it is waiting
//it does not relinquish control of the lock
//otherwise the other thread
//notifyALL() notifies all threads waiting for lock
//it's more efficient to call notify() if you're waiting for a
single thread
//notify does not hand over control , so the code after notify
will continue executing
//hence the sleep for 5 ms before control goes over to producer
Thread.sleep(5000);
}
}

public class App {


public static void main(String[] args) throws InterruptedException{
final Processor proc = new Processor();
Thread t1 = new Thread(new Runnable(){
public void run() {
try {
proc.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(new Runnable(){
public void run() {
try {
proc.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

});
t1.start();
t2.start();

t1.join();
t2.join();
}

9. A worked example using Low-Level Synchronization


import java.util.LinkedList;
import java.util.Random;
public class Processor {
private LinkedList<Integer> list = new LinkedList<Integer>();
private final int LIMIT = 10;
private Object lock = new Object(); // object to lock on
//to emphasize that wait & notify need to be called on an
object to lock on
public void produce() throws InterruptedException {
int value = 0;
while(true){
synchronized (lock) {
while(list.size() == LIMIT){
lock.wait();
}
list.add(value++);
lock.notify();// after adding an int in the
list wake up consumer thread!
}
}
}
public void consume() throws InterruptedException {
Random random = new Random();
while(true){
synchronized (lock) { //when control is handed over
to consumer thread
//this removes/consumes an item from the list
while(list.size() == 0){
lock.wait();
}
System.out.println("List size is: " +
list.size());
int value = list.removeFirst();
System.out.println("; value is: " + value);
lock.notify(); //awaken producer thread
}
Thread.sleep(random.nextInt(1000));
}
}

10.
import
import
import
import

Re-entrant Locks
java.util.Scanner;
java.util.concurrent.locks.Condition;
java.util.concurrent.locks.Lock;
java.util.concurrent.locks.ReentrantLock;

public class Runner {


private int count = 0 ;
private Lock lock = new ReentrantLock();
//reentrant lock = once a thread has aquired this lock & locked it
//it can lock it again if it wants to
//lock keeps count of no. of times it has been locked
//it must be unlocked the same no. of times
//every object in Java can be notified & can wait because it
inherits from Object
//with ReentrantLock class the wait method = await()
//you can only call wait or notify method on the object, if you have the
lock of the object
private Condition cond = lock.newCondition();

private void increment(){


for(int i = 0 ; i < 10000 ; i++) {
count++;
}
}
public void firstThread() throws InterruptedException {
lock.lock();
System.out.println("Waiting......");
cond.await();//wait
System.out.println("Woken up!");
try{

increment();
}finally {// in case code throws an exception lock shouldn't
remain locked
lock.unlock();

}
public void secondThread() throws InterruptedException {
Thread.sleep(1000);
lock.lock();

System.out.println("Press return/enter key");


new Scanner(System.in).nextLine();
System.out.println("Got return key!");
cond.signal();//notify
//unlock after you call cond.signal() else awoken
thread can't get lock
try{

increment();
}finally {
lock.unlock();
}

public void finished(){


System.out.println("Count is: " + count);
}

11.

Deadlock

public class Account {


private int balance = 10000;
public void deposit(int amount) {
balance += amount;
}
public void withdraw(int amount) {
balance -= amount;
}
public int getBalance() {
return balance;
}
public static void transfer(Account acc1, Account acc2, int amount) {
acc1.withdraw(amount);
acc2.deposit(amount);
}
}

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Runner {
private Account acc1 = new Account();
private Account acc2 = new Account();
private Lock lock1 = new ReentrantLock();
private Lock lock2 = new ReentrantLock();

// acquireLocks() job is to acquire first & second lock in such a


way //that deadlock cannot occur !!!
private void acquireLocks(Lock firstLock, Lock secondLock)
throws InterruptedException {
while (true) {
// Acquire locks
boolean gotFirstLock = false;
boolean gotSecondLock = false;
try {

gotFirstLock = firstLock.tryLock();
// Acquires the lock if it is not held by another
//thread and
// returns immediately with the value true, setting //
the lock
// hold count to one.
gotSecondLock = secondLock.tryLock();
} finally {
if (gotFirstLock && gotSecondLock) {
// if both locks can be acquired
return; // return from while(true) & //
//acquireLocks method,our goal was accomplished!
}
if (gotFirstLock) {
// if just the first lock acquired with
// tryLock() earlier
firstLock.unlock(); // unlock it to give other
threads the chance to lock it
// meanwhile this while(true) runs until
//both //locks can be acquired!!!
}
if (gotSecondLock) {// if just the second lock was
//acquired with tryLock() earlier
secondLock.unlock(); // unlock it to give other
threads the chance to lock it
// meanwhile this while(true) runs until
both //locks can be acquired!!!
}

}
// Locks not acquired so sleep for 1 ms
Thread.sleep(1);
}

public void firstThread() throws InterruptedException {


Random random = new Random();
for (int i = 0; i < 10000; i++) {
/*
* lock1.lock(); // lock first account for upcoming transfer
* lock2.lock(); // lock 2nd account for upcoming transfer
*/

acquireLocks(lock1, lock2);
try {

Account.transfer(acc1, acc2, random.nextInt(100));


} finally { // if transfer methods were to throw an

exception we

}
}

// should unlock accounts


lock1.unlock();
lock2.unlock();

public void secondThread() throws InterruptedException {


Random random = new Random();
for (int i = 0; i < 10000; i++) {
// locking in reverse order -> perfect recipe for deadlock
// first thread at some point acquires lock 1 and now second
thread
// acquires lock 2
// next: first thread will wait to acquire lock 2 and the
2nd thread
// will wait to acquire lock 1
// -----> DEADLOCK, program is blocked
/*
* lock2.lock();
* lock1.lock();
*/
// deadlock can occure when you lock your locks in different
order
// deadlock can not only occur with ReentrantLock but also
with
// nested synchronized blocks
// not just directly nested, also indirectly : the first
// synchronized block contains a method, that
// calls a method that has a synchronized block
acquireLocks(lock2, lock1);
try {

Account.transfer(acc2, acc1, random.nextInt(100));


} finally {
lock2.unlock();
lock1.unlock();
}
}

public void finished() {


System.out.println("Account 1 balance: " + acc1.getBalance());
System.out.println("Account 2 balance: " + acc2.getBalance());
System.out.println("Total balance: "
+ (acc1.getBalance() + acc2.getBalance()));
}

12.
-

Semaphores
A counting semaphore. Conceptually, a semaphore maintains a set of
permits.
Each acquire() blocks if necessary until a permit is available, and then
takes it.
Each release() adds a permit, potentially releasing a blocking acquirer.
However, no actual permit objects are used; the Semaphore just keeps a
count of the number available and acts accordingly.
Semaphores are often used to restrict the number of threads than can
access some (physical or logical) resource.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class App {

//
//
//
//
//

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


//semaphores control access to some resource
Semaphore sem = new Semaphore(1);
sem.release();//increments count of permits
sem.acquire();//decrements count of permits
System.out.println("Available permits: " +
sem.availablePermits());
//a cached thread pool is a thread pool that creates a new thread
when submitting a new task
//or try to reuse a thread that has been idle
ExecutorService executor = Executors.newCachedThreadPool();
for(int i = 0 ; i < 200 ; i++){
executor.submit(new Runnable(){
public void run() {
//running this without Semaphore in Connection
-> immediately get 200 connections
Connection.getInstance().connect();
}
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.DAYS);
}

}
import java.util.concurrent.Semaphore;
public class Connection {

//this is a Singleton
private static Connection instance = new Connection();
//limit no. of connections any given time
//true param : to ensure fairness, you don;t want it to let a thread
hang in the
//background while other threads are being serviced
//fairness: which ever thread calls aquire first, gets a permit first
//(false as param.-> performance benefits)
Semaphore sem = new Semaphore(10, true);
private int connections = 0;
private Connection() {
}
//there's only one connection at a given time
public static Connection getInstance() {
return instance;
}
// if Thread.sleep were to throw an exception sem.release() would never
be calles
//renamed connect() to doConnect and this new connect() handles the
prev. named possibility
public void connect(){
try {
sem.acquire();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try{

doConnect();
}
finally{
sem.release();
}

public void doConnect() {


//increment connections when entering method
synchronized (this) {
connections++;
System.out.println("Current connections: " + connections);
}
try {
Thread.sleep(2000); //simulate doing some work with
Thread.sleep
} catch (InterruptedException e) {
e.printStackTrace();
}
//decrement connections before exiting the method
synchronized (this) {
connections--;

13.
-

Callable & Future


interface Callable<>
A task that returns a result and may throw an exception.
Implementors define a single method with no arguments called call.
The Callable interface is similar to Runnable, in that both are designed for
classes whose instances are potentially executed by another thread.
- a Runnable, however, does not return a result and cannot throw a checked
exception.
- the Executors class contains utility methods to convert from other
common forms to Callable classes.
- Future has a method that allows you to interrupt a thread : cancel() and
isDone() method that tells you whether your thread is done or not
- suppose you want to use some methods of Future without wanting to
return a result
specify

14.

Future<?> future = executor.submit(new Callable<Void>() {


@Override
public Void call() throws Exception {
...
return null;
}
});

Void with big V because its suppose to be a wrapper-type !


dont forget returning null
Interrupting Threads

import java.util.Random;
public class App {
public static void main(String[] args) throws InterruptedException {
System.out.println("Starting.");
Thread t1 = new Thread(new Runnable(){
public void run() {
Random ran = new Random();
for(int i = 0 ; i < 1E8; i++){ //1E8 = 1 * 10^8
//to check if your thread has been interrupted
/*
if (Thread.currentThread().isInterrupted()){
System.out.println("Interrupted!");
break;
}
*/

try{

Thread.sleep(1);
} catch(InterruptedException e){
System.out.println("Interrupted!");
break;
}
}

Math.sin(ran.nextDouble());

}});
t1.start();
Thread.sleep(500);
//doesn't stop thread, sets a flag telling thread that
it is being interrupted
t1.interrupt();
//if you want to know if thread has been interrupted
you have to quiz the thread
t1.join();
System.out.println("Finished.");
}
}

15.
-

Multithreading in Swing with SwingWorker


SwingWorker<Void, Void>
If you're doing Swing (GUI) programming in Java, you might want to
consider using the SwingWorker class for your multithreading needs.
SwingWorker is a ferocious-looking but useful class that's perfect for
running stuff in the background while simultaneously updating progress
bars and that sort of thing

Potrebbero piacerti anche