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 |