Download presentation
Presentation is loading. Please wait.
1
Principles of Software Development
Concurrency CSCI 201 Principles of Software Development Jeffrey Miller, Ph.D.
2
Outline Synchronization USC CSCI 201L
3
Motivation for Synchronization
Thread synchronization is used to coordinate the execution of dependent threads Sometimes we want to have some control over when threads execute or what code they are able to simultaneously access Shared resources pose potential problems to multi-threaded code since we don’t typically have control over when a thread will be switched into and out of the CPU USC CSCI 201L
4
Synchronization Program Analogy
Assume I brought a piggy bank to class (shared resource) 100 students in the class have to deposit a penny into the piggy bank (independent threads) How many pennies should be in the bank at the end? Deposit 1¢ USC CSCI 201L
5
Synchronization Example #1
1 public class AddAPenny implements Runnable { 2 private static PiggyBank piggy = new PiggyBank(); 3 4 public void run() { 5 piggy.deposit(1); 6 } 7 8 public static void main(String [] args) { 9 for (int i=0; i < 100; i++) { 10 Thread t = new Thread(new AddAPenny()); 11 t.start(); 12 } 13 System.out.println("Balance = " + piggy.getBalance()); 14 } 15 } class PiggyBank { 18 private int balance = 0; 19 public int getBalance() { 20 return balance; 21 } 22 public void deposit(int amount) { 23 int newBalance = balance + amount; 24 Thread.yield(); 25 balance = newBalance; 26 } 27 } 4 Executions The values are not close to 100 because of how soon the println executes after the threads are started. Another reason these values are not close to 100 because the CPU switches the threads out of the CPU before they are able to set the balance variable on line 29 That means that threads are overwriting the other threads USC CSCI 201L
6
Synchronization Example #2
1 import java.util.concurrent.ExecutorService; 2 import java.util.concurrent.Executors; 3 4 public class AddAPenny implements Runnable { 5 private static PiggyBank piggy = new PiggyBank(); 6 7 public void run() { 8 piggy.deposit(1); 9 } public static void main(String [] args) { 12 ExecutorService executor = Executors.newCachedThreadPool(); 13 for (int i=0; i < 100; i++) { 14 executor.execute(new AddAPenny()); 15 } 16 executor.shutdown(); 17 // wait until all tasks are finished 18 while(!executor.isTerminated()) { 19 Thread.yield(); 20 } System.out.println("Balance = " + piggy.getBalance()); 23 } 24 } class PiggyBank { 27 private int balance = 0; 28 public int getBalance() { 29 return balance; 30 } 31 public void deposit(int amount) { 32 int newBalance = balance + amount; 33 Thread.yield(); 34 balance = newBalance; 35 } 36 } 4 Executions The ExecutorService ensures that all of the threads will complete execution before printing the balance This is what while(!executor.isTerminated()) does These values are not close to 100 because the CPU switches the threads out of the CPU before they are able to set the balance variable on line 29 That means that threads are overwriting the other threads These values are lower than the values on the previous slide (slightly) because there is a little more overhead with the ExecutorService USC CSCI 201L
7
Synchronization Example #3
1 import java.util.concurrent.ExecutorService; 2 import java.util.concurrent.Executors; 3 4 public class AddAPenny implements Runnable { 5 private static PiggyBank piggy = new PiggyBank(); 6 7 public void run() { 8 piggy.deposit(1); 9 } public static void main(String [] args) { 12 ExecutorService executor = Executors.newCachedThreadPool(); 13 for (int i=0; i < 100; i++) { 14 executor.execute(new AddAPenny()); 15 } 16 executor.shutdown(); 17 // wait until all tasks are finished 18 while(!executor.isTerminated()) { 19 Thread.yield(); 20 } System.out.println("Balance = " + piggy.getBalance()); 23 } 24 } class PiggyBank { 27 private int balance = 0; 28 public int getBalance() { 29 return balance; 30 } 31 public void deposit(int amount) { 32 balance += amount; 33 } 34 } 4 Executions These outputs are closer to 100 (expected answer) because there is much less code in the deposit method, so fewer chances of being context switched out of the CPU USC CSCI 201L
8
Synchronization Overview
The problems in the previous examples are based on the use of a shared variable across multiple threads (the PiggyBank object piggy) The OS switches threads out of the CPU at times unknown to us This seems to be happening inside the deposit(int) method after newBalance is set but before it is assigned back to balance This causes the old value of balance to be used in subsequent threads When we reduce the deposit(int) method to a single statement, we get higher values for balance because there are fewer locations at which the OS can switch the thread out of the CPU before setting the value of balance USC CSCI 201L
9
Race Conditions A race condition is a state where more than one thread is accessing the same variable and overwriting the other thread’s updates This is a common problem in multi-threaded programming A class is thread-safe if an object of the class does not cause a race condition in the presence of multiple threads The AddAPenny class in the previous examples is not thread-safe USC CSCI 201L
10
Critical Section To avoid race conditions, it is necessary to prevent more than one thread from simultaneously entering a certain part of the program This part of the program is called a critical section or critical region In the previous examples, the critical section would be the deposit(int) method in the PiggyBank class If only one thread at a time was able to access the deposit(int) method, we would not have a race condition on the shared variable balance Note that balance is shared in the static instance of PiggyBank 22 public void deposit(int amount) { int newBalance = balance + amount; Thread.yield(); balance = newBalance; 36 } USC CSCI 201L
Similar presentations
© 2024 SlidePlayer.com. Inc.
All rights reserved.