9장. 스레드

9-1 변경가능한 공유 데이터에 접근할 때 동기화하라

  • 동기화는 불안정한 쓰레드 객체의 상태에 어떤 메소드도 접근할수 없게 만든다.
  • 안전한-스레드-객체(Thread-safe)의 상태를 다른 안전한-스레드-객체(Thread-safe)의 상태로 만든다.
  • 병행(concurrent)과 병렬(pareallel)은 다르다

병행과 병렬

  • 다음과 같은 소스에서 문제가 발생한다.

public class Bank {
	
	int moneyBox;
	
	public void putMoney(int money) {
		moneyBox = money;
	}
	
	public int getMoney() {
		int rs = moneyBox;
		moneyBox = 0;
		return rs;
	}
	
	public static void main(String[] args) {
		Bank bank = new Bank();
		
		Person person1 = new Person("선웅", 10000, bank);
		Person person2 = new Person("용운", 4000, bank);
		Person person3 = new Person("도훈", 500000, bank);
		
		person1.start();
		person2.start();
		person3.start();
		
	}
}



public class Person extends Thread {
	
	private String name;
	private int myMoney=0;
	private Bank bank;
	
	
	public Person(String name, int myMoney, Bank bank) {
		this.name = name;
		this.myMoney = myMoney;
		this.bank = bank;
	}

	public void run() {
		
			while(true) {
				
				
				bank.putMoney(myMoney);
				System.out.println(name + "님께서 " + myMoney + "원을 입금하셨습니다.");
				
				myMoney = bank.getMoney();
				System.out.println(name + "님께서 " + myMoney + "원을 출금하셨습니다.");
				
				System.out.println("현재 " + name + "님께서는 " + myMoney + "원을 가지고 있습니다.");
			}
			
	
		
	}
	
}


내 돈을 찾아주세효~

선웅님께서 10000원을 입금하셨습니다.
선웅님께서 10000원을 출금하셨습니다.
현재 선웅님께서는 10000원을 가지고 있습니다.
선웅님께서 10000원을 입금하셨습니다.
선웅님께서 10000원을 출금하셨습니다.
현재 선웅님께서는 10000원을 가지고 있습니다.
선웅님께서 10000원을 입금하셨습니다.
선웅님께서 10000원을 출금하셨습니다.
현재 선웅님께서는 10000원을 가지고 있습니다.
선웅님께서 10000원을 입금하셨습니다.
선웅님께서 10000원을 출금하셨습니다.
현재 선웅님께서는 10000원을 가지고 있습니다.
선웅님께서 10000원을 입금하셨습니다.
용운님께서 4000원을 입금하셨습니다.
도훈님께서 500000원을 입금하셨습니다.
선웅님께서 500000원을 출금하셨습니다.
용운님께서 0원을 출금하셨습니다.
도훈님께서 0원을 출금하셨습니다.
현재 선웅님께서는 500000원을 가지고 있습니다.
현재 용운님께서는 0원을 가지고 있습니다.
현재 도훈님께서는 0원을 가지고 있습니다.
선웅님께서 500000원을 입금하셨습니다.
용운님께서 0원을 입금하셨습니다.
도훈님께서 0원을 입금하셨습니다.
선웅님께서 0원을 출금하셨습니다.
생략...

  • violatile을 사용해서 안정화 시킬순 있지만, 많은 JVM에서 volatile이 완벽하게 동작하지 않는다.
  • 그래서~ 동기화를 위해서 synchronized 키워드를 사용한다.

public class Person extends Thread {
	
	private String name;
	private int myMoney=0;
	private Bank bank;
	
	static private Object police = new Object(); // 경찰 한명 둔다.
	
	public Person(String name, int myMoney, Bank bank) {
		this.name = name;
		this.myMoney = myMoney;
		this.bank = bank;
	}

	public void run() {
		synchronized(police) { // 경찰이 감시한다. 한번에 한사람만 접근가능하다.
			bank.putMoney(myMoney);
			System.out.println(name + "님께서 " + myMoney + "원을 입금하셨습니다.");
			
			myMoney = bank.getMoney();
			System.out.println(name + "님께서 " + myMoney + "원을 출금하셨습니다.");
			
			System.out.println("현재 " + name + "님께서는 " + myMoney + "원을 가지고 있습니다.");
		}
	}
}


경찰을 둔다

도훈님께서 500000원을 입금하셨습니다.
도훈님께서 500000원을 출금하셨습니다.
현재 도훈님께서는 500000원을 가지고 있습니다.
선웅님께서 10000원을 입금하셨습니다.
선웅님께서 10000원을 출금하셨습니다.
현재 선웅님께서는 10000원을 가지고 있습니다.
용운님께서 4000원을 입금하셨습니다.
용운님께서 4000원을 출금하셨습니다.
현재 용운님께서는 4000원을 가지고 있습니다.
도훈님께서 500000원을 입금하셨습니다.
도훈님께서 500000원을 출금하셨습니다.
현재 도훈님께서는 500000원을 가지고 있습니다.
선웅님께서 10000원을 입금하셨습니다.
선웅님께서 10000원을 출금하셨습니다.
현재 선웅님께서는 10000원을 가지고 있습니다.
용운님께서 4000원을 입금하셨습니다.
용운님께서 4000원을 출금하셨습니다.
현재 용운님께서는 4000원을 가지고 있습니다.
도훈님께서 500000원을 입금하셨습니다.
도훈님께서 500000원을 출금하셨습니다.
현재 도훈님께서는 500000원을 가지고 있습니다.
선웅님께서 10000원을 입금하셨습니다.
선웅님께서 10000원을 출금하셨습니다.
현재 선웅님께서는 10000원을 가지고 있습니다.
용운님께서 4000원을 입금하셨습니다.
용운님께서 4000원을 출금하셨습니다.
현재 용운님께서는 4000원을 가지고 있습니다.
도훈님께서 500000원을 입금하셨습니다.
도훈님께서 500000원을 출금하셨습니다.
현재 도훈님께서는 500000원을 가지고 있습니다.
선웅님께서 10000원을 입금하셨습니다.
선웅님께서 10000원을 출금하셨습니다.
현재 선웅님께서는 10000원을 가지고 있습니다.
용운님께서 4000원을 입금하셨습니다.
용운님께서 4000원을 출금하셨습니다.
현재 용운님께서는 4000원을 가지고 있습니다.
도훈님께서 500000원을 입금하셨습니다.
도훈님께서 500000원을 출금하셨습니다.
현재 도훈님께서는 500000원을 가지고 있습니다.
선웅님께서 10000원을 입금하셨습니다.
선웅님께서 10000원을 출금하셨습니다.
현재 선웅님께서는 10000원을 가지고 있습니다.
생략...

9-2 지나친 동기화는 피하라

  • 교착상태(deadlock)

public class Thread1 {
	public static void main(String[] args) {
		
		Tool spoon = new Tool("숟가락");
		Tool chopsticks  = new Tool("젓가락");		
		
		EaterThread thread1 = new EaterThread("용운", spoon, chopsticks);
		EaterThread thread2 = new EaterThread("선웅", chopsticks, spoon);
		
		thread1.start();		
		thread2.start();
		
		
	}
}


public class Tool {
	private final String name;

	public Tool(String name) {	
		this.name = name;
	}
	
	public String toString() {
		return "[ " + name + " ]";
	}
}


public class EaterThread extends Thread {
	private String name;
	private final Tool lefthand;
	private final Tool righthand;
	
	public EaterThread(String name, Tool lefthand, Tool righthand) {	
		this.name = name;
		this.lefthand = lefthand;
		this.righthand = righthand;
	}
	
	public void run() {
		while(true)
			eat();
	}
	
	public void eat() {
		synchronized (lefthand) {
			System.out.println(name + "이가 " + lefthand + "을  (왼손) 으로 사용합니다.");
			synchronized (righthand) {
				System.out.println(name + "이가 " + righthand + "을 (오른손)  으로 사용합니다.");
				System.out.println(name + "이가 식사를 합니다. 냠!냠!");
				System.out.println(name + "이가 " + righthand + "을 (오른손) 으로 내려놓습니다.");
			}			
			System.out.println(name + "이가 " + lefthand + "을 (왼손) 으로 내려놓습니다.");
		}
	}

}


결과

용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 사용합니다.
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 사용합니다.
용운이가 식사를 합니다. 냠!냠!
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 사용합니다.
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 사용합니다.
용운이가 식사를 합니다. 냠!냠!
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 사용합니다.
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 사용합니다.
용운이가 식사를 합니다. 냠!냠!
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 사용합니다.
선웅이가 WEBSTUDY: 젓가락을 (왼손) 으로 사용합니다.

<<<<< 프로그램이 멈춘다.(교착상태 발생) >>>>>>>

  • 교착상태가 제거된 클래스

public class Thread1 {
	public static void main(String[] args) {
		
		Tool spoon = new Tool("숟가락");
		Tool chopsticks  = new Tool("젓가락");
		
		/*
		 * 선웅이용
		 */
		Tool spoon2 = new Tool("숟가락"); // 추가
		Tool chopsticks2  = new Tool("젓가락");// 추가	
		 
		EaterThread thread1 = new EaterThread("용운", spoon, chopsticks);
		EaterThread thread2 = new EaterThread("선웅", chopsticks2, spoon2); // 변경
		
		thread1.start();		
		thread2.start();
		
		
	}
}

교착상태가 제거된 결과

용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 사용합니다.
선웅이가 WEBSTUDY: 젓가락을 (왼손) 으로 사용합니다.
용운이가 식사를 합니다. 냠!냠!
선웅이가 WEBSTUDY: 숟가락을 (오른손) 으로 사용합니다.
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 내려놓습니다.
선웅이가 식사를 합니다. 냠!냠!
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 내려놓습니다.
선웅이가 WEBSTUDY: 숟가락을 (오른손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 사용합니다.
선웅이가 WEBSTUDY: 젓가락을 (왼손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 사용합니다.
선웅이가 WEBSTUDY: 젓가락을 (왼손) 으로 사용합니다.
용운이가 식사를 합니다. 냠!냠!
선웅이가 WEBSTUDY: 숟가락을 (오른손) 으로 사용합니다.
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 내려놓습니다.
선웅이가 식사를 합니다. 냠!냠!
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 내려놓습니다.
선웅이가 WEBSTUDY: 숟가락을 (오른손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 사용합니다.
선웅이가 WEBSTUDY: 젓가락을 (왼손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 사용합니다.
선웅이가 WEBSTUDY: 젓가락을 (왼손) 으로 사용합니다.
용운이가 식사를 합니다. 냠!냠!
선웅이가 WEBSTUDY: 숟가락을 (오른손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 사용합니다.
선웅이가 WEBSTUDY: 젓가락을 (왼손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 사용합니다.
선웅이가 WEBSTUDY: 젓가락을 (왼손) 으로 사용합니다.
용운이가 식사를 합니다. 냠!냠!
선웅이가 WEBSTUDY: 숟가락을 (오른손) 으로 사용합니다.
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 내려놓습니다.
선웅이가 식사를 합니다. 냠!냠!
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 내려놓습니다.
선웅이가 WEBSTUDY: 숟가락을 (오른손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 사용합니다.
중략 .....
선웅이가 WEBSTUDY: 젓가락을 (왼손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 사용합니다.
선웅이가 WEBSTUDY: 젓가락을 (왼손) 으로 사용합니다.
용운이가 식사를 합니다. 냠!냠!
선웅이가 WEBSTUDY: 숟가락을 (오른손) 으로 사용합니다.
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 내려놓습니다.
선웅이가 식사를 합니다. 냠!냠!
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 내려놓습니다.
선웅이가 WEBSTUDY: 숟가락을 (오른손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 사용합니다.
선웅이가 WEBSTUDY: 젓가락을 (왼손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 사용합니다.
선웅이가 WEBSTUDY: 젓가락을 (왼손) 으로 사용합니다.
용운이가 식사를 합니다. 냠!냠!
선웅이가 WEBSTUDY: 숟가락을 (오른손) 으로 사용합니다.
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 내려놓습니다.
선웅이가 식사를 합니다. 냠!냠!
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 내려놓습니다.
선웅이가 WEBSTUDY: 숟가락을 (오른손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 사용합니다.
선웅이가 WEBSTUDY: 젓가락을 (왼손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 사용합니다.
선웅이가 WEBSTUDY: 젓가락을 (왼손) 으로 사용합니다.
용운이가 식사를 합니다. 냠!냠!
선웅이가 WEBSTUDY: 숟가락을 (오른손) 으로 사용합니다.
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 내려놓습니다.
선웅이가 식사를 합니다. 냠!냠!
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 내려놓습니다.
선웅이가 WEBSTUDY: 숟가락을 (오른손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 사용합니다.
선웅이가 WEBSTUDY: 젓가락을 (왼손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 사용합니다.
선웅이가 WEBSTUDY: 젓가락을 (왼손) 으로 사용합니다.
용운이가 식사를 합니다. 냠!냠!
선웅이가 WEBSTUDY: 숟가락을 (오른손) 으로 사용합니다.
용운이가 WEBSTUDY: 젓가락을 (오른손) 으로 내려놓습니다.
선웅이가 식사를 합니다. 냠!냠!
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 내려놓습니다.
선웅이가 WEBSTUDY: 숟가락을 (오른손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 숟가락을 (왼손) 으로 사용합니다.
선웅이가 WEBSTUDY: 젓가락을 (왼손) 으로 내려놓습니다.
용운이가 WEBSTUDY: 젓가락을 (오른손)

  • 교착상태(deadlock)에 빠지지 않으려면 동기화 메소드나 동기화 블록 안에서 클라이언트에게 제어를 절대 넘겨서는 안된다.
    • 동기화 영역에 재정의 할수 있는 public이나 protected메소드를 호출하지 마라.
  • shynchronized 지시자는 프로그램성능을 약 20%까지 떨어트릴수있다.
  • Vector, Hashtable는 Thread-safe(동기화 된)클래스이다.

9-3 wait메소드는 반복문 안에서만 호출하라

  • 쓰레드의 건전성(liveness)을 지킬 수 있다.
    (건전성이란? 대기상태(wait)에 들어가버린 쓰레드는 언제 다시 깨어날지(notifyAll)는 아무도 모른다.)
  • 다른 쓰레드가 실수나 악의로 대기중인 스레드에 notify메소드를 호출할 수도 있으니, wait가 들어간 메소드는 public으로 선언하는걸 주의하라.
  • 대기상태(wait)에서 쓰레드를 깨울때(notify), notifyAll을 사용해야만 하는경우도 있다.
  • 대기상태인 쓰레드가 저절로 깨어날수도 있다.
    쓰레드의 제어는 항상 신뢰할 수 없다.

9-4 스레드 스케줄러에 의존하지 마라

  • 프로그램의 정확성을 쓰레드 스케줄러로 제어하려 하지마라. 프로그램의 이식성과 강건성이 떨어진다.
  • 쓰레드 스케줄러를 보조하는 Thread.yield메소드나 쓰레드 우선순위를 믿지마라.

9-5 스레드 안전성을 문서화하라

  • 안정성 수준
    • 변경불가(immutable): 이 클래스의 인스턴스는 절대로 변하지 않으므로, 클라이언트 상수로 생각하면된다.
    • 쓰레드-안전(Thread-safe): 이 클래스의 인스턴스는 변경가능하지만 모든 메소드 내부에서 충분히 동기화하고 있으므로,
      외부에서 동기화하지 않더라도 다중 스레드에서 안정하기 쓸 수 있다.
    • 상황에 따라 쓰레드-안전(conditionally thread-safe): 위의 쓰레드 안전과 유사하지만, 일부는 안전하지 않다.
      ex> HashTable, Vector들은 Thread-safe이지만, Iterator를 사용할때는 Thread-safe가 아니다.
    • 쓰레드-호환(Thread-compatible): 이 클래스의 인스턴스는 각 메소드를 호출할 때 외부에서 동기화 해주어야 다중 쓰레드
      환경에서 안전하게 쓸수 있다.
      ex> ArrayList, HashMap같은 것들은 사용할때 동기화(synchronized)를 해주어야 한다.
    • 쓰레드-적대(Thread-hostile): 이미 자바에는 거의 없지만, 동기화(synchronized)를 시켜도 쓰레드에서 안전하지않다.
  • 서비스 거부 공격(DoS)



public class Person_DoS extends Thread {
	
	private String name;
	private int myMoney=0;
	private Bank bank;
	
//	static private Object police = new Object(); // 경찰을 해고했다.
	
	public Person_DoS(String name, int myMoney, Bank bank) {
		this.name = name;
		this.myMoney = myMoney;
		this.bank = bank;
	}

	public void setMyMoney(int money) {
		this.myMoney = money;
	}
	
	public void setMyName(String name) {
		this.name = name;
	}
	
	public void run() {
		synchronized(name) { // name 객체를 Lock했다.
			/**
			 * 이 구간에서는 현재 이 메소드를 제외한 다른 메소드에서는 name을 접근할 수 없다!
			 * 
			 * DoS( Denial-of-Service). name 서비스를 거부하고있다.
			 * 
			 */
			bank.putMoney(myMoney);
			System.out.println(name + "님께서 " + myMoney + "원을 입금하셨습니다.");
			
			myMoney = bank.getMoney();
			System.out.println(name + "님께서 " + myMoney + "원을 출금하셨습니다.");
			
			System.out.println("현재 " + name + "님께서는 " + myMoney + "원을 가지고 있습니다.");
		} // name 객체가 unLock된다.
	}
}

 

9-6 스레드 그룹을 쓰지 마라

  • ThreadGroup은 시스템쓰레드그룹을 루트(root)로 하여 쓰레드를 관리할수 있는 형태이다.
  • 그러나 이 쓰레드 그룹을 사용하려면 enumerate같은 Thread-safe가 아닌 메소드를 호출하여야 하기때문에, 스레드 안에서는 안전하지않다.
  • 사용하지마라.


public class ThreadGroupTest {
	
	public static void main(String[] args) {
		ThreadGroup tg1 = Thread.currentThread().getThreadGroup(); // 시스템 쓰레드
		ThreadGroup tg2 = new ThreadGroup(tg1, "ThreadGroup2"); // 쓰레드 그룹 (부모:시스템 쓰레드)
		ThreadGroup tg3 = new ThreadGroup("ThreadGroup3"); // 쓰레드 그룹
		
		Thread t1 = new Thread(); // 일반 쓰레드 (자동으로 시스템쓰레드가 부모가 된다)
		Thread t2 = new Thread(tg1, "Thread-tg1"); // 쓰레드1그룹 소속 쓰레드
		Thread t3 = new Thread(tg2, "Thread-tg2"); // 쓰레드2그룹 소속 쓰레드
		Thread t4 = new Thread(tg3, "Thread-tg3"); // 쓰레드3그룹 소속 쓰레드
		
		
		/*
		 * 쓰레드 정보를 출력한다.
		 */
		System.out.println("this: "+Thread.currentThread());
		System.out.println("  t1: "+t1);
		System.out.println("  t2: "+t2);
		System.out.println("  t3: "+t3);
		System.out.println("  t4: "+t4);
		
		
		/*
		 * 쓰레드 그룹정보를 출력한다.
		 */
		System.out.println("this: "+Thread.currentThread().getThreadGroup()+", "
							+Thread.currentThread().getThreadGroup().activeCount()+", "
							+Thread.currentThread().getThreadGroup().activeGroupCount());
		System.out.println(" t1: "+t1.getThreadGroup());
		System.out.println(" t2: "+t2.getThreadGroup());
		System.out.println(" t3: "+t3.getThreadGroup());
		System.out.println("t4:"+t4.getThreadGroup()+","+t4.getThreadGroup().getName());
		  					Thread.currentThread().getThreadGroup().list();

	}
	
}

결과

this: Thread[WEBSTUDY:main,5,main]
t1: Thread[WEBSTUDY:Thread-0,5,main]
t2: Thread[WEBSTUDY:Thread-tg1,5,main]
t3: Thread[WEBSTUDY:Thread-tg2,5,ThreadGroup2]
t4: Thread[WEBSTUDY:Thread-tg3,5,ThreadGroup3]
this: java.lang.ThreadGroup[WEBSTUDY:name=main,maxpri=10], 1, 2
t1: java.lang.ThreadGroup[WEBSTUDY:name=main,maxpri=10]
t2: java.lang.ThreadGroup[WEBSTUDY:name=main,maxpri=10]
t3: java.lang.ThreadGroup[WEBSTUDY:name=ThreadGroup2,maxpri=10]
t4:java.lang.ThreadGroup[WEBSTUDY:name=ThreadGroup3,maxpri=10],ThreadGroup3
java.lang.ThreadGroup[WEBSTUDY:name=main,maxpri=10]
Thread[WEBSTUDY:main,5,main]
java.lang.ThreadGroup[WEBSTUDY:name=ThreadGroup2,maxpri=10]
java.lang.ThreadGroup[WEBSTUDY:name=ThreadGroup3,maxpri=10]

문서에 대하여