-
31. 멀티스레드2개발자 수업/Java 2021. 10. 25. 18:17
package kr.co.ezenac.wait2; /* * 책을 못빌릴 경우에 wait() 호출해서 기다림 * 책이 반납되어 빌릴 수 있게 될 경우 깨워주는게 notify()임 */ import java.util.ArrayList; class EzenLibrary { public ArrayList<String> books = new ArrayList<>(); public EzenLibrary() { books.add("인텔리제이 IEDA1"); books.add("인텔리제이 IEDA2"); books.add("인텔리제이 IEDA3"); } // 책 빌림 public synchronized String lendBook() throws InterruptedException { Thread t = Thread.currentThread(); if(books.size() == 0) { System.out.println(t.getName() + " waiting start"); wait(); //현재 이 스레드를 기다리게 함 (Object의 메서드) System.out.println(t.getName() + " waiting end"); } String title = books.remove(0); System.out.println(t.getName() + " : " + title + " lend"); //어떤 스레드가 빌리는지 확인 return title; } //책 반납 public synchronized void returnBook(String title) { Thread t = Thread.currentThread(); System.out.println(t.getName() + " : " + title + " return"); //어떤 스레드가 반납하는지 확인 books.add(title); notify(); //wait()하고 있는 스레드를 (하나씩) 깨워줌 (Object의 메서드) } } class Student extends Thread { public Student(String name) { super(name); } @Override public void run() { try { String title = LibraryTest.library.lendBook(); if(title == null) { System.out.println(getName() + " : 빌리지 못했음"); return; } sleep(5000); //(5초 동안 책을 읽음) LibraryTest.library.returnBook(title); } catch (InterruptedException e) { System.out.println(e.getMessage()); //e.printStackTrace(); } } } public class LibraryTest { public static EzenLibrary library = new EzenLibrary(); //shared Resource public static void main(String[] args) { Student stu1 = new Student("학생1"); //리소스는 3개로 한정적인데 사용하려는 스레드는 더 많음 Student stu2 = new Student("학생2"); Student stu3 = new Student("학생3"); Student stu4 = new Student("학생4"); Student stu5 = new Student("학생5"); Student stu6 = new Student("학생6"); stu1.start(); stu2.start(); stu3.start(); stu4.start(); stu5.start(); stu6.start(); } }
//Tread 클래스 상속하여 만들기 class MyThread extends Thread { @Override public void run() { int i; for(i=0; i<=50; i++) { System.out.print(i + "\t"); try { Thread.sleep(10); } catch (InterruptedException e) { System.out.println(e.getMessage()); //e.printStackTrace(); } } } } public class ThreadTest { public static void main(String[] args) { System.out.println(Thread.currentThread() + "start"); MyThread thread1 = new MyThread(); MyThread thread2 = new MyThread(); thread1.start(); thread2.start(); System.out.println(thread1); System.out.println(thread2); System.out.println(Thread.currentThread() + "end"); } }
//Runnable 인터페이스 구현하여 만들기 //자바는 다중 상속이 허용되지 않으므로 이미 다른 클래스를 상속한 경우 thread를 만들기 위해 Runnable interface를 구현하도록 함 class MyThread2 implements Runnable { @Override public void run() { int i; for(i=0; i<=50; i++) { System.out.print(i + "\t"); try { Thread.sleep(10); } catch (InterruptedException e) { System.out.println(e.getMessage()); //e.printStackTrace(); } } } } public class ThreadTest2 { public static void main(String[] args) { System.out.println(Thread.currentThread() + "start"); MyThread2 runner1 = new MyThread2(); Thread thread1 = new Thread(runner1); thread1.start(); MyThread2 runner2 = new MyThread2(); Thread thread2 = new Thread(runner2); thread2.start(); System.out.println(thread1); System.out.println(thread2); System.out.println(Thread.currentThread() + "end"); } }
1. multi-threading
1) 여러 thread가 동시에 수행되는 프로그래밍. 여러 작업이 동시에 실행되는 효과
2) thread는 각각 자신만의 작업 공간을 가짐 : context
3) 각 thread 사이에는 공유하는 자원이 있을 수 있음 (자바에서는 static instance)
4) 여러 thread가 자원을 공유하여 작업이 수행되는 경우
서로 자원을 차지하려는 race condition이 발생할 수 있음
5) 이렇게 여러 thread가 공유하는 자원 중 경쟁이 발생하는 부분을 critical section이라 함
6) critical section에 대한 동기화(일종의 순차적 수행)를 구현하지 않으면 오류가 발생할 수 있음
2. Thread Status(스레드 상태)
(그림 첨부)
3. Join()
1) 다른 스레드의 종료를 기다림
2) 동시에 두 개 이상의 Thread가 실행될 때 다른 Thread의 결과를 참조하여 실행해야 하는 경우 join() 함수를 사용함
3) join() 함수를 호출한 Thread가 not-runnable 상태가 됨
4) 다른 Thread의 수행이 끝나면 runnable 상태로 돌아옴//1부터 50, 51부터 100까지의 합을 구하는 두 개의 thread 생성 public class JoinTest extends Thread{ int start; int end; int total; public JoinTest(int start, int end) { this.start = start; this.end = end; } @Override public void run() { int i; for(i=start; i<=end; i++) { total += i; } } public static void main(String[] args) { JoinTest joinTest1 = new JoinTest(1, 50); JoinTest joinTest2 = new JoinTest(51, 100); joinTest1.start(); joinTest2.start(); int lastTotal = joinTest1.total + joinTest2.total; System.out.println("joinTest1.total = " + joinTest1.total); System.out.println("joinTest2.total = " + joinTest2.total); System.out.println("lastTotal = " + lastTotal); } }
//1부터 50, 51부터 100까지의 합을 구하는 두 개의 thread 생성 public class JoinTest2 extends Thread{ int start; int end; int total; public JoinTest2(int start, int end) { this.start = start; this.end = end; } @Override public void run() { int i; for(i=start; i<=end; i++) { total += i; } } public static void main(String[] args) { JoinTest2 joinTest1 = new JoinTest2(1, 50); JoinTest2 joinTest2 = new JoinTest2(51, 100); joinTest1.start(); joinTest2.start(); try { joinTest1.join(); //스레드 상태 제어 joinTest2.join(); //joinTest1, joinTest2에 join()을 main스레드가 호출하고 main스레드는 not running(기다림) 상태임 } catch (InterruptedException e) { e.printStackTrace(); } int lastTotal = joinTest1.total + joinTest2.total; System.out.println("joinTest1.total = " + joinTest1.total); System.out.println("joinTest2.total = " + joinTest2.total); System.out.println("lastTotal = " + lastTotal); } }
4. interrupt()
1) 다른 Thread에 예외를 발생시키는 interrupt을 보냄
2) Thread가 sleep(시간), wait(), join() 함수에 의해 Not Runnable 상태일 때, interrupt() 메소드를 호출하면 다시 Runnable 상태가 될 수 있음public class Sleep extends Thread { @Override public void run() { int i; for(i=0; i<100; i++) { System.out.println(i); try { sleep(50); } catch (InterruptedException e) { System.out.println(e); //e.printStackTrace(); } } } public static void main(String[] args) { Sleep sleep = new Sleep(); sleep.start(); System.out.println("end"); } }
public class Sleep2 extends Thread { @Override public void run() { int i; for(i=0; i<100; i++) { System.out.println(i); try { sleep(50); } catch (InterruptedException e) { System.out.println(e); System.out.println("Wake Up"); //e.printStackTrace(); } } } public static void main(String[] args) { Sleep2 sleep = new Sleep2(); sleep.start(); sleep.interrupt(); System.out.println("end"); } }
5. Thread 종료하기
1) 무한 반복의 경우 while(flag)의 flag 변수 값을 false로 바꾸어 종료를 시킴package kr.co.ezenac.terminate; import java.io.IOException; public class TerminateThread extends Thread { private boolean flag = false; int i; public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } public TerminateThread(String name) { super(name); } @Override public void run() { while(!flag) { try { sleep(100); //0.1초간 잠들었다가(not running) 깨었다가(running)를 계속 반복 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("end"); } public static void main(String[] args) throws IOException { TerminateThread threadA = new TerminateThread("A"); //스레드 이름 A TerminateThread threadB = new TerminateThread("B"); TerminateThread threadC = new TerminateThread("C"); threadA.start(); threadB.start(); threadC.start(); //계속 실행 중인 상태를 멈추게 함 int in; while(true) { in = System.in.read(); if(in == 'A') { threadA.setFlag(true); //threadA를 멈추게 함 } else if(in == 'B') { threadB.setFlag(true); //threadB를 멈추게 함 } else if(in == 'C') { threadC.setFlag(true); //threadC를 멈추게 함 } else if(in == 'M') { threadA.setFlag(true); threadB.setFlag(true); threadC.setFlag(true); break; } } System.out.println("main end"); } }
6. 멀티 Thread 프로그래밍에서 동기화
1) critical section : 두 개 이상의 thread가 동시에 접근하는 경우 문제가 생길 수 있기 때문에 동시에 접근할 수 없게 해야 함
2) 한 순간 오직 하나의 thread만이 공유자원을 얻을 수 있고, 나머지 thread들은 대기(blocking) 상태가 됨
7. 동기화(synchronization)
1) 두 개의 thread가 같은 객체에 접근할 경우 동시에 접근함으로써 오류가 발생
2) 동기화는 임계영역에 접근한 경우 공유자원을 lock하여 다른 thread의 접근을 제어
3) 동기화를 잘못 구현하면 deadlock에 빠질 수 있음
8. 자바에서 synchronized 메소드나 synchronized 블럭을 사용
1) synchronized 블럭
- 현재 객체 또는 다른 객체를 lock으로 만듦
synchronized(참조형 수식){
수행문;
}
2) synchronized 메소드
- 객체의 메소드에 synchronized 키워드 사용
- 현재 이 메소드가 속해있는 객체에 lock을 걺package kr.co.ezenac.sync; class Bank { //critical section : 두 개 이상의 스레드가 동시에 접근할 수 없는 영역 private int money = 10000; public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } public void saveMoney(int save) { int m = this.getMoney(); try { Thread.sleep(3000); //저축하는데 3초가 걸림(쉼) } catch (InterruptedException e) { e.printStackTrace(); } setMoney(m + save); } public void minusMoney(int minus) { int m = this.getMoney(); try { Thread.sleep(200); //소비하는데 0.2초가 걸림 } catch (InterruptedException e) { e.printStackTrace(); } setMoney(m - minus); } } class Ryu extends Thread { @Override public void run() { System.out.println("start save"); SyncTest.myBank.saveMoney(3000); //critical section => 각 스레드에 share됨 System.out.println("After save money : " + SyncTest.myBank.getMoney()); } } class RyuWife extends Thread { @Override public void run() { System.out.println("start minus"); SyncTest.myBank.minusMoney(1000); System.out.println("After minus money : " + SyncTest.myBank.getMoney()); } } public class SyncTest { public static Bank myBank = new Bank(); public static void main(String[] args) throws InterruptedException { Ryu ryu = new Ryu(); ryu.start(); Thread.sleep(200); //0.2초 멈춘 후에 RyuWife ryuWife = new RyuWife(); ryuWife.start(); } }
package kr.co.ezenac.sync2; // 두 개의 스레드가 이 은행을 동시에 접근하는 경우 동기화 처리 class Bank { //critical section : 두 개 이상의 스레드가 동시에 접근할 수 없는 영역 private int money = 10000; public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } public synchronized void saveMoney(int save) { //Bank에 lock이 걸림 int m = this.getMoney(); try { Thread.sleep(3000); //저축하는데 3초가 걸림(쉼) } catch (InterruptedException e) { e.printStackTrace(); } setMoney(m + save); } public synchronized void minusMoney(int minus) { int m = this.getMoney(); try { Thread.sleep(200); //소비하는데 0.2초가 걸림 } catch (InterruptedException e) { e.printStackTrace(); } setMoney(m - minus); } } class Ryu extends Thread { @Override public void run() { System.out.println("start save"); SyncTest.myBank.saveMoney(3000); //critical section => 각 스레드에 share됨 System.out.println("After save money : " + SyncTest.myBank.getMoney()); } } class RyuWife extends Thread { @Override public void run() { System.out.println("start minus"); SyncTest.myBank.minusMoney(1000); System.out.println("After minus money : " + SyncTest.myBank.getMoney()); } } public class SyncTest { public static Bank myBank = new Bank(); public static void main(String[] args) throws InterruptedException { Ryu ryu = new Ryu(); ryu.start(); Thread.sleep(200); //0.2초 멈춘 후에 RyuWife ryuWife = new RyuWife(); ryuWife.start(); } }
package kr.co.ezenac.sync3; // 두 개의 스레드가 이 은행을 동시에 접근하는 경우 동기화 처리 class Bank { //critical section : 두 개 이상의 스레드가 동시에 접근할 수 없는 영역 private int money = 10000; public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } public void saveMoney(int save) { //Bank에 lock이 걸림 synchronized (this) { int m = this.getMoney(); try { Thread.sleep(3000); //저축하는데 3초가 걸림(쉼) } catch (InterruptedException e) { e.printStackTrace(); } setMoney(m + save); } } public synchronized void minusMoney(int minus) { int m = this.getMoney(); try { Thread.sleep(200); //소비하는데 0.2초가 걸림 } catch (InterruptedException e) { e.printStackTrace(); } setMoney(m - minus); } } class Ryu extends Thread { @Override public void run() { System.out.println("start save"); SyncTest.myBank.saveMoney(3000); //critical section => 각 스레드에 share됨 System.out.println("After save money : " + SyncTest.myBank.getMoney()); } } class RyuWife extends Thread { @Override public void run() { System.out.println("start minus"); SyncTest.myBank.minusMoney(1000); System.out.println("After minus money : " + SyncTest.myBank.getMoney()); } } public class SyncTest { public static Bank myBank = new Bank(); public static void main(String[] args) throws InterruptedException { Ryu ryu = new Ryu(); ryu.start(); Thread.sleep(200); //0.2초 멈춘 후에 RyuWife ryuWife = new RyuWife(); ryuWife.start(); } }
9. wait() / notify(), notifyAll() 메소드를 활용한 동기화 프로그래밍
1) 리소스가 어떤 조건에서 더 이상 유효하지 않은 경우 리소스를 기다리기 위해 Thread가 wait() 상태가 됨 (Not Runnable)
2) wait() 상태가 된 Thread는 notify()가 호출될 때까지 기다림
3) 유효한 자원이 생기면 notify() 호출되고 wait()하고 있는 Thread 중 무작위로 하나의 Thread를 재시작 하도록 함 (Runnable)
4) notifyAll()이 호출되는 경우 wait()하고 있는 모든 Thread가 재시작 됨
- 이 경우 유효한 리소스만큼의 Thread만이 수행될 수 있고 자원을 갖지 못한 Thread의 경우는 다시 wait() 상태로 만듦
- notifyAll() 메소드의 사용을 권장package kr.co.ezenac.wait; import java.util.ArrayList; class EzenLibrary { public ArrayList<String> books = new ArrayList<>(); public EzenLibrary() { books.add("인텔리제이 IEDA1"); books.add("인텔리제이 IEDA2"); books.add("인텔리제이 IEDA3"); books.add("인텔리제이 IEDA4"); books.add("인텔리제이 IEDA5"); books.add("인텔리제이 IEDA6"); } // 책 빌림 public String lendBook() { String title = books.remove(0); Thread t = Thread.currentThread(); System.out.println(t.getName() + " : " + title + "lend"); //어떤 스레드가 빌리는지 확인 return title; } //책 반납 public void returnBook(String title) { books.add(title); Thread t = Thread.currentThread(); System.out.println(t.getName() + " : " + title + "return"); //어떤 스레드가 반납하는지 확인 } } class Student extends Thread { public Student(String name) { super(name); } @Override public void run() { try { String title = LibraryTest.library.lendBook(); sleep(5000); //(5초 동안 책을 읽음) LibraryTest.library.returnBook(title); } catch (InterruptedException e) { e.printStackTrace(); } } } public class LibraryTest { public static EzenLibrary library = new EzenLibrary(); //shared Resource public static void main(String[] args) { Student stu1 = new Student("학생1"); Student stu2 = new Student("학생2"); Student stu3 = new Student("학생3"); Student stu4 = new Student("학생4"); Student stu5 = new Student("학생5"); stu1.start(); stu2.start(); stu3.start(); stu4.start(); stu5.start(); } }
package kr.co.ezenac.wait2; /* * 책을 못빌릴 경우에 wait() 호출해서 기다림 * 책이 반납되어 빌릴 수 있게 될 경우 깨워주는게 notify()임 */ import java.util.ArrayList; class EzenLibrary { public ArrayList<String> books = new ArrayList<>(); public EzenLibrary() { books.add("인텔리제이 IEDA1"); books.add("인텔리제이 IEDA2"); books.add("인텔리제이 IEDA3"); } // 책 빌림 public synchronized String lendBook() throws InterruptedException { Thread t = Thread.currentThread(); if(books.size() == 0) { System.out.println(t.getName() + " waiting start"); wait(); //현재 이 스레드를 기다리게 함 (Object의 메서드) System.out.println(t.getName() + " waiting end"); } String title = books.remove(0); System.out.println(t.getName() + " : " + title + " lend"); //어떤 스레드가 빌리는지 확인 return title; } //책 반납 public synchronized void returnBook(String title) { Thread t = Thread.currentThread(); System.out.println(t.getName() + " : " + title + " return"); //어떤 스레드가 반납하는지 확인 books.add(title); notify(); //wait()하고 있는 스레드를 (하나씩) 깨워줌 (Object의 메서드) } } class Student extends Thread { public Student(String name) { super(name); } @Override public void run() { try { String title = LibraryTest.library.lendBook(); if(title == null) { System.out.println(getName() + " : 빌리지 못했음"); return; } sleep(5000); //(5초 동안 책을 읽음) LibraryTest.library.returnBook(title); } catch (InterruptedException e) { System.out.println(e.getMessage()); //e.printStackTrace(); } } } public class LibraryTest { public static EzenLibrary library = new EzenLibrary(); //shared Resource public static void main(String[] args) { Student stu1 = new Student("학생1"); //리소스는 3개로 한정적인데 사용하려는 스레드는 더 많음 Student stu2 = new Student("학생2"); Student stu3 = new Student("학생3"); Student stu4 = new Student("학생4"); Student stu5 = new Student("학생5"); Student stu6 = new Student("학생6"); stu1.start(); stu2.start(); stu3.start(); stu4.start(); stu5.start(); stu6.start(); } }
package kr.co.ezenac.wait3; /* * 책을 못빌릴 경우에 wait() 호출해서 기다림 * 책이 반납되어 빌릴 수 있게 될 경우 깨워주는게 notify()임 * * notify() => 스레드가 한 개만 깨어나므로 경우에 따라서는 못 깨어나는 경우도 있을 수 있음 * notifyAll() => 모두 다 깨어나게 됨 => 하나만 반납했는데 모두가 빌리는 상황이 되면 문제가 됨 */ import java.util.ArrayList; class EzenLibrary { public ArrayList<String> books = new ArrayList<>(); public EzenLibrary() { books.add("인텔리제이 IEDA1"); books.add("인텔리제이 IEDA2"); books.add("인텔리제이 IEDA3"); } // 책 빌림 public synchronized String lendBook() throws InterruptedException { Thread t = Thread.currentThread(); while(books.size() == 0) { System.out.println(t.getName() + " waiting start"); wait(); //현재 이 스레드를 기다리게 함 (Object의 메서드) System.out.println(t.getName() + " waiting end"); } if(books.size() > 0) { String title = books.remove(0); System.out.println(t.getName() + " : " + title + " lend"); //어떤 스레드가 빌리는지 확인 return title; } else { return null; } } //책 반납 public synchronized void returnBook(String title) { Thread t = Thread.currentThread(); System.out.println(t.getName() + " : " + title + " return"); //어떤 스레드가 반납하는지 확인 books.add(title); notifyAll(); //wait()하고 있는 스레드를 다 깨워줌 (Object의 메서드) } } class Student extends Thread { public Student(String name) { super(name); } @Override public void run() { try { String title = LibraryTest.library.lendBook(); if(title == null) { System.out.println(getName() + " : 빌리지 못했음"); return; } sleep(5000); //(5초 동안 책을 읽음) LibraryTest.library.returnBook(title); } catch (InterruptedException e) { System.out.println(e.getMessage()); //e.printStackTrace(); } } } public class LibraryTest { public static EzenLibrary library = new EzenLibrary(); //shared Resource public static void main(String[] args) { Student stu1 = new Student("학생1"); //리소스는 3개로 한정적인데 사용하려는 스레드는 더 많음 Student stu2 = new Student("학생2"); Student stu3 = new Student("학생3"); Student stu4 = new Student("학생4"); Student stu5 = new Student("학생5"); Student stu6 = new Student("학생6"); stu1.start(); stu2.start(); stu3.start(); stu4.start(); stu5.start(); stu6.start(); } }
'개발자 수업 > Java' 카테고리의 다른 글
문제 2 - Chap09 / kr.co.ezenac.assignment (0) 2021.10.26 문제 1 - Chap13 / kr.co.ezenac.review02 (0) 2021.10.26 30. Review (0) 2021.10.25 29. 멀티스레드 (0) 2021.10.22 28. IO입출력 (0) 2021.10.20