Thread API : http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html
Thread Group API : http://docs.oracle.com/javase/7/docs/api/java/lang/ThreadGroup.html
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 |
join() join(long millis) join(long millis, int nanos) |
지정된 시간동안 쓰레드가 실행되도록 한다. 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속한다. |
void |
resume() |
suspend()에 의해 일시정지상태에 있는 쓰레드를 실행대기상태로 만든다. |
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() : 대기중인 모든 쓰레드를 깨운다. 호출된 객체의 대기 중인 쓰레드만 해당 된다.
'Java > Java SE' 카테고리의 다른 글
Java 이클립스(Eclipse) JDBC 오라클(ORACLE) 개발 환경 설정 (2) | 2013.08.23 |
---|---|
Java 입출력(I/O) (0) | 2013.08.20 |
Java 이름 규칙(Naming Rule) (0) | 2013.08.16 |
Java Deep Copy, Shallow Copy (0) | 2013.08.12 |
Java 컬렉션 프레임웍(Collection Framework) (2) | 2013.08.12 |