Advanced Tools for Multi- Threads Programming Avshalom Elmalech Eliahu Khalastchi 2010
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Introduction By now, you know how to program threads But in a very basic level that matches low- level implementations For higher-level code, we need advanced tools Tools that will hide the threads logic from us Makes it easier for us to control threads Mostly are from java.util.concurrent introduced in java 1.5
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Scheduling Tasks We want an ordered “ping- pong” sequence Half a second apart Does this code meet the demands? public class ThreadTest { private static class Ping implements Runnable{ public void run(){while(true)System.out.println("ping");} } private static class Pong implements Runnable{ public void run(){while(true)System.out.println("pong");} } public static void main(String[] args) { Ping ping=new Ping(); Pong pong=new Pong(); Thread t=new Thread(ping,"thread 1"); Thread t1=new Thread(pong,"thread 2"); t.start(); t1.start(); }
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Scheduling Tasks We already saw a solution using wait() Here is a solution using sleep() public class ThreadTest { private static class Ping implements Runnable{ public void run(){ while(true){ System.out.println("ping"); try {Thread.sleep(1000);} catch (InterruptedException e) {} } private static class Pong implements Runnable{ … public static void main(String[] args) throws InterruptedException { Ping ping=new Ping(); Pong pong=new Pong(); Thread t=new Thread(ping,"thread 1"); Thread t1=new Thread(pong,"thread 2"); t.start(); long time2,time=System.currentTimeMillis(); while((time2=System.currentTimeMillis())-time<500); t1.start(); }} public static void main(String[] args) throws InterruptedException { Ping ping=new Ping(); Pong pong=new Pong(); Thread t=new Thread(ping,"thread 1"); Thread t1=new Thread(pong,"thread 2"); t.start(); long time2,time=System.currentTimeMillis(); while((time2=System.currentTimeMillis())-time<500); t1.start(); }}
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Scheduling Tasks A far more simple way – use a timer! import java.util.Timer; import java.util.TimerTask; public class ThreadTest { private static class Ping extends TimerTask{ public void run(){System.out.println("ping");} } private static class Pong extends TimerTask{ public void run(){System.out.println("pong");} } public static void main(String[] args){ Ping ping=new Ping(); Pong pong=new Pong(); Timer t=new Timer(); t.scheduleAtFixedRate(ping, 0, 1000); t.scheduleAtFixedRate(pong, 500, 1000); } import java.util.Timer; import java.util.TimerTask; public class ThreadTest { private static class Ping extends TimerTask{ public void run(){System.out.println("ping");} } private static class Pong extends TimerTask{ public void run(){System.out.println("pong");} } public static void main(String[] args){ Ping ping=new Ping(); Pong pong=new Pong(); Timer t=new Timer(); t.scheduleAtFixedRate(ping, 0, 1000); t.scheduleAtFixedRate(pong, 500, 1000); } int i; while((i=System.in.read())!=13); ping.cancel(); // canceled task pong.cancel(); // t continues… t.cancel(); // t is cancled int i; while((i=System.in.read())!=13); ping.cancel(); // canceled task pong.cancel(); // t continues… t.cancel(); // t is cancled Canceling tasks:
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Atomic objects Last time we synchronized the update() method, since count++ is an atomic action It took about 46 seconds to complete public class Count { private int count; public void setCount(int x){count=x;} public int getCount(){return count;} synchronized public synchronized void update(){count++;} } public class ThreadTest { private static class CountAdapter implements Runnable{ Count c; public CountAdapter(Count c){ this.c=c; } public void run(){ for(int i=0;i< ;i++) c.update(); } public static void main(String[] args) { Count c=new Count(); c.setCount(0); CountAdapter ca=new CountAdapter(c); Thread t=new Thread(ca); Thread t1=new Thread(ca); long time=System.currentTimeMillis(); t.start(); t1.start(); while(t.isAlive() || t1.isAlive()); System.out.println(c.getCount()); System.out.println((System.currentTimeMillis()-time)/1000); }}
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Atomic objects We use an AtomicInteger instead of int There is no synchronized Took only 6 seconds to complete (with the desired result) import java.util.concurrent.atomic.AtomicInteger; public class Count { private AtomicInteger count = new AtomicInteger(0); public void setCount(int x){count.set(x);} public int getCount(){return count.get();} public void update(){ count.incrementAndGet();// count++ } public class ThreadTest { private static class CountAdapter implements Runnable{ Count c; public CountAdapter(Count c){ this.c=c; } public void run(){ for(int i=0;i< ;i++) c.update(); } public static void main(String[] args) { Count c=new Count(); c.setCount(0); CountAdapter ca=new CountAdapter(c); Thread t=new Thread(ca); Thread t1=new Thread(ca); long time=System.currentTimeMillis(); t.start(); t1.start(); while(t.isAlive() || t1.isAlive()); System.out.println(c.getCount()); System.out.println((System.currentTimeMillis()-time)/1000); }}
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Deadlock Example public class Friend { private String name; public Friend(String name){this.name = name;} public String getName(){return this.name;} public synchronized void sayHA(Friend date) { System.out.format("%s says HA to %s%n",this.name, date.getName()); date.sayDA(this); } public synchronized void sayDA(Friend date) { System.out.format("%s says DA to %s%n",this.name,date.getName()); } public class DeadLock { private static class FriendRun implements Runnable{ private Friend me,date; public FriendRun(Friend m,Friend d){ me=m;date=d;} public void run(){ me.sayHA(date); } } public static void main(String[] args) { Friend alice = new Friend("Alice"); Friend bob = new Friend("Bob"); Thread t=new Thread(new FriendRun(alice,bob)); Thread t1=new Thread(new FriendRun(bob,alice)); t.start(); t1.start(); }} Alice says HA to Bob Bob says HA to Alice Since in dates the topic is usually about “HA and DA” an icebreaker protocol was devised: The first person says “ha” and the other should reply “da” This is an implementation of the protocol. Will it work?
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Deadlock Example public class Friend { private String name; public Friend(String name){this.name = name;} public String getName(){return this.name;} public synchronized void sayHA(Friend date) { System.out.format("%s says HA to %s%n",this.name, date.getName()); date.sayDA(this); } public synchronized void sayDA(Friend date) { System.out.format("%s says DA to %s%n",this.name,date.getName()); } public class DeadLock { private static class FriendRun implements Runnable{ private Friend me,date; public FriendRun(Friend m,Friend d){ me=m;date=d;} public void run(){ me.sayHA(date); } } public static void main(String[] args) { Friend alice = new Friend("Alice"); Friend bob = new Friend("Bob"); Thread t=new Thread(new FriendRun(alice,bob)); Thread t1=new Thread(new FriendRun(bob,alice)); t.start(); t1.start(); }} A B t1 FR AB t AB A.sayHA(B) A is locked by t Start() B.sayHA(A) B is locked by t1 Start() t: B.sayDA(A) Impossible, B is locked by t1 A.sayHa(B) will not finish, A remains locked! t1: A.sayDA(B) Impossible, A is locked by t B.sayHa(A) won’t finish, B remains locked! Neither side has finished saying HA tt1
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Solving deadlocks with locks import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Friend { private String name; protected Lock lock = new ReentrantLock(); public Friend(String name){this.name = name;} public String getName(){return this.name;} public boolean iCanLockBoth(Friend date) { Boolean myLock = false; Boolean yourLock = false; try { myLock=lock.tryLock(); yourLock=date.lock.tryLock(); } finally { if (!(myLock&&yourLock)) { if (myLock) lock.unlock(); if (yourLock) date.lock.unlock(); } return myLock&&yourLock; } iCanLockBoth will return true only if we managed to lock both locks (by the same thread). Only then, will the friend say “ha” and call the other to say “da”, otherwise it will say it couldn’t say ha… public void sayHA(Friend date) { if(iCanLockBoth(date)){ try{ System.out.format("%s says HA to %s%n",this.name,date.getName()); date.sayDA(this); } finally{ lock.unlock(); date.lock.unlock(); } } else System.out.format("%s started to say HA but realized %s already started",name,date.getName()); } Alice says HA to Bob Bob says DA to Alice Bob started to say HA but realized Alice already started
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Executor Interface So far we used threads with Runnable’s public void run() method There was a direct attachment between the task and the thread that runs it… Sometimes we want to decouple these two Controlling the number of threads Scheduling tasks etc Therefore an Executor Interface was created
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Executor Interface It has one method: public void execute(Runnable r) We can implement it as we wish class DirectExecutor implements Executor { public void execute(Runnable r) { r.run(); } class ThreadPerTaskExecutor implements Executor { public void execute(Runnable r) { new Thread(r).start(); }
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Executor Interface We can also use a queue Each execute puts the runnable at the end Only the first n are polled out of the queue They are ran via a thread When a task has finished, another is polled This is called a Thread Pool It is commonly used to control the number of threads, clients etc… Strong and flexible thread pools are already implemented for us
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Thread Pools import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadTest3 { private static void delay(long ms){ try { Thread.sleep(ms);} catch (InterruptedException e) {} } private static class RunnableTask1 implements Runnable{ public void run(){ System.out.println("task1 started"); delay(10000); System.out.println("task1 finished"); } } … public static void main(String[] args) { Executors.newSingleThreadExecutor(); Executor executor = Executors.newSingleThreadExecutor(); executor.execute (new RunnableTask1 ()); executor.execute (new RunnableTask2 ()); executor.execute (new RunnableTask3 ()); ((ExecutorService) executor).shutdown(); } Executors class has a factory of thread pools, a SingleThreadExecutor allows only one thread to be ran at a time What would be the output? task1 started task1 finished task2 started task2 finished task3 started task3 finished
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Thread Pools import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadTest3 { private static void delay(long ms){ try { Thread.sleep(ms);} catch (InterruptedException e) {} } private static class RunnableTask1 implements Runnable{ public void run(){ System.out.println("task1 started"); delay(10000); System.out.println("task1 finished"); } } … public static void main(String[] args) { Executors. newFixedThreadPool (2); Executor executor = Executors. newFixedThreadPool (2); executor.execute (new RunnableTask1 ()); executor.execute (new RunnableTask2 ()); executor.execute (new RunnableTask3 ()); ((ExecutorService) executor).shutdown(); } How about now? task1 started task2 started task1 finished task2 finished task3 started task3 finished
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Thread Pools import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadTest3 { private static void delay(long ms){ try { Thread.sleep(ms);} catch (InterruptedException e) {} } private static class RunnableTask1 implements Runnable{ public void run(){ System.out.println("task1 started"); delay(10000); System.out.println("task1 finished"); } } … public static void main(String[] args) { Executors. newCachedThreadPool(); Executor executor = Executors. newCachedThreadPool(); executor.execute (new RunnableTask1 ()); executor.execute (new RunnableTask2 ()); executor.execute (new RunnableTask3 ()); ((ExecutorService) executor).shutdown(); } How about now? task1 started task2 started task3 started task1 finished task3 finished task2 finished
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Callable Runnabler’s run() method Cannot return a value Cannot throw an exeption A Callable Interface can Interface Callable V call() throws Exception;
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Future Executors can submit a Callable Using the submit(Callable c) method But what should submit return?? c.call() return V, that could be anything… Submit return a special object called Future Future submit(Callable c) V should be the same V of the Callable
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Callable, Future import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class ThreadTest3 { private static void delay(long ms){ try { Thread.sleep(ms);} catch (InterruptedException e) {} } private static class CallableTask implements Callable { Integer i; public CallableTask(int i){ this.i=new Integer(i); } public Integer call() throws Exception { System.out.printf("task%d started\n",i); delay(10000); return i; } public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService executor = Executors.newCachedThreadPool(); Future futures[]=new Future[3]; for(int i=0;i<3;i++) futures[i]=executor.submit (new CallableTask(i+1)); executor.shutdown(); for(int i=0;i<3;i++) System.out.printf("task%d finished\n",futures[i].get()); } task1 started task2 started task3 started task1 finished task2 finished task3 finished
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Thread Safe Containers Most of java.util containers are not thread safe Because synchronize slows performance They could be wrapped with synchronized decorators Only when we must, we’ll pay with performance private Map hm = Collections.synchronizedMap( new HashMap ());
Introduction Scheduling Tasks Atomic objects Deadlock avoidance Thread Pools Callable, Future Thread safe containers Thread Safe Containers java.util.concurrent introduced Thread Safe containers, that also provides good performance! ArrayBlockingQueue ArrayBlockingQueue ConcurrentHashMap ConcurrentHashMap ConcurrentLinkedQueue ConcurrentLinkedQueue etc…