550 likes | 730 Views
Lecture 10: Multithreading, Part I. Multithreading. Multithreading is just what it sounds like: concurrent running of multiple tasks.
E N D
Multithreading Multithreading is just what it sounds like: concurrent running of multiple tasks. Multithreading does not always involve parallel execution, in which multiple operations are executed simultaneously. It can be implemented in a situation in which multiple threads have access to the CPU at different times
Multithreading Multithreading has been available in programming for a long time, even when it was unusual to run an application on multiple processors. • Multithreading offers a way to run multiple tasks at once without blocking,ie making tasks wait for another task which has not completed because it is waiting for user input, for some resource to become available, for a set amount of time to pass, etc. • Multithreading offers a way to give higher-priority tasks access to resources when needed without starving lower-priority ones • Event driven programming with GUI components often uses multiple threads, some of which are waiting for user input.
Multithreading Multithreading also makes it easier for an operating system or other scheduling mechanism to spread work among different processors. This is increasingly important as multiple cores have become the norm and as distributed applications, ones running on multiple computers on a network, become more prevalent.
Threads Concept Multiple threads on multiple CPUs Multiple threads sharing a single CPU
Creating Tasks and Threads Write a class that implements Runnable Instantiate a Thread, sending an instance of your Runnable implementation to the constructor Call start() on the Thread
The Static sleep(milliseconds) Method The sleep(long mills) method puts the thread to sleep for the specified time in milliseconds. For example, suppose you modify the code in Lines 53-57 in TaskThreadDemo.java as follows: public void run() { for (int i = 1; i <= lastNum; i++) { System.out.print(" " + i); try { if (i >= 50) Thread.sleep(1); } catch (InterruptedException ex) { } } } Every time a number (>= 50) is printed, the print100 thread is put to sleep for 1 millisecond.
The following example uses a .wav file which is available at http://cd.textfiles.com/10000soundssongs/WAV/COWBELL2.WAV Any other very short .wav file would work too.
package week8; import javax.sound.sampled.*; import java.io.File; import java.io.IOException; import javax.sound.sampled.LineEvent.Type; public class WavPlayer { File file; AudioListener listener; AudioInputStreamaudioInputStream; public WavPlayer(String fileName) { file = new File(fileName); } public void playClip() throws IOException, UnsupportedAudioFileException, LineUnavailableException, InterruptedException { listener = new AudioListener(); audioInputStream = AudioSystem.getAudioInputStream(file); try { Clip clip = AudioSystem.getClip(); clip.addLineListener(listener); clip.open(audioInputStream); try { clip.start(); listener.waitUntilDone(); } finally { clip.close(); } } finally { audioInputStream.close(); } } private class AudioListener implements LineListener { private boolean done = false; @Override public synchronized void update(LineEvent event) { Type eventType = event.getType(); if (eventType == Type.STOP || eventType == Type.CLOSE) { done = true; // notifyAll() tells all objects or threads that are waiting for // the line to do something that its state has changed notifyAll(); } } public synchronized void waitUntilDone() throws InterruptedException { while (!done) { wait(); } } } }
package week8; public class Rhythm implements Runnable { private int interval; private WavPlayer player; public Rhythm(String wav, intintervalIn) { interval = intervalIn; player = new WavPlayer(wav); } @Override public void run() { while (true) { try { player.playClip(); Thread.sleep(interval); } catch (Exception e) { e.printStackTrace(); } } } }
package week8; public class RhythmDriver { public static void main(String[] args) { Rhythm r1 = new Rhythm("cowbell2.WAV", 400); Rhythm r2 = new Rhythm("cowbell2.WAV", 600); Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); t2.start(); } }
Multithreading Example from Liang textbook follows. This example sets up several threads that produce various output and runs them simultaneously. Before we take any steps to control how the output from the different threads is synchronized, the order of the output depends on aspects of the operating system and hardware that we can't predict. Run the original code several times, and you will probably see different output.
public class TaskThreadDemo { public static void main(String[] args) { // Create tasks Runnable printA = new PrintChar('a', 100); Runnable printB = new PrintChar('b', 100); Runnable print100 = new PrintNum(100); // Create threads Thread thread1 = new Thread(printA); Thread thread2 = new Thread(printB); Thread thread3 = new Thread(print100); // Start threads thread1.start(); thread2.start(); thread3.start(); } } // The task for printing a specified character in specified times class PrintChar implements Runnable { private char charToPrint; // The character to print private int times; // The times to repeat /** Construct a task with specified character and number of * times to print the character */ public PrintChar(char c, int t) { charToPrint = c; times = t; } @Override /** Override the run() method to tell the system * what the task to perform */ public void run() { for (inti = 0; i < times; i++) { System.out.print(charToPrint); } } } // The task class for printing number from 1 to n for a given n class PrintNum implements Runnable { private intlastNum; /** Construct a task for printing 1, 2, ... i */ public PrintNum(int n) { lastNum = n; } @Override /** Tell the thread how to run */ public void run() { for (inti = 1; i <= lastNum; i++) { System.out.print(" " + i); } } }
The Static yield() Method You can use the yield() method to temporarily release time for other threads. For example, suppose you modify the code in Lines 53-57 in TaskThreadDemo.java as follows: public void run() { for (inti = 1; i <= lastNum; i++) { System.out.print(" " + i); Thread.yield(); } } Every time a number is printed, the print100 thread is yielded. So, the numbers are printed after the characters.
The join() Method You can use the join() method to force one thread to wait for another thread to finish. For example, suppose you modify the code in Lines 53-57 in TaskThreadDemo.java as follows: The numbers after 50 are printed after thread printA is finished.
Multiple Cores and Multithreading Assignment of your threads to threads in the kernel, which in turn are assigned to particular processors, is generally unpredictable. This differs from one OS to another, and there are many other threads also running at any time. You will learn more about this in CS440. Try the following example with one thread, two threads, and three threads, etc, and check your CPU usage. You will probably find that, while total usage is high, you can’t see any obvious patterns related to the number of threads in use.
Multiple Cores and Multithreading package week8; public class CPUBurner implements Runnable { long a = 0; @Override public void run() { while (true) { a++; } } }
Multiple Cores and Multithreading package week8; public class CPUBurnerDemo { public static void main(String[] args){ Runnable b1 = new CPUBurner(); Runnable b2 = new CPUBurner(); Runnable b3 = new CPUBurner(); Thread t1 = new Thread(b1); Thread t2 = new Thread(b2); Thread t3 = new Thread(b3); t1.start(); t2.start(); t3.start(); } } **Use the red stop button in Eclipse to terminate this after you get bored!!!** If you forget to close an app in Eclipse, you can get a list of open apps by clicking on the monitor screen icon in the output panel, choosing the zombie app, and clicking on the red button.
Thread Life Cycle: States Threads may be in any of the following states: • New (not started) • Ready (scheduled by OS, not yet running) • Running • Waiting (say, for input) • Timed Waiting (Thread.sleep) • Blocked (by CPU scheduler, for cases where another thread must execute first) • Terminated
isAlive(), interrupt(), and isInterrupted() The isAlive() method is used to find out the state of a thread. It returns true if a thread is in the Ready, Blocked, or Running state; it returns false if a thread is new and has not started or if it is finished. The interrupt() method interrupts a thread in the following way: If a thread is currently in the Ready or Running state, its interrupted flag is set; if a thread is currently blocked, it is awakened and enters the Ready state, and an java.io.InterruptedException is thrown. The isInterrupted() method tests whether the thread is interrupted.
The deprecated stop(), suspend(), and resume() Methods NOTE: The Thread class also contains the stop(), suspend(), and resume() methods. As of Java 2, these methods are deprecated (or outdated) because they are known to be inherently unsafe. You should assign null to a Thread variable to indicate that it is stopped rather than use the stop() method.
Life Cycle Demo package week8; import javax.swing.JOptionPane; public class LifeCycleThread implements Runnable { int a = 0; @Override public void run() { int quit; do { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } quit = JOptionPane.showConfirmDialog(null, "Quit?"); } while(quit != 0); } }
Life Cycle Demo package week8; public class LimitedCPUBurner implements Runnable { long a = 0; @Override public void run() { while(true){ a++; try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
package week8; import java.lang.Thread.State; public class LifeCycleDemo { public static void main(String[] args){ Runnable b1 = new CPUBurner(); Runnable l = new LifeCycleThread(); Runnable b2 = new LimitedCPUBurner(); Thread t1 = new Thread(b1); Thread t2 = new Thread(l); Thread t3 = new Thread(b2); System.out.println("Thread 1 is " + t1.getState()); t1.start(); //t2.start(); System.out.println("Thread 1 is " + t1.getState()); System.out.println("Thread 1 is " + (t1.isAlive()?"alive":"dead")); System.out.println("Thread 1 is " + (t1.isInterrupted()?"":"not ") +"interrupted"); System.out.println("Interrupting t1"); t1.interrupt(); System.out.println("Thread 1 is " + t1.getState()); System.out.println("Thread 1 is " + (t1.isInterrupted()?"":"not ") +"interrupted"); t2.start(); t3.start(); System.out.println("Thread 3 is " + t3.getState()); do { try { Thread.sleep(1000); System.out.println("Thread 2 is " + t2.getState()); System.out.println("Thread 3 is " + t3.getState()); } catch (InterruptedException e) { e.printStackTrace(); } } while(t2.getState() != State.TERMINATED); t2 = null; System.exit(0); } }
Thread Priority • Each thread is assigned a default priority of Thread.NORM_PRIORITY. You can reset the priority using setPriority(int priority). • Some constants for priorities include Thread.MIN_PRIORITYThread.MAX_PRIORITYThread.NORM_PRIORITY • The results of setting priorities are still not fully predictable; your OS has complex ways of deciding when threads get the CPU and output
package week8; public class ThreadPriorityDemo { public static void main(String[] args) { // Create tasks Runnable printA = new MyPrintChar('a', 100); Runnable printB = new MyPrintChar('b', 100); Runnable print100 = new MyPrintNum(100); // Create threads Thread thread1 = new Thread(printA); Thread thread2 = new Thread(printB); Thread thread3 = new Thread(print100); thread1.setPriority(Thread.MIN_PRIORITY); thread3.setPriority(Thread.MAX_PRIORITY); // Start threads thread1.start(); thread2.start(); thread3.start(); } } // The task for printing a specified character in specified times class MyPrintChar implements Runnable { private char charToPrint; // The character to print private int times; // The times to repeat /** Construct a task with specified character and number of * times to print the character */ public MyPrintChar(char c, int t) { charToPrint = c; times = t; } @Override /** Override the run() method to tell the system * what the task to perform */ public void run() { for (inti = 0; i < times; i++) { System.out.print(charToPrint); } } } // The task class for printing number from 1 to n for a given n class MyPrintNum implements Runnable { private intlastNum; /** Construct a task for printing 1, 2, ... i */ public MyPrintNum(int n) { lastNum = n; } @Override /** Tell the thread how to run */ public void run() { for (inti = 1; i <= lastNum; i++) { System.out.print(" " + i); } } }
GUI Event Dispatcher Thread GUI event handling and painting code executes in a single thread, called the event dispatcher thread. This ensures that each event handler finishes executing before the next one executes and the painting isn’t interrupted by events.
Thread Pools Starting a new thread for each task could limit throughput and cause poor performance as the OS juggles a huge number of threads. A thread pool provides a limited number of threads and allocates them to tasks, which are usually held in a queue. The JDK uses the Executor interface for executing tasks in a thread pool and the ExecutorService interface for managing and controlling tasks. ExecutorService is a subinterface of Executor.
Creating Executors To create an Executor object, use the static methods in the Executorsclassin the java.util.concurrent package.
package week8; import java.util.concurrent.*; public class ExecutorDemo { public static void main(String[] args) { // Create a fixed thread pool with maximum three threads ExecutorService executor = Executors.newFixedThreadPool(3); // Submit runnable tasks to the executor executor.execute(new PrintChar('a', 100)); executor.execute(new PrintChar('b', 100)); executor.execute(new PrintChar('c', 100)); executor.execute(new PrintChar('d', 100)); executor.execute(new PrintNum(100)); // Shutdown the executor executor.shutdown(); } // The thread class for printing a specified character // in specified times private static class PrintChar implements Runnable { private char charToPrint; // The character to print private int times; // The times to repeat /** Construct a thread with specified character and number of times to print the character */ public PrintChar(char c, int t) { charToPrint = c; times = t; } }
/** Override the run() method to tell the system what the thread will do */ public void run() { for (inti = 0; i < times; i++) System.out.print(charToPrint); } } // The thread class for printing number from 1 to n for a given n private static class PrintNum implements Runnable { private intlastNum; /** Construct a thread for print 1, 2, ... i */ public PrintNum(int n) { lastNum = n; } /** Tell the thread how to run */ public void run() { for (inti = 1; i <= lastNum; i++) System.out.print(" " + i); } } }
Deadlock Threads must sometimes wait for particular resources, such as the CPU or output hardware, to become available. A deadlock occurs when multiple threads are waiting for resources held by the others in an order that cannot be resolved. The simplest form of deadlock occurs when a process or thread enters a waiting state because a resource requested is being held by another waiting process, which in turn is waiting for a resource that the current thread is holding.
invokeLater and invokeAndWait You may need to run the code in the event dispatcher thread to avoid possible deadlock. • You can use the static methods, invokeLater and invokeAndWait, in the javax.swing.SwingUtilities class to run the code in the event dispatcher thread. Put this code in the run method of a Runnable object and specify the Runnable object as the argument to invokeLater and invokeAndWait. • The invokeLater method returns immediately, without waiting for the event dispatcher thread to execute the code. • The invokeAndWait method is just like invokeLater, except that invokeAndWait doesn't return until the event-dispatching thread has executed the specified code.
Launch Application from Main Method So far, you have launched your Swing GUI application from the main method by creating a frame and making it visible. This works fine for most applications. In certain situations, however, it could cause problems. To avoid possible thread deadlock, you should launch GUI creation from the event dispatcher thread as follows: public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { // Place the code for creating a frame and setting it properties } }); }
Thread Synchronization A shared resource may be corrupted if it is accessed simultaneously by multiple threads. For example, two unsynchronized threads accessing the same bank account may cause conflict.
package week8; import java.util.concurrent.*; public class AccountWithoutSync { private static Account account = new Account(); public static void main(String[] args) { ExecutorService executor = Executors.newCachedThreadPool(); // Create and launch 100 threads for (inti = 0; i < 100; i++) { executor.execute(new AddAPennyThread()); } executor.shutdown(); // Wait until all tasks are finished while (!executor.isTerminated()) { } System.out.println("What is balance ? " + account.getBalance()); } // A thread for adding a penny to the account private static class AddAPennyThread implements Runnable { public void run() { account.deposit(1); } } // An inner class for account private static class Account { private int balance = 0; public intgetBalance() { return balance; } public void deposit(int amount) { intnewBalance = balance + amount; // This delay is deliberately added to magnify the // data-corruption problem and make it easy to see. try { Thread.sleep(1); } catch (InterruptedException ex) { ex.printStackTrace(); } balance = newBalance; } } }
Race Condition What, then, caused the error in the example? Here is a possible scenario: The effect of this scenario is that Task 1 did nothing, because in Step 4 Task 2 overrides Task 1's result. Task 1 and Task 2 are accessing a common resource in a way that causes conflict. This is a common problem known as a race condition in multithreaded programs. A class is said to be thread-safe if an object of the class does not cause a race condition in the presence of multiple threads. As demonstrated in the preceding example, the Account class is not thread-safe.
The synchronized keyword To avoid race conditions, more than one thread must be prevented from simultaneously entering certain part of the program, known as critical region. The critical region in the Listing 29.7 is the entire deposit method. You can use the synchronized keyword to synchronize the method so that only one thread can access the method at a time. One way to correct the problem with Account is to make it thread-safe by adding the synchronized keyword in the deposit method as follows: public synchronized void deposit(intamount)
Synchronizing Instance Methods and Static Methods With the deposit method synchronized, the preceding scenario cannot happen. If Task 2 starts to enter the method, and Task 1 is already in the method, Task 2 is blocked until Task 1 finishes the method.
Synchronizing Instance Methods and Static Methods A synchronized method must acquire a lock before it executes. For an instance method, the lock is on the object for which the method was invoked. For a static method, the lock is on the class. If one thread invokes a synchronized instance method (respectively, static method) on an object, the lock of that object (respectively, class) is acquired first, then the method is executed, and finally the lock is released. Another thread invoking the same method of that object (respectively, class) is blocked until the lock is released.
Synchronizing Statements Invoking a synchronized instance method of an object acquires a lock on the object, and invoking a synchronized static method of a class acquires a lock on the class. A synchronized statement can be used to acquire a lock on any object, not just this object, when executing a block of the code in a method. This block is referred to as a synchronized block. The general form of a synchronized statement is as follows: synchronized (expr) { statements; } The expression expr must be an object reference. If the object is already locked by another thread, the thread is blocked until the lock is released. When a lock is obtained on the object, the statements in the synchronized block are executed, and then the lock is released.
Synchronizing Statements vs. Methods Any synchronized instance method can be converted into a synchronized statement. Suppose that the following is a synchronized instance method: public synchronized void xMethod() { // method body } This method is equivalent to public void xMethod() { synchronized (this) { // method body } }
Source Control By this point, you have all had the experience of losing work because of HD crash or other problems Also consider these situations: • You are writing code that depends on someone else's work. You write your code, then the other programmer sends you an update that breaks your code • Your customer requires technical support, but the current version of the software is not the same as the version the customer has installed, so you can't give accurate instructions or replicate the problem. • Multiple programmers want to try out various approaches to the same problem, decide later which to use, but keep versions using the other solutions • You spend a significant amount of time trying an approach that does not work well and want to revert your code to some earlier state. Source Control addresses these problems
Source Control Source control provides one or more repositories for code. Programmers can update the repository code to match their latest work, or update their work to the current or some past state of the repository. Make sure other programmers' code is up to date before you write code that depends on it. Revert to earlier state of the code Designate particular versions of the software (eg release 1.1) for archival purposes • maintain old or variant versions for tech support • keep rejected solutions in case you want to try them out again later • trace back to the point when some bug originated
Source Control The most popular source control platforms in use now are Subversion and Git. Subversion is easier to understand, so this is the one I will use in my examples. • Subversion uses a single, centralized repository • Git is newer and probably more widely used today, but it is more complex than Subversion. Git allows multiple, decentralized repositories so teams of programmers can work together to build alternative solutions on a large scale before merging them into the main project.
Subversion with Eclipse Using Subversion with Eclipse: Go to Help/Eclipse Marketplace Search for "Subclipse" and install it Get a repository • many companies offer small repositories for free; just do a web search • Google code provides repositories, but the code must be open-source, ie anyone can search the web and find your code
Subversion with Eclipse Connect to repository using the urland logon provided from Eclipse's SVN Repository Exploring view
Subversion with Eclipse Open the repository and create a new folder in trunk
Subversion with Eclipse Choose import and get the project you want to source-control