760 likes | 778 Views
Explore multithreading concepts in Java programming through a bouncing ball GUI application. Learn thread creation, synchronization, and managing thread states in a graphical user interface.
E N D
COMP201 Java Programming Part III: Advanced Features Topic 12: Multithreading Volume II,Chapter 1
Outline • Introduction: Why and what • Basics: creating and running threads • Issues • Thread states • Thread scheduling • Synchronization • Suspending and stopping threads • Threads and Swing
Introduction • Consider the program Bounce.java • Desired effects • If the Start button is clicked, a ball starts to bounce. • If the button is clicked again, a second ball starts to bounce and so on. • If the Close button is clicked, the windows closes and the program terminates • Classes • Ball • BallPanel extends JPanel • BounceFrame extends JFrame • Bounce
Introduction • Classes • Ball • public Ellipe2D getShape() Gets the shape of the ball at its current position • public void move() • Moves the ball to the next position, reversing direction if it hits one of the edges • class BallPanel extends JPanel • Keeps a list of balls = new ArrayList<Ball>(); • public void add(Ball b) • Add a ball to the Panel. • public void paintComponent(Graphics g) • Draw all balls at their current positions
Introduction • Classes class BounceFrame extends JFrame • Set up GUI and listeners • When the Close button is clicked this method is called, public void actionPerformed(ActionEvent evt) { System.exit(0); } • When the Start Button is clicked, this method is called public void actionPerformed(ActionEvent evt) { addBall(); //Creates and adds a bouncing ball to the panel // and make it bounce 1,000 times. }
Introduction public void addBall() { try { Ball ball = new Ball(); panel.add(ball); for (int i = 1; i <= STEPS; i++) { ball.move(panel.getBounds()); panel.paint(panel.getGraphics()); Thread.sleep(DELAY); } } catch (InterruptedException e) {} } Note: sleep is a static method that puts the currently running thread to sleep. It throws InterruptedException when interrupted.
Introduction • However • Cannot start a second ball before the current ball finishes bouncing • Cannot terminate program before the current ball finishes bouncing • Actually, won’t even work if repaint() is used (as it should be) instead of paint.
Introduction • Why? • There is a single thread of control. • Actions are executed one by one. • Cannot execute next action before current action finishes • Implications in general: • Cannot do anything else while waiting data from the net. • Cannot stop downloading even though you know, after seeing part of the download, that you don’t want the download any more • Solution: Multithreading
Introduction • A multithreaded program has multiple threads of control • OS runs one thread a short period of time, then switches to another, and so on • To user, threads appear to run simultaneously. An illusion. • Nonetheless, makes it easier for user to interact with program
Introduction • In our example, we need more than one thread: • One thread to listen for button clicks • One thread to bounce each ball //BounceThread.java
Outline • Introduction: Why and what • Basics: creating and running threads • Issues • Thread states • Thread scheduling • Synchronization • Suspending and stopping threads • Threads and Swing
Creating and Running Threads • How to create and run new threads (from the current thread)? 1. Write a class that implements the interface java.lang.Runnable class MyRunnable implements Runnable { public void run(){ task code }} • Construct an object of your class: Runnable r = new MyRunnable(); 3. Construct a Thread object from the Runnable: Thread t = new Thread(r); 4. Start the thread: t.start(); Don’t call run, it just executes the task in the same thread, no new thread is started.
Creating and Running Threads class BallRunnable implements Runnable { …… public void run() { try { for (int i = 1; i <= STEPS; i++) { ball.move(component.getBounds()); component.repaint(); Thread.sleep(DELAY); } }catch (InterruptedException e){} } private Ball ball; private Component component; …} //BounceThread.java
Invoke the addball() when “Start” button is clicked addButton(buttonPanel, "Start",new ActionListener() { public void actionPerformed(ActionEvent event) { addBall();} }); public void addBall() { Ball b = new Ball(); panel.add(b); Runnable r = new BallRunnable(b, panel); Thread t = new Thread(r); t.start(); } private BallPanel panel;
Outline • Introduction: Why and what • Basics: creating and running threads • Issues • Thread states • Thread scheduling • Synchronization • Suspending and stopping threads • Threads and Swing
Thread States Four states for threads: new, runnable, blocked, dead sleep blocked done sleeping new suspend resume start wait runnable notify Wait for lock Lock available block on I/O runexits stop dead I/O complete Note: suspend, resume,stop deprecated.
new start runnable Thread States • When a thread has just been created using the newoperator, it is in the newstate. • Once start method is invoked (which calls the run method), the thread becomes runnable. • A runnable thread might not be running. • There can be many runnable threads. But only one of them can be running at any time point. • OS decides which thread to run. More on this later.
Thread States • A runnable thread enters the blocked state when • The thread is currently running and methodThread.sleepis called • suspend method of the thread is called. (deprecated) • The thread calls thewaitmethod. • The thread tries to lock an object locked by another thread. • The thread calls an operation that is blocked on i/o. sleep blocked suspend wait runnable A blocked thread cannot be running Wait for lock block on I/O
Thread States • A blocked reenters runnable state when • It has slept the specified amount of time. • resume method of the thread is called. (deprecated) • Another method calls notifyornotifyAll • Object lock released by another thread • I/O completed. blocked done sleeping resume runnable notify Lock available I/O complete
Thread States • A runnable thread enters the dead state when • Its run method exits. Natural death. • stop method of the thread is called. (deprecated) • An exception is thrown but not caught. runnable runexits stop dead
Thread States • Finding out states of threads • Method isAlive allows you to find out whether a thread is alive or dead. • This method returns true if the thread is runnable or blocked, • false if the thread is still new and not yet runnable or if the thread is dead • No way to find out whether an alive thread is running, runnable, or blocked.
Outline • Introduction: Why and what • Basics: creating and running threads • Issues • Thread states • Thread scheduling • Synchronization • Suspending and stopping threads • Threads and Swing
Thread Scheduling • At any time, there might be many runnable threads. But only one of them is actually running. • The thread scheduler decides which runnable thread to run. • Questions: • When does the thread scheduler kick in and pick a thread to run? • How does the thread scheduler select among the runnable threads? • A not-so-precise answer: • A running Java thread will continue to run until • It calls yield method, or • It ceases to be runnable (dead or blocked), or • Another thread with higher priority moves out of blocked state • Then the thread scheduler kicks in and picks another thread with the highest priority to run
Thread Scheduling • Two different thread implementations • “Native thread” implementation (e.g. Windows): Performs time-slicing. Interrupts the running thread periodically to give other threads a chance to run. • “Green thread” implementation (e.g. Solaris) Does not perform time-slicing. It keeps a running thread active until a higher-priority thread awakes and takes control.
Thread Scheduling • The answer on slide 23 is precise for the green thread implementation. • For the native thread implementation, the precise answer is • A running Java thread will continue to run until • It calls yield method, or • It ceases to be runnable (dead or blocked), or • Another thread with higher priority moves out of blocked state, or • It is pre-emptied by OS (time-slicing). • Then the thread scheduler kicks in and picks another thread with the highest priority to run
Thread Scheduling • Priority of individual threads • Can be increased or decreased using setPriority • Java have 10 priority levels (constants of Thread class) MIN_PRIORITY = 1; NORMAL_PRIORITY = 5; MAX_PRIORITY = 10 • A thread inherits priority from its parent thread, the one that creates it. • Note • Some OS has fewer. E.g. Windows NT has 7. • JVM maps Java priority levels to priority level of the underlying OS.
Thread Scheduling • Example: BounceExpress.java • Two kinds of balls: black and red. • Red ball threads have higher priority and hence get more chance to run. The effect is that red balls appear to be moving faster.
Thread Scheduling • The addBall method public void addBall(int priority, Color color) { for (int i = 0; i< 300; i++){ Ball b = new Ball( color); panel.add(b); Runnable r = new BallRunnable(b, panel); Thread t = new Thread(r); t.setPriority(priority);// priority set here t.start(); try { Thread.sleep(1); }catch(InterruptedException exception){}; } }
Thread Scheduling • Buttons and listeners addButton(buttonPanel, "Start", new ActionListener() { public void actionPerformed(ActionEvent evt) { addBall(Thread.NORM_PRIORITY - 2, Color.black); }}); addButton(buttonPanel, "Express", new ActionListener() { public void actionPerformed(ActionEvent evt) { addBall(Thread.NORM_PRIORITY + 2, Color.red); } });
Thread Scheduling • Question 1: • Consider the case when there are 1 black ball and 1 red ball. • When the red-ball thread goes to sleep, there is only one other thread, the black-ball thread. • Hence the black-ball thread must be chosen. • Implication: • black ball takes one move, red ball takes one move, black ball takes one move, and so on. • The two balls should be of the same speed. • Well, this is not the case. Why? • There is another thread! What is it? What role does it play? • When event dispatch thread pauses, the red-ball thread already wake up from sleep and hence picked by scheduler over back-ball thread.
Thread Scheduling • Question 2: • If we change sleeping to 50 or running the program on a faster cpu, red balls are not faster any more. • Why? • In order for the red-ball thread to be picked more often than the black-ball thread, it must “meet” the scheduler more often. • When event dispatch thread pauses, the red-ball thread is still sleeping, just as the black-ball thread.
sleepvs. yield • In BallRunnable, sleep is called to give other thread a chance to run. • Another way is to call yield. class BallRunnable implements Runnable{ public BallRunnable(Ball aBall, Component aComponent) { ball = aBall; component = aComponent; } public void run() // codes for new thread { for ( int i = 1; i <= STEPS; i++) { ball.move(component.getBounds()); component.repaint(); Thread.yield(); } }}
sleepvs. yield • There is a big difference • Calling sleep put the current running thread into the blocked state • Calling yield does not put the calling thread, t1, into the blocked state • It merely let the scheduler kick in and pick another thread to run. • It might happen that the t1 is select to run again. This happens when t1 has a higher priority than all other runnable threads.
Thread Scheduling • Cooperating vs. Selfish threads: • A cooperating thread gives others a chance to run • Calling sleep: pause for the specified period of time • Calling yield: pause temporarily. Scheduler kicks in. • A selfish thread does none of this. • Effects of selfish threads are system-dependent: • Green thread: A selfish thread can consume all the CPU time. • Native thread: A selfish thread doesn’t post a big problem.
Thread Scheduling public void run() { try {for (int i = 1; i <= STEPS; i++) { ball.move(component.getBounds()); component.repaint(); if (ball.getSelfish()) { // busy wait for 5 milliseconds long t = System.currentTimeMillis(); while (System.currentTimeMillis()<t + 5) ; } else Thread.sleep(DELAY); }catch (InterruptedException exception){} } //BounceSelfish.java
Thread Scheduling • Question 3: • The balls some times jump. • Why? • Event dispatch thread doesn’t get the time to run. Paint events accumulate.
Outline • Introduction: Why and what • Basics: creating and running threads • Issues • Thread states • Thread scheduling • Synchronization • Suspending and stopping threads • Threads and Swing
Synchronization • The Synchronization problem: Two different threads modify the same object atthe same time, leading to corrupted object. Such a situation is called race condition. • An analog: • You and your partner are finishing a group project and starting to write the project report. • The report is kept at a centralized location. • You open the report and edit it • Your partner opens the report and edits it • You save the edits. • Your partner saves his edits, • Your edits are lost!
Synchronization • An example: UnsynchBankTest.java • class Bank • A bank with a number of bank accounts. • class TransferRunnable implements Runnable - A runnable that transfers money from an account to other accounts in a bank. • public class UnsynchBankTest • Create 10 accounts and multiple threads to make random transfers
Synchronization • public voidtransfer(int from, int to, int amount) { if (accounts[from] < amount) return ; accounts[from] -= amount; // added by Instructor so that corruption occurs //more easily try { Thread.sleep(3); } catch(InterruptedException e) {} accounts[to] += amount; counter++; //print out total after every 1000 transactions if (counter %1000 == 0){ System.out.print(Thread.currentThread()); System.out.printf(" %10.2f from %d to %d", amount, from, to); System.out.printf(" Total Balance: %10.2f%n", getTotalBalance()); }
Synchronization • class TransferRunnable implements Runnable { publicTransferRunnable, int from, double max) {…} public void run() { try { while (true) { int toAccount = (int)(bank.size() * Math.random()); double amount = maxAmount * Math.random(); bank.transfer(fromAccount, toAccount, amount); Thread.sleep((int)Delay * Math.random()); }} catch(InterruptedException e) {} } private Bank bank; private int fromAccount; private double maxAmount; private int DELAY = 1000;
Synchronization • ClassUnsynchBankTest publicstatic void main(String[] args) { Bank b = new Bank(NACCOUNTS, INITIAL_BALANCE); int i; for (i = 0; i < NACCOUNTS; i++) { TransferRunnable r = new TransferRunnable(b, i, INITIAL_BALANCE); Thread t = new Thread(r); t.start(); } } public static final int NACCOUNTS = 10; public static final int INITIAL_BALANCE = 10000; Note: Total amount in all accounts = 100,000
Synchronization • Run the program • Very quickly, the amounts in the accounts do not add up to 100,000 • Why? • The transfer method of Bank class is not atomic: consists of many steps • Can be interrupted in the middle
Synchronization • Problem scenario: • Thread 1 takes 50 from account A • Goes to sleep (simulate interruption, self interruption) • Thread 2 completes transfer and call test • Result: • 50 less in total • The total amount can only be less than 100,000 • If we swap the deduction and addition, the total will always be more.
Synchronization • Note that even instructions are not atomic. • Consider the following accounts[to] += amount; • It is processed in three steps as follows: • Load current value of accounts[to] to a register • Add amount • Move the result back to accounts[to].
Synchronization • Execution interruption • accounts[0] is currently 100. • Thread 1 perform accounts[0] += 50; • Thread 2 performs accounts[0] += 50; • The correct result should be: accounts[0] == 200. • What is the result if the following happens? Actual result: accounts[0] == 150 The probability of such interruptions is low (but possible). This is why we faked interruption using sleep.
ReentrantLock • How to avoid the work of the transfer method being interrupted? JDK 5.0 introduces the Reentrantlock class class Bank { … publicvoid transfer( int from, int to,int amount){ bankLock.lock(); try{ if (accounts[from] < amount ) return; accounts[from] -= amount; try {Thread.sleep(1);}catch(InterruptedException e) {} accounts[to] += amount; … } finally {bankLock.unlock();} }…. Private Lock bankLock= newReentrantlock (); //Reentrantlock implements the Lock interface. } // SynchronizedBankTest0.java
ReentrantLock • How does the mechanism work? • This construct guarantees that only one thread at a time can enter the critical section. As soon as one thread locks the lock object, no other thread can get past the lock statement. When other threads call lock they are blocked until the first thread unlocks the lock object. • Suppose one thread calls transfer and gets preempted before it is done. Suppose a second thread also calls transfer . The second thread cannot acquire the lock and is blocked in the call to the lockmethod. It is deactivated and must wait for the first thread to finish executing the transfer method. When the first thread unlockthe lock, then the second thread can proceed. • The lock is called reentrant because a thread can repeatedly acquire a lock that it already owns. The lock keeps a hold count that keeps track of the nested calls to the lockmethod. The thread has to call unlockfor every call to lockin order to relinquish the lock. So code that is protected by a lock can call another method that uses the same lock.
Condition Object • Often, a thread enters a critical section, only to discover that it can’t proceed until a condition is fulfilled. JDK5.0 uses a condition object to manage threads that have acquired a lock but cannot do useful work. • For example, what do we do where there is not enough money in the account? We wait until some other thread has added funds. But this thread has just gained exclusive access to the banklock, so no other thread has a chance to make a deposit. This is where condition object come in. • A lock object can have one or more associated condition objects. You obtain a condition object with the newCondition method. It is customary to give each condition object a name that evokes the condition that it represents. For example: private Condition sufficientFunds; sufficientFunds = bankLock.newCondition ();
Condition Object • If the transfer method finds that sufficient funds are not available, it calls sufficientFunds.await(); • The current thread is now blocked and gives up the lock. This lets in another thread that can (possibly) increase the account balance. • When another thread transfers money, it should call sufficientFunds.signalAll();to unblock all threads that are waiting for the condition. or call sufficientFunds.signal(); to unblock a single thread from the wait set, chosen at random. • When the threads are removed from the wait set, they are again runnable and the scheduler will eventually activate them again. At that time, they will attempt to reenter the object. As soon as the lock is available, one of them will acquire the lock and continue where it left off, returning from the await. At this time the thread should test the condition again. There is no guarantee that the condition is now fulfilled.