1. 프로세스와 쓰레드

- 프로세스(Process) : 실행 중인 프로그램(Program), 자원(resources)과 쓰레드로 구성, 공장에 비유된다.

- 쓰레드(Thread) : 프로세스 내에서 실제 작업을 수행한다, 모든 프로세스는 하나 이상의 쓰레드를 가지고 있다, 일꾼에 비유된다.

- 싱글쓰레드 프로세스 = 자원 + thread

- 멀티쓰레드 프로세스 = 자원 + thread + thread +  . . .


2. 멀티쓰레딩

- 하나의 프로세스 내에서 여러 쓰레드가 동시에 작업을 수행하는 것

- 아주 짧은 시간 동안 여러 작업을 번갈아 가며 수행함으로써 동시에 여러 작업이 수행되는 것처럼 보이게 하는 것

- CPU의 사용률을 향상시킨다.

- 자원을 보다 효율적으로 사용할 수 있다.

- 사용자에 대한 응답성이 향상된다.

- 작업이 분리되어 코드가 간결해진다.

- 동기화(synchronization), 교착상태(deadlock) 같은 문제들을 고려해야 한다.

- 각 쓰레드가 효율적으로 고르게 실행될 수 있게 해야 한다.


3. 쓰레드의 구현과 실행

- 구현하는 두가지 방법은 Thread 클래스를 상속받는 방법과 Runnable 인터페이스를 구현하는 방법이 있다.

- 다른 클래스를 상속받을때에는 Runnable 인터페이스를 구현하는 방법이 일반적이다.

- 한번 사용한 쓰레드는 다시 재사용할 수  없다.

class Thread1 extends Thread {
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println(getName()); // 조상 Thread의 getName()을 호출
		} // for
	} // run
} // Thread1

class Thread1_1 implements Runnable {
	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			// Thread.currentThread(); // 현재 실행 중인 Thread를 반환한다.
			System.out.println(Thread.currentThread().getName());
		} // for
	} // run
} // Thread1_1

public class ThreadTest {
	public static void main(String[] args) {
		Thread1 t1 = new Thread1();
		
		Thread1_1 t = new Thread1_1();
		Thread t2 = new Thread(t); // 생성자 Thread(Runnable target)
		
		t1.start();
		t2.start();
	} // main
} // ThreadTest

/*
 * 결과
 * 
 * Thread-0
 * Thread-0
 * Thread-0
 * Thread-0
 * Thread-0
 * Thread-1
 * Thread-1
 * Thread-1
 * Thread-1
 * Thread-1
 */

4. start(), run()

- run()을 호출하는 것은 생성된 쓰레드를 실행시키는 것이 아니라 단순히 클래스에 속한 메서드 하나를 호출하는 것이다.

- start()는 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택을 생성한 다음에 run()을 호출해서 저장되게 한다.

- 실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다.

class Human2 extends Thread {
	String name;

	public Human2(String name) {
		this.name = name;
	} // Human2 생성자
	
	public void run() {
		for (int i = 1; i <= 10; i++) {
			System.out.println(name + " 이(가) " + "음식" + " 을(를) 먹어요 " + i);
			
			try {
				Thread.sleep(500);
			} catch (Exception e) {
				e.printStackTrace();
			} // try - catch
		} // for
	} // run
} // Human

public class ThreadTest2 {
	public static void main(String[] args) {
		Human2 human1 = new Human2("갱짱");
		Human2 human2 = new Human2("이갱짱");
		
		//human1.run();
		//human2.run();
		human1.start();
		human2.start();
	} // main
} // ThreadTest

5. 싱글쓰레드와 멀티쓰레드

- 싱글쓰레드 : 두 개의 작업을 하나의 쓰레드로 처리하는 경우

- 멀티쓰레드 : 두 개의 작업을 두개의 쓰레드로 처리하는 경우

- 단순히 CPU만을 사용하는 계산작업이라면 작업전환(context switching)에 시간이 안걸리는 싱글쓰레드가 효율적이다.

- 하지만 CPU이외의 자원을 사용하는 작업의 경우에는 멀티쓰레드 프로세스가 더 효율적이다.


6. 쓰레드의 우선순위(Proiority)

- 우선순위라는 속성(멤버변수)을 가지고 있는데, 이 우선순위의 값에 따라 쓰레드가 얻는 실행시간이 달라진다.

- 수행하는 작업의 중요도에 따라 쓰레드의 우선순위를 서로 다르게 지정하여 특정 쓰레드가 더 많은 작업시간을 갖도록 할 수 있다.

- 쓰레드가 가질 수 있는 우선순위의 범위는 1 ~ 10 이며, 숫자가 높을수록 우선순위가 높다.

- 쓰레드의 우선순위는 쓰레드를 생성한 쓰레드로부터 상속받는다.

// void setPriority(int newPriority) : 쓰레드의 우선 순위를 지정한 값으로 변경한다.
// int getPriority() : 쓰레드의 우선순위를 반환한다.
// public static final int MAX_PRIORITY = 10 : 최대우선순위
// public static final int MAX_PRIORITY = 1 : 최소우선순위
// public static final int MAX_PRIORITY = 5 : 보통우선순위

class Thread_1 extends Thread {
	public void run() {
		for (int i = 0; i < 300; i++) {
			System.out.print("-");
			for (int x = 0; x < 10000000; x++); // 작업을 지연시키기 위한 for 문
		} // for
	} // run
} // Thread_1

class Thread_2 extends Thread {
	public void run() {
		for (int i = 0; i < 300; i++) {
			System.err.print("|");
			for (int x = 0; x < 10000000; x++); // 작업을 지연시키기 위한 for 문
		} // for
	} // run
} // Thread_2

public class ThreadPriorityTest {
	public static void main(String[] args) {
		Thread_1 th1 = new Thread_1();
		Thread_2 th2 = new Thread_2();
		
		th2.setPriority(8);
		
		System.out.println("Priority of th1 (-) : " + th1.getPriority());
		System.out.println("Priority of th2 (|) : " + th2.getPriority());
		
		th1.start();
		th2.start();
	} // main
} // ThreadPriorityTest

/*
 * 결과
 * 
 * Priority of th1 (-) : 5
 * Priority of th2 (|) : 7
 * -||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 */

6. 쓰레드 그룹(thread group)

- 서로 관련된 쓰레드를 그룹으로 묶어서 다루기 위한 것.

- 쓰레드 그룹에 다른 쓰레드 그룹을 포함 시킬 수 있다. 보안상의 이유로 도입된 개념이다.

- 자신이 속한 쓰레드 그룹이나 하위 쓰레드 그룹은 변경할 수 있지만 다른 쓰레드 그룹의 쓰레드를 변경할 수는 없다.

- 모든 쓰레드는 반드시 하나의 쓰레드 그룹에 포함되어 있어야 한다.

- 쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 'main쓰레드 그룹'에 속한다.

- 쓰레드를 쓰레드 그룹에 포함시키려면 Thread의 생성자를 이용해야한다.


7. 데몬 쓰레드(daemon thread)

- 일반 쓰레드(non-daemon thread)의 작업을 돕는 보조적인 역할을 수행한다.

- 일반 쓰레드가 모두 종료되면 자동적으로 종료된다.

- 가비지컬렉터, 워드프로세서의 자동저장, 화면자동갱신 등에 사용된다.

- 무한루프와 조건문을 이용해서 실행 후 대기하다가 특정 조건이 만족되면 작업을 수행하고 다시 대기하도록 작성한다.

- boolean is Daemon() : 쓰레드가 데몬 쓰레드인지 확인한다. 데몬 쓰레드이면 true를 반환한다.

- void setDaemon(boolean on) : 쓰레드를 데몬 쓰레드로 또는 사용자 쓰레드로 변경한다. 매개변수 on의 값을 true로 지정하면 데몬 쓰레드가 된다.

- setDaemon은 반드시 start()를 호출하기 전에 실행되어야 한다.


8. 쓰레드의 실행제어

- 쓰레드의 생명 주기


- 쓰레드의 스케줄링과 관련된 메서드

 반환형

 메서드

 설명 

 void

 interrupt() 

 sleep()이나 join()에 의해 일시정지상태인 쓰레드를 실행 대기 상태로 만든다.

 해당 쓰레드에서는 InterruptedException이 발생함으로써 일시정지상태를 벗어나게 된다. 

 void
 void

 void

 join()

 join(long millis)  

 join(long millis, int nanos)

 지정된 시간동안 쓰레드가 실행되도록 한다.

 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속한다.

 void

 resume()

 suspend()에 의해 일시정지상태에 있는 쓰레드를 실행대기상태로 만든다. 

 static void
 static void

 sleep(long millis)

 sleep(long millis, int nanos)

  지정된 시간(천분의 일초 단위)동안 쓰레드를 일시정지시킨다.

  지정한 시간이 지나고 나면, 자동적으로 다시 실행대기상태가 된다.

 void

 stop()

 쓰레드를 즉시 종료시킨다. 교착상태(dead-lock)에 빠지기 쉽기 때문에 deprecated 되었다. 

 void

 suspend()

 쓰레드를 일시 정지시킨다. resume()을 호출하면  

 static void

 void yield()

 실행 중에 다른 쓰레드에게 양보(yield)하고 실행대기상태가 된다. 


- 쓰레드의 상태

 상태

 설명 

 NEW 

 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태 

 RUNNABLE 

 실행 중 또는 실행 가능한 상태 

 BLOCKED

 동기화블럭에 의해서 일시정지된 상태(LOCK이 풀릴 때 까지 기다리는 상태) 

 WAITING, TIMED_WAITING 

 쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrunnable) 일시 정지상태, TIMED_WAITING은 일시정지시간이 지정된 경우를 의미 

 TERMINATED

 쓰레드의 작업이 종료된 상태 


- 쓰레드를 생성하고 start()를 호출하면 바로 실행되는 것이 아니라 실행대기열(큐와같은 구조)에 저장되어 자신의 차례가 될 때까지 기다려야 한다.

- 실행대기상태에 있다가 자신의 차례가 되면 실행상태가 된다.

- 주어진 실행시간이 다되거나 yield()를 만나면 다시 실행대기상태가 되고 다음 차례의 쓰레드가 실행상태가 된다.

- 실행 중에 suspend(), sleep(), wait(), join(), I/O block에 의해 일시정지상태가 될 수 있다. I/O block은 입출력작업에서 발생하는 지연상태를 말한다.

- 지정된 일시정지시간이 다되거나(time-out), notify(), resume(), interrupt()가 호출되면 일시정지 상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다리게 된다.

- 실행을 모두 마치거나 stop()이 호출되면 쓰레드는 소멸된다.

// 한 쓰레드의 작업의 중간에 다른 쓰레드의 작업이 필요할 때 join()을 사용한다.

class ThreadJoin_1 extends Thread {
	public void run() {
		for (int i = 0; i < 300; i++) {
			System.out.print("-");
		} // for
	} // run
} // ThreadJoin_1

class ThreadJoin_2 extends Thread {
	public void run() {
		for (int i = 0; i < 300; i++) {
			System.out.print("+");
		} // for
	} // run
} // ThreadJoin_1

public class ThreadJoinTest {
	public static void main(String[] args) {
		ThreadJoin_1 th1 = new ThreadJoin_1();
		ThreadJoin_2 th2 = new ThreadJoin_2();
		
		th1.start();
		try {
			th1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} // try - catch
		System.out.println();
		th2.start();
	} // main
} // ThreadJoinTest

/*
 * 결과
 * 
 * ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
// sleep()는 쓰레드를 지정된 시간동안 일시정지 상태가 되도록 한다. 천분의 일초 단위이다.
// suspend()가 호출되면 쓰레드는 일시정지 상태가 되고
// resume()이 호출되면 다시 실행 대기 상태가 된다.
// stop()을 호출하면 쓰레드는 즉시 종료된다.

// interrupt()는 InterruptedException을 발생시켜서, sleep(), join(), wait()에 의해 일시정지상태인 쓰레드를 실행 대기 상태로 만든다.
// 그러나, 호출되었을 때 sleep(), join(), wait()에 의한 일시정지상태가 아니라면 아무 일도 일어나지 않는다.

class MyTherdTest implements Runnable {
	boolean suspended = false;
	boolean stopped = false;
	
	Thread th;
	
	MyTherdTest(String name) {
		th = new Thread(this, name); // Thread(Runnable r, String name)
	} // MyMyTherdTest 생성자
	
	@Override
	public void run() {
		String name = Thread.currentThread().getName();
		
		while (!stopped) {
			if (!suspended) {
				System.out.println(name);
				try {
					Thread.sleep(1000);
				} catch (Exception e) {
					System.out.println(name + " - interrupted");
				} // try - catch
			} else {
				Thread.yield();
			} // if
		} // while
		System.out.println(name + " - stopped");
	} // run
	
	public void suspend() {
		suspended = true;
		th.interrupt();
		System.out.println("interrupt() in suspend()");
	} // suspend
	
	public void resume() {
		suspended = false;
	} // resume
	
	public void stop() {
		stopped = true;
		th.interrupt();
		System.out.println("interrupt() in stop()");
	} // stop
	
	public void start() {
		th.start();
	} // start
} // MyTheradTest

public class TheradTest {
	public static void main(String[] args) {
		MyTherdTest th1 = new MyTherdTest("●");
		MyTherdTest th2 = new MyTherdTest("★★");
		MyTherdTest th3 = new MyTherdTest("◆◆◆");
		
		th1.start();
		th2.start();
		th3.start();
		
		try {
			Thread.sleep(2000);
			th1.suspend();
			Thread.sleep(2000);
			th2.suspend();
			Thread.sleep(3000);
			th1.resume();
			Thread.sleep(3000);
			th1.stop();
			th2.stop();
			Thread.sleep(2000);
			th3.stop();
		} catch (Exception e) {
			e.printStackTrace();
		} // try - catch
	} // main
} // TheradTest

/*
 * 실행 결과 
●
◆◆◆
★★
●
◆◆◆
★★
●
interrupt() in suspend()
◆◆◆
★★
● - interrupted
★★
◆◆◆
◆◆◆
interrupt() in suspend()
★★
★★ - interrupted
◆◆◆
◆◆◆
◆◆◆
●
◆◆◆
●
◆◆◆
●
◆◆◆
interrupt() in stop()
interrupt() in stop()
★★ - stopped
● - stopped
◆◆◆
◆◆◆
interrupt() in stop()
◆◆◆ - interrupted
◆◆◆ - stopped
*/

9. 쓰레드의 동기화

- 멀티쓰레드 프로세스의 경우 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업을 하기 때문에 동기화가 필요하다.

- 한 번에 하나의 쓰레드만 객체에 접근할 수 있도록 객체에 락(lock)을 걸어서 데이터의 일관성을 유지한다.

- synchronized 키워드를 통해 작업과 관련된 공유데이터에 lock를 건다.

- 특정 객체에 lock을 걸 때 : synchronized(객체의 참조변수) { /* ... */ }

- 메서드에 lock을 걸 때 : public synchronized void calcSum() { /* ... */ }

class ATM implements Runnable {
	private long depositeMoney = 10000;

	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			try {
				Thread.sleep(1000);
			} catch (Exception e) {
				e.printStackTrace();
			} // try - catch

			if (getDepositeMoney() <= 0)
				break;

			synchronized (this) {
				withDraw(1000);
			} // synchronized
		} // for
	} // run

	public void withDraw(long howMuch) {
		if (getDepositeMoney() > 0) {
			depositeMoney -= howMuch;
			System.out.print(Thread.currentThread().getName() + " , ");
			System.out.printf("잔액 : %,d 원 %n", getDepositeMoney());
		} else {
			System.out.print(Thread.currentThread().getName() + " , ");
			System.out.println("잔액이 부족합니다");
		} // if
	} // withDraw

	public long getDepositeMoney() {
		return depositeMoney;
	} // getDepositeMoney
} // ATM

public class SynchronizedTest {
	public static void main(String[] args) {
		ATM atm = new ATM();

		Thread mother = new Thread(atm, "mother");
		Thread son = new Thread(atm, "son");

		mother.start();
		son.start();
	} // main
} // SynchronizedTest

10. wait(), notify()

- 동기화의 효율을 높이기 위해 사용한다. 

- Object 클래스의 정의되어 있으므로, 모든 객체에서 호출이 가능하다.

- 동기화 블록내에서만 사용할 수 있다.

- wait() : 객체의 lock을 풀고 해당 객체의 쓰레드를 대기상태로 둔다.

- notify() : 대기중인 쓰레드 중의 하나를 깨운다.

- notifyAll() : 대기중인 모든 쓰레드를 깨운다. 호출된 객체의 대기 중인 쓰레드만 해당 된다.

+ Recent posts