620 likes | 806 Views
제 13 주 멀티쓰레딩 (Multi-threading). 제 13 주 목표 여러 쓰레드가 어떻게 병렬도 실행되는 지 이해함 쓰레드를 구현하는 법을 배움 레이스 컨디션과 데드락에 대해 배움 락과 컨디션을 사용함으로써 공유자료가 엉망이 되지 않도록 하는 법을 배움 애니메이션에 스레드를 이용하는 법을 배움. public static void main(String[] args) { final Rectangle box = new Rectangle(5, 10, 20, 30);
E N D
제 13주 멀티쓰레딩(Multi-threading) • 제 13주 목표 • 여러 쓰레드가 어떻게 병렬도 실행되는 지 이해함 • 쓰레드를 구현하는 법을 배움 • 레이스 컨디션과 데드락에 대해 배움 • 락과 컨디션을 사용함으로써 공유자료가 엉망이 되지 않도록 하는 법을 배움 • 애니메이션에 스레드를 이용하는 법을 배움 강원대학교
public static void main(String[] args) { final Rectangle box = new Rectangle(5, 10, 20, 30); class Mover implements ActionListener { public void actionPerformed(ActionEvent event) { box.translate(1, 1); System.out.println(box); } } ActionListener listener = new Mover(); final int DELAY = 100; // Milliseconds between timer ticks Timer t = new Timer(DELAY, listener); t.start(); // 타이머가 별도의 스레드로 실행됨! JOptionPane.showMessageDialog(null, "Quit?"); System.out.println("Last box position: " + box); System.exit(0); } 강원대학교
Thread를 만드는 일반적 방법 • Runnable인터페이스를 구현하는 클래스를 정의하고 수행할 작업을 run메소드에 적어준다. public class MyRunnable implements Runnable { public void run() { // Task statements go here } } public interface Runnable{ void run();} 강원대학교
Running a Thread • 정의된 클래스 객체를 구성한다. • Runnable 객체를 인자로 삼아 Thread객체를 구성한다. • Thread에 start메소들를 호출한다. Runnable r = new MyRunnable(); Thread t = new Thread(r); t.start(); 강원대학교
Example • 매 초 현재시각과 "Hello World“를 출력하는 프로그램 Thu Dec 28 23:12:03 PST 2004 Hello, World! Thu Dec 28 23:12:04 PST 2004 Hello, World! Thu Dec 28 23:12:05 PST 2004 Hello, World! Thu Dec 28 23:12:06 PST 2004 Hello, World! Thu Dec 28 23:12:07 PST 2004 Hello, World! Thu Dec 28 23:12:08 PST 2004 Hello, World! Thu Dec 28 23:12:09 PST 2004 Hello, World! Thu Dec 28 23:12:10 PST 2004 Hello, World! Thu Dec 28 23:12:11 PST 2004 Hello, World! Thu Dec 28 23:12:12 PST 2004 Hello, World! 강원대학교
GreetingRunnable 뼈대 public class GreetingRunnable implements Runnable { public GreetingRunnable(String aGreeting) { greeting = aGreeting; } public void run() { // 할 일을 이곳에 적어줌 . . . } // run 메소드에서 이용할 인스턴스 필드 private String greeting; } 강원대학교
GreetingRunnable의 run 메소드에서 할 일 • 현재시각을 출력 Print a time stamp • 인사말을 출력 Print the greeting • 일 초 동안 기다림 Wait a second • 현재 날짜와 시간은 Date 객체를 구성함으로써 얻을 수 있다. Date now = new Date(); 강원대학교
GreetingRunnable의 run 메소드에서 할 일 • 1초 기다리기 위해서는 Thread 클래스의 sleep 메소드 호출 • Sleeping thread는 InterruptedException을 던지는 수가 있음 • Catch the exception • Terminate the thread Thread.sleep(milliseconds) 강원대학교
일반적인run메소드 모양 public void run() { try {Task statements } catch (InterruptedException exception) { }Clean up, if necessary} 강원대학교
File GreetingRunnable.java 01: import java.util.Date; 02: 03: /** 04: A runnable that repeatedly prints a greeting. 05: */ 06:public class GreetingRunnable implements Runnable 07: { 08: /** 09: Constructs the runnable object. 10: @param aGreeting the greeting to display 11: */ 12:public GreetingRunnable(String aGreeting) 13: { 14: greeting = aGreeting; 15: } 16: 17:public void run() 18: { 강원대학교
File GreetingRunnable.java 19:try 20: { 21:for (int i = 1; i <= REPETITIONS; i++) 22: { 23: Date now = new Date(); 24: System.out.println(now + " " + greeting); 25: Thread.sleep(DELAY); 26: } 27: } 28:catch (InterruptedException exception) 29: { 30: } 31: } 32: 33:private String greeting; 34: 35:private static final int REPETITIONS = 10; 36:private static final int DELAY = 1000; 37: } 강원대학교
File GreetingThreadTester.java 01: import java.util.Date; 02: 03: /** 04: This program tests the greeting thread by running two 05: threads in parallel. 06: */ 07:public class GreetingThreadTester 08: { 09:public static void main(String[] args) 10: { 11: GreetingRunnable r1 = new GreetingRunnable("Hello, World!"); 12: Thread t1 = new Thread(r1); 13: t1.start(); 14: } 15: } 16: 강원대학교
File GreetingThreadTester.java 01: import java.util.Date; 02: 03: /** 04: This program tests the greeting thread by running two 05: threads in parallel. 06: */ 07:public class GreetingThreadTester 08: { 09:public static void main(String[] args) 10: { 11: GreetingRunnable r1 = new GreetingRunnable("Hello, World!"); 12: GreetingRunnable r2 = new GreetingRunnable("Goodbye, World!"); 강원대학교
File GreetingThreadTester.java 13: Thread t1 = new Thread(r1); 14: Thread t2 = new Thread(r2); 15: t1.start(); 16: t2.start(); 17: } 18: } 19: 강원대학교
Output Thu Dec 28 23:12:03 PST 2004 Hello, World! Thu Dec 28 23:12:03 PST 2004 Goodbye, World! Thu Dec 28 23:12:04 PST 2004 Hello, World! Thu Dec 28 23:12:05 PST 2004 Hello, World! Thu Dec 28 23:12:04 PST 2004 Goodbye, World! Thu Dec 28 23:12:05 PST 2004 Goodbye, World! Thu Dec 28 23:12:06 PST 2004 Hello, World! Thu Dec 28 23:12:06 PST 2004 Goodbye, World! Thu Dec 28 23:12:07 PST 2004 Hello, World! Thu Dec 28 23:12:07 PST 2004 Goodbye, World! Thu Dec 28 23:12:08 PST 2004 Hello, World! Thu Dec 28 23:12:08 PST 2004 Goodbye, World! Thu Dec 28 23:12:09 PST 2004 Hello, World! Thu Dec 28 23:12:09 PST 2004 Goodbye, World! Thu Dec 28 23:12:10 PST 2004 Hello, World! Thu Dec 28 23:12:10 PST 2004 Goodbye, World! Thu Dec 28 23:12:11 PST 2004 Goodbye, World! Thu Dec 28 23:12:11 PST 2004 Hello, World! Thu Dec 28 23:12:12 PST 2004 Goodbye, World! Thu Dec 28 23:12:12 PST 2004 Hello, World! 강원대학교
Thread Scheduler • Thread scheduler는 각 쓰레드를 짧은 시간 (time slice) 동안 실행(activate)시킨다. • 쓰레드 실행 시간에는 작은 변이가 있을 수 있다(특히 입출력 동작시). • 쓰레드 실행 순서에는 어떤 보장도 없다. 강원대학교
Threads 종료 • 쓰레드는 run메소드가 완료되면 종료된다. • run 메소드가 완료되기 전에 쓰레드를 종료시키려면 그 쓰레드에 interrupt를 호출한다. GreetingRunnable r1 = new GreetingRunnable("Hello, World!"); Thread t1 = new Thread(r1); t1.start(); t.interrupt(); 강원대학교
Threads 종료 interrupt가 쓰레드를 강제로 종료시키는 것은 아니다. 쓰레드 자료구조 내의 특정 비트를 세트할 뿐이다. 쓰레드가 이를 감지하고 스스로 종료해야 한다. public void run() { for (int i = 1; i <= REPETITIONS && !Thread.interrupted(); i++) {Do work } Clean up} 인터럽트가 걸렸는지 스스로 확인하는 법 강원대학교
Threads 종료 • sleep하고 있는 중에 interrupt가 걸리면 InterruptedException이 발생됨 • Interrupt가 걸린 상태에서 sleep 메소드가 호출되는 경우에도 InterruptedException이 발생됨 • run 메소드 내에 sleep 문장이 있는 경우에는 interrupt가 걸렸는지 일일이 확인할 필요 없이 InterruptedException 발생 여부만을 감시하면 됨 강원대학교
Threads 종료 public void run() { try { for (int i = 1; i <= REPETITIONS; i++) { Do work and sleep} } catch (InterruptedException exception) { 아무것도 하지 않거나 (종료) 적절한 작업을 함 } Clean up} 강원대학교
public class MyRunnable implements Runnable { public void run() { try { System.out.println(1); Thread.sleep(1000); System.out.println(2); } catch (InterruptedException exception) { System.out.println(3); } System.out.println(4); }} Thread t = new Thread(new MyRunnable()); t.start(); t.interrupt(); 강원대학교
경쟁 조건 (Race Conditions) • 여러 쓰레드가 하나의 자료를 공유하며 자료를 업데이트 할 때 이 자료가 엉망이 될 수 있다. • 예: 여러 쓰레드가 하나의 은행계좌를 조작할 때 강원대학교
Sample Application • BankAccount 객체 구성 • 두 개의 쓰레드를 구성 • t1 = DepositRunnable 쓰레드 • t2 = WithdrawRunnable 쓰레드 • t1은 일정 시간 간격으로 $100를 10번 저축함 • t2는 일정 시간 간격으로 $100를 10번 인출함 강원대학교
DepositRunnable의 run 메소드 public void run() { try { for (int i = 1; i <= count; i++) { account.deposit(amount); Thread.sleep(DELAY); } } catch (InterruptedException exception) { }} public void deposit(double amount) { System.out.print("Depositing " + amount); double newBalance = balance + amount; System.out.println(", new balance is " + newBalance); balance = newBalance; } BankAccount의 deposit 메소드 강원대학교
WithdrawRunnable의 run 메소드 public void run() { try { for (int i = 1; i <= count; i++) { account.withdraw(amount); Thread.sleep(DELAY); } } catch (InterruptedException exception) { }} public void withdraw(double amount) { System.out.print("Withdrawing " + amount); double newBalance = balance - amount; System.out.println(", new balance is " + newBalance); balance = newBalance; } BankAccount의 withdraw 메소드 강원대학교
Sample Application Depositing 100.0, new balance is 100.0 Withdrawing 100.0, new balance is 0.0 Depositing 100.0, new balance is 100.0 Depositing 100.0, new balance is 200.0 Withdrawing 100.0, new balance is 100.0 . . . Withdrawing 100.0, new balance is 0.0 Depositing 100.0Withdrawing 100.0, new balance is 100.0, new balance is -100.0 강원대학교
balance = 0 Withdraw thread Deposit thread amount := 100; newBalance := balance + amount; --- 스위치 balance := newBalance; amount := 100; newBalance := balance - amount; balance := newBalance; balance = 100 BankAccount 객체는 하나! 인스턴스필드인 balance도 하나! 지역변수인 newBalance는 스레드마다 하나씩! 강원대학교
이렇게 한다 해도 해결되지 않음 public void deposit(double amount) { balance = balance + amount; System.out.print("Depositing " + amount + ", new balance is " + balance); } 붉은 색 문장이 기계적으로는 여러 단계에 걸쳐 실행됨 강원대학교
File BankAccountThreadTester.java 01: /** 02: This program runs two threads that deposit and withdraw 03: money from the same bank account. 04: */ 05:public class BankAccountThreadTester 06: { 07:public static void main(String[] args) 08: { 09: BankAccount account = new BankAccount(); 10:final double AMOUNT = 100; 11:final int REPETITIONS = 1000; 12: 13: DepositRunnable d = new DepositRunnable( 14: account, AMOUNT, REPETITIONS); 15: WithdrawRunnable w = new WithdrawRunnable( 16: account, AMOUNT, REPETITIONS); 강원대학교
File BankAccountThreadTester.java 17: 18: Thread t1 = new Thread(d); 19: Thread t2 = new Thread(w); 20: 21: t1.start(); 22: t2.start(); 23: } 24: } 25: 강원대학교
File DepositRunnable.java 01: /** 02: A deposit runnable makes periodic deposits to a bank // account. 03: */ 04:public class DepositRunnable implements Runnable 05: { 06: /** 07: Constructs a deposit runnable. 08: @param anAccount the account into which to deposit // money 09: @param anAmount the amount to deposit in each //repetition 10: @param aCount the number of repetitions 11: */ 12:public DepositRunnable(BankAccount anAccount, double anAmount, 13:int aCount) 14: { 강원대학교
File DepositRunnable.java 15: account = anAccount; 16: amount = anAmount; 17: count = aCount; 18: } 19: 20:public void run() 21: { 22:try 23: { 24:for (int i = 1; i <= count; i++) 25: { 26: account.deposit(amount); 27: Thread.sleep(DELAY); 28: } 29: } 30:catch (InterruptedException exception) {} 31: } 32: 강원대학교
File DepositRunnable.java 33:private static final int DELAY = 1; 34:private BankAccount account; 35:private double amount; 36:private int count; 37: } 강원대학교
File WithdrawalRunnable.java 01: /** 02: A withdraw runnable makes periodic withdrawals from a // bank account. 03: */ 04:public class WithdrawRunnable implements Runnable 05: { 06: /** 07: Constructs a withdraw runnable. 08: @param anAccount the account from which to withdraw money 09: @param anAmount the amount to deposit in each repetition 10: @param aCount the number of repetitions 11: */ 12:public WithdrawRunnable(BankAccount anAccount, double anAmount, 13:int aCount) 14: { 15: account = anAccount; 16: amount = anAmount; 17: count = aCount; 18: } 강원대학교
File WithdrawalRunnable.java 19: 20:public void run() 21: { 22:try 23: { 24:for (int i = 1; i <= count; i++) 25: { 26: account.withdraw(amount); 27: Thread.sleep(DELAY); 28: } 29: } 30:catch (InterruptedException exception) {} 31: } 32: 33:private static final int DELAY = 1; 34:private BankAccount account; 35:private double amount; 36:private int count; 37: } 강원대학교
File BankAccount.java 01: /** 02: A bank account has a balance that can be changed by 03: deposits and withdrawals. 04: */ 05:public class BankAccount 06: { 07: /** 08: Constructs a bank account with a zero balance. 09: */ 10:public BankAccount() 11: { 12: balance = 0; 13: } 14: 15: /** 16: Deposits money into the bank account. 17: @param amount the amount to deposit 18: */ 강원대학교
File BankAccount.java 19:public void deposit(double amount) 20: { 21: System.out.print("Depositing " + amount); 22:double newBalance = balance + amount; 23: System.out.println(", new balance is " + newBalance); 24: balance = newBalance; 25: } 26: 27: /** 28: Withdraws money from the bank account. 29: @param amount the amount to withdraw 30: */ 31:public void withdraw(double amount) 32: { 33: System.out.print("Withdrawing " + amount); 34:double newBalance = balance - amount; 35: System.out.println(", new balance is " + newBalance); 36: balance = newBalance; 37: } 강원대학교
File BankAccount.java 38: 39: /** 40: Gets the current balance of the bank account. 41: @return the current balance 42: */ 43:public double getBalance() 44: { 45:return balance; 46: } 47: 48:private double balance; 49: } 강원대학교
File BankAccount.java Output Depositing 100.0, new balance is 100.0 Withdrawing 100.0, new balance is 0.0 Depositing 100.0, new balance is 100.0 Withdrawing 100.0, new balance is 0.0 . . . Withdrawing 100.0, new balance is 400.0 Depositing 100.0, new balance is 500.0 Withdrawing 100.0, new balance is 400.0 Withdrawing 100.0, new balance is 300.0 일반적으로는 위와 같이 올바른 출력이 나오지만 잘 못된 출력이 나오는 수가 있음 강원대학교
객체 접근 동기화(Synchronizing Object Access) • 두 개 이상이 쓰레드가 하나의 객체에 접근할 때 그 시간을 통제하여 경쟁조건을 해결하는 것 • Lock인터페이스와 Lock인터페이스를 구현한 클래스를 이용 • ReentrantLock: 흔히 쓰이는 Lock 클래스 강원대학교
balance = 0 Withdraw thread Deposit thread amount := 100; newBalance := balance + amount; --- 스위치 balance := newBalance; amount := 100; newBalance := balance - amount; balance := newBalance; balance = 100 세 문장을 실행하는 중간에 스위칭이 일어나면서 문제 발생! 세 문장이 한꺼번에 실행되면 문제가 발생하지 않음 강원대학교
balance = 0 Withdraw thread Deposit thread 자물쇠를 채움 amount := 100; newBalance := balance + amount; balance := newBalance; 자물쇠를 품 자물쇠를 채움 amount := 100; newBalance := balance - amount; balance := newBalance; 자물쇠를 품 balance = 0 어떤 쓰레드가 자물쇠를 채우면 다른 쓰레드가 그 자물쇠를 사용할 수 없음 세 문장을 실행 전에 자물쇠에 lock을 걸고 세 문장의 실행을 마친 후 lock을 풂 세 문장이 한번에 모두 실행되도록 보장 강원대학교
Synchronizing Object Access • 통상 공유자료에 접근하는 메소드를 갖는 클래스에 Lock 객체 삽입 public class BankAccount { public BankAccount() { balanceChangeLock = new ReentrantLock(); . . . } . . . private Lock balanceChangeLock;} ReentrantLock: 흔히 쓰이는 Lock 클래스 강원대학교
Synchronizing Object Access • 공유 자료에 접근하는 코드를 lock과 unlock으로 둘러쌈 balanceChangeLock.lock(); Code that manipulates the shared resource balanceChangeLock.unlock(); 강원대학교
Synchronizing Object Access • 쓰레드가 lock호출에 성공하면 unlock을 호출할 때까지 Lock을 점유함 • 다른 쓰레드가 Lock을 점유하고 있는 동안 lock을 호출하는 쓰레드는 일시적으로 비활성화됨(deactivated) • Thread scheduler는 주기적으로 쓰레드를 활성화시켜 다시 Lock을 점유할 기회를 줌 강원대학교
Synchronizing Object Access • lock과unlock사이에서 예외가 발생하면 unlock이 영영 실행되지 못함 • 이런 문제를 해결하기 위해 unlock을 finally절에 넣음 강원대학교
Synchronizing Object Access public void deposit(double amount) { balanceChangeLock.lock(); try { System.out.print("Depositing " + amount); double newBalance = balance + amount; System.out.println(", new balance is " + newBalance); balance = newBalance; } finally { balanceChangeLock.unlock(); } } * withraw 메소드도 같은 요령으로 처리 강원대학교
Deadlock(교착상태) • 쓰레드들이 다른 쓰레드의 작업이 마무리 되기를 기다리고 있으나 실제로는 서로 맞물려 더 이상 진행하지 못하는 상태 강원대학교
Deadlock(교착상태) 예 • 잔고가 음수로 떨어지지 않도록 하고 싶은 경우 public void withdraw(double amount) { balanceChangeLock.lock(); try { while (balance < amount) Wait for the balance to grow . . . } finally { balanceChangeLock.unlock(); }} 이 부분에서 sleep을 호출하면 lock을 계속 점유하므로 다른 쓰레드가 deposit할 수 없게 됨 – deadlock! 강원대학교
Deadlock(교착상태)을 방지하는 법 • Condition 객체 사용 • Condition 객체는 특정 Lock 객체와 연계됨 • Condition을 사용하면 쓰레드가 일시적으로 Lock을 놓았다가 나중에 다시 점유하게 됨 강원대학교