220622_인터넷 강의_스레드 클래스의 여러 메소드

창고·2022년 10월 21일
0

티스토리에 저장했던 글을 옮겼습니다.
https://mrcocoball.tistory.com/95

1. Thread 클래스의 여러 메소드

(1) Thread 우선순위

  • Thread.MIN_PRIORITY(=1) ~ Thread.MAX_PRIORITY(=10)
  • 디폴트는 Thread.NORMAL_PRIORITY(=5)
  • 우선 순위가 높을수록 CPU의 배분을 받을 확률 높아짐
  • setPriority() / getPriority()
class PriorityThread extends Thread {

	   public void run() {

	      int sum = 0; // 초기값

	      Thread t = Thread.currentThread(); // 스레드 참조용 객체 생성
	      System.out.println(t + "start");

	      for(int i=0; i<=1000000; i++) {

	         sum += i;
	      }

	      System.out.println(t.getPriority() + "end"); // 스레드 우선순위 가져오기
	   }
	}

	public class PriorityTest {

	   public static void main(String[] args) {

	      int i;
	      for(i=Thread.MIN_PRIORITY; i<= Thread.MAX_PRIORITY; i++) {

	         PriorityThread pt = new PriorityThread(); // 스레드 객체 생성
	         pt.setPriority(i); // 1~10까지 i 증가시켜서 우선 순위 세트
	         pt.start();

	      }
	   }
	}
}	

(2) join()

  • 동시에 두 개 이상의 Thread 실행 시 다른 Thread의 결과를 참조하여 실행해야 할 경우 join() 사용
  • join() 함수 호출 Thread가 Not-Runnable 상태로 변환
  • 다른 Thread의 수행이 끝나야 Runnable 상태로 돌아옴
public class JoinTest extends Thread {

	   int start;
	   int end;
	   int total;

	   public JoinTest(int start, int end) {
	      this.start = start;
	      this.end = end;
	   }

	   public void run() {

	      int i;
	      for (i = start; i <= end; i++) {
	         total += i;
	      }
	   }


	   public static void main(String[] args) {

	      JoinTest jt1 = new JoinTest(1, 50);
	      JoinTest jt2 = new JoinTest(51, 100);

	      jt1.start();
	      jt2.start();

	      try{
	         jt1.join();
	         jt2.join();

	      } catch (InterruptedException e) {
	         System.out.println(e);
	      }
	      

	      int lastTotal = jt1.total + jt2.total;

	      System.out.println("jt1.total = " + jt1.total);
	      System.out.println("jt2.total = " + jt2.total);

	      System.out.println("lastTotal = " + lastTotal);
	   }
	}

(3) interrupt()

  • 다른 Thread에 예외를 발생시키는 interrupt를 보냄 (InterruptedException)
  • Thread가 join(), sleep(), wait() 등에 의해 Not-Runnable 상태일 때 interrupt() 호출 시 다시 Runnable 상태가 됨

2. Thread 종료하기

  • 무한 반복의 경우 while(flag)의 flag 변수 값을 false로 바꾸어 종료시킴
public class TerminateThread extends Thread {

	   private boolean flag = false; // flag의 true/false 여부로 종료
	   int i;

	   public TerminateThread(String name) {
	      super(name);
	   }

	   public void run() {

	      while(!flag) { // !flag가 true일 경우 = flag가 false인 경우 반복
	         try {
	            sleep(100);
	         } catch (InterruptedException e) {
	            e.printStackTrace();
	         }
	      }

	      System.out.println(getName() + " end"); // while문 탈출 시 getName() 후 메시지 처리

	   }

	   public void setFlag(boolean flag) { // flag를 stop / true 받아서 처리
	      this.flag = flag;
	   }


	   public static void main(String[] args) throws IOException {

	      TerminateThread threadA = new TerminateThread("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); // A 입력 시 threadA의 flag를 true로 전환
	         } else if (in == 'B') {
	            threadB.setFlag(true); // B 입력 시 threadB의 flag를 true로 전환
	         } else if (in == 'C') {
	            threadC.setFlag(true); // C 입력 시 threadC의 flag를 true로 전환
	         } else if (in == 'M') { // M 입력시 모든 thread의 flag를 true로 전환
	            threadC.setFlag(true);
	            threadC.setFlag(true);
	            threadC.setFlag(true);
	            break; // break로 while문 탈출
	         } else {
	            System.out.println("type again");
	         }
	      }

	      System.out.println("main end");
	   }
}

3. 멀티 Thread 프로그래밍에서의 동기화

(1) 임계 영역 (critical section) 과 semaphore

  • sempaphore는 특별한 형태의 시스템 객체, get/release 두 가지 기능이 있음
  • 한 순간 하나의 thread만이 semaphore를 얻을 수 있고 나머지는 대기(blocking) 상태가 됨
  • semaphore를 얻은 thread 만이 임계 영역에 들어갈 수 있음

(2) 동기화 (synchronization)

  • 두 개의 thread가 같은 객체에 접근할 경우, 동시에 접근함으로서 오류가 발생함
  • 동기화는 임계 영역에 접근한 경우 공유 자원을 lock 하여 다른 thread의 접근을 제어
  • 동기화를 잘못 구현할 경우 deadlock에 빠질 수 있음

(3) 동기화 메소드 / 동기화 블록

  • 동기화 메소드 (synchronized 메소드) : 객체의 메소드에 synchronized 키워드 사용
    현재 이 메소드가 속해 있는 객체에 lock을 걸게 됨.
    가급적 해당 메소드에서 다른 동기화 메소드를 호출하지 않도록 해야 함
  • 예제
class Bank {

	   private int money = 10000;

	   public void saveMoney(int save) {
		   
		   synchronized (this) { // 해당 객체를 block
			   
			      int m = this.getMoney(); // 현재 금액 가져오기

			      try {
			         Thread.sleep(3000);
			      } catch (InterruptedException e) {
			         e.printStackTrace();
			      }

			      setMoney( m + save ); // 현재 금액에 save 만큼 저장
			   
		   }

	   }

	   public synchronized void minusMoney(int minus) {

	      int m = this.getMoney(); // 현재 금액 가져오기

	      try {
	         Thread.sleep(2000);
	      } catch (InterruptedException e) {
	         e.printStackTrace();
	      }

	      setMoney ( m - minus ) ; // 현재 금액에 minus 만큼 차감

	   }

	   public int getMoney() {
	      return money;
	   }

	   public void setMoney(int money) {
	      this.money = money;
	   }
	}

	// Rico 스레드
	class Rico extends Thread {

	   public void run() {
	      System.out.println("리코가 돈을 저축합니다");
	      SyncMain.myBank.saveMoney(3000);
	      System.out.println("saveMoney(3000): " + SyncMain.myBank.getMoney() );
	   }
	}

	// Mari 스레드
	class Mari extends Thread {

	   public void run() {
	      System.out.println("마리가 돈을 씁니다");
	      SyncMain.myBank.minusMoney(1000);
	      System.out.println("minusMoney(1000): " + SyncMain.myBank.getMoney() );
	   }
	}


	public class SyncMain {

	   public static Bank myBank = new Bank(); // 정적 멤버인 myBank 생성, 공용 리소스

	   public static void main(String[] args) throws InterruptedException {

	      Rico r = new Rico();
	      r.start();

	      Thread.sleep(200);

	      Mari m = new Mari();
	      m.start();
	   }
	}

  • 동기화 블록 (synchronized 블록) : 현재 객체 혹은 다른 객체를 lock으로 만듬
sychronized(참조형 수식) {

   수행문;
}
	   public void saveMoney(int save) {
		   
		   synchronized (this) { // 해당 객체를 block
			   
			      int m = this.getMoney(); // 현재 금액 가져오기

			      try {
			         Thread.sleep(3000);
			      } catch (InterruptedException e) {
			         e.printStackTrace();
			      }

			      setMoney( m + save ); // 현재 금액에 save 만큼 저장
			   
		   }

	   }

4. wait() / notify() 를 활용한 동기화 프로그래밍

(1) wait()과 notify()

  • 리소스가 어떤 조건에서 더 이상 유효하지 않은 경우 리소스를 기다리기 위해 thread가 wait() 상태가 됨
  • wait() 상태가 된 thread는 notify()가 호출될 때까지 기다림
  • 유효 리소스가 생길 시 notify()가 호출되고 wait() 중인 thread 중 무작위로 하나를 재시작함
  • notifyAll()이 호출될 경우 wait() 중인 모든 thread가 재시작됨
  • 이 경우 유효한 리소스만큼의 thread가 수행되고 그 리소스를 가지지 못한 thread는 다시 wait() 상태로
  • 자바에서는 notifyAll() 사용을 권장
  • notify()를 사용할 경우
class FastLibrary {

	   public ArrayList<String> shelf = new ArrayList<String>();

	   public FastLibrary() {

	      shelf.add("벽쿵일기 1");
	      shelf.add("벽쿵일기 2");
	      shelf.add("벽쿵일기 3");
	   }

	   public synchronized String lendBook() throws InterruptedException {

	      Thread t = Thread.currentThread(); // 현재 스레드 참조

	      if(shelf.size() == 0) {
	         System.out.println(t.getName() + "는 책이 없어서 대기합니다");
	         wait();
	         System.out.println(t.getName() + "의 대기가 끝났습니다");
	      }
	      String book = shelf.remove(0); // shelf의 첫 책을 꺼냄
	      System.out.println(t.getName() + "가 " + book + "을 빌렸습니다");

	      return book;

	   }

	   public synchronized void returnBook(String book) {

	      Thread t = Thread.currentThread(); // 현재 스레드 참조

	      shelf.add(book); // 책을 추가 (입고)
	      notify(); // notifyAll() 진행
	      System.out.println(t.getName() + "가 빌린 " + book + " 책이 반환되었습니다");
	   }

	}

	class Student extends Thread {

	   public void run() {

	      try {

	         String title = LibraryMain.library.lendBook(); // library 객체에 대해 lendBook() 하여 책 빌리기
	         if(title==null) return; // title이 없을 경우 = lendBook한 책이 없을 경우 return하여 스레드 종료
	         sleep(5000);
	         LibraryMain.library.returnBook(title); // 위의 return을 거치지 않았다면 returnBook 하여 책 반납

	      } catch (InterruptedException e) {
	         System.out.println(e);
	      }

	   }
	}

	public class LibraryMain {

	   public static FastLibrary library = new FastLibrary(); // 정적 객체 library 생성, 공용 리소스
	   public static void main(String[] args) {

	      Student std1 = new Student();
	      Student std2 = new Student();
	      Student std3 = new Student();
	      Student std4 = new Student();
	      Student std5 = new Student();
	      Student std6 = new Student();

	      std1.start();
	      std2.start();
	      std3.start();
	      std4.start();
	      std5.start();
	      std6.start();
	   }
	
	
}

  • notifyAll() 사용 시 (lendBook(), returnBook() 메소드 수정)
	   public synchronized void returnBook(String book) {

	      Thread t = Thread.currentThread(); // 현재 스레드 참조

	      shelf.add(book); // 책을 추가 (입고)
	      notifyAll(); // notifyAll() 진행
	      System.out.println(t.getName() + "가 빌린 " + book + " 책이 반환되었습니다");
	   }
profile
공부했던 내용들을 모아둔 창고입니다.

0개의 댓글