1. 프로세스와 쓰레드
프로세스(process)는 간단하게 말하면 실행중인 프로그램이다.
프로그램 –실행–> 프로세스
프로세스는 데이터, 메모리등의 자원과 쓰레드로 구성되어있음.
프로세스의 자원을 이용해서 실제 작업을 수행하는 것.
모든 프로세스는 최소 하나 이상의 쓰레드가 존재. 둘 이상이면 멀티쓰레드 프로세스라한다.
- 멀티쓰레딩의 장점
- CPU의 사용률을 향상시킴.
- 자원을 보다 효울적으로 사용할 수 있음.
- 사용자에 대한 응답성이 향상됨.
- 작업이 분리되어 코드가 간결해짐.
동기화(synhronization), 교착상태(deadlock)등을 고려해서 신중히 프로그래밍 해야함.
2. 쓰레드의 구현과 실행
구현 방법
Thread클래스 상속
1
2
3
4
5class MyThread extends Thread{
public void run(){
//Thread의 run()함수를 오버라이딩.
}
}Runnable인터페이스 구현(일반적인 방법)
1
2
3
4
5class MyThread implements Runnable{
public void run(){
//추상메서드 run()을 구현
}
}
1 | class ThreadEx1{ |
쓰레드 생성 후 start()를 호출해야 작업을 시작함.
한번 사용한 쓰레드는 다시 재사용할 수 없다. 하나의 쓰레드에 한번의 start()만 호출 될 수 있음.
1 | ThreadEx1_1 t1 = new ThreadEx_1(); |
3. start()와 run()
- run()을 호출하는 것은 생성된 쓰레드를 실행하는 것이 아니라 단순히 클래스에속한 메서드를 하나 호출하는것.
call stack
run
main
- start()을 호출하는 것은 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택을 생성한 후 run()을 호출해서 생성된 호출스택에 run()이 저장되게 한다.
모든 쓰레드는 독립적인 작업을 수행하기 위해 자신만의 호출스택을 필요로 하기 때문에 새로운 쓰레드를 생성하고 실행시킬때마다 새로운 호출스택이 생성되고 쓰레드가 종료되면 작업에 사용된 호출스택은 소멸된다.
- main메서드에서 쓰레드의 start메서드를 호출한다.
- start메서드는 쓰레드가 작업을 수행하는데 사용될 새로운 호출 스택을 생성한다.
- 생성된 호출스택에 run 메서드를 호출해서 쓰레드가 작업을 수행하도록 한다.
- 이제는 호츨스택이 2개이기때문에 스케줄러가 정한 순서에 으해 번갈아 가면서 실행된다.
실행중인 쓰레드가 하나도 없을때 프로그램은 종료된다.
4. 싱글쓰레드와 멀티쓰레드
두개의 작업을 하나의 쓰레드로 하면 한 작업 끝난 후 다른 작업 끝.
두개의 작업을 두개의 쓰레드로 하면 짧은시간동안 쓰레드 2개가 번갈아 가면서 작업을 수행해서 동시에 두 작업이 처리되는것과 같다고 느낌.
CPU만 사용하는 계산 작업이면 멀티쓰레드가 전환하는 시간때문에 오히려 느림.
CPU외 자원을 사용하는 경우 싱글쓰레드 프로세스 보다 멀티쓰레드프로세스가 더 효율적임.
ex)외부기기에서 입출력 받는 경우1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import javax.swing.JOptionPane;
class ThreadEx6{
public static void main(String[] args) throws Exception{
String input = JOptionPanel.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하신 값은 "+input+"입니다.");
for(int i = 10; i > 0 ; i--) {
System.out.println(i);
try{
Thread.sleep(1000);
}catch(Exception e){}
}
}
}
위의 예는 입력을 받은 후 출력하기 때문에 사용자가 입력하는 동안에는 출력 작업이 일어나지 않는다.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24import javax.swing.JOptionPane;
class ThreadEx7{
public static void main(String[] args) throws Exception{
ThreadEx7_1 th1 = new ThreadEx7_1();
th1.start();
String input = JOptionPanel.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하신 값은 "+input+"입니다.");
}
}
class ThreadEx7_1 extends Thread{
public void run(){
for(int i = 10; i > 0 ; i--) {
System.out.println(i);
try{
Thread.sleep(1000);
}catch(Exception e){}
}
}
}
위의 코드는 입력받는 동안 출력되고 입력된 값이 출력되고 또 출력이 된다.
5. 쓰레드의 우선순위
쓰레드에 우선순위(priority)의 멤버변수가 있다.
우선순위의 범위는 1~10이고 숫자가 높을수록 더 우선순위가 높다.
우선순위의 값은 상대적이다. 1,2와 8,9의 결과 값이 같다.
우선순위는 쓰레드생성한 쓰레드로부터 상속받는다.
main 메서드를 수행하는 쓰레드의 우선순위는 5.
파일 다운로드와 채팅기능 중 채팅에 더 우선순위를 높여야 한다.1
2
3
4
5
6void setPripority(int new Priority);
int getPriority();
public static final int MAX_PRIORITY =10;
public static final int MIN_PRIORITY =1;
public static final int NORM_PRIORITY = 5;
1 | class ThreadEx9{ |
위 결과는 |가 먼저 끝나고, - 이 완료된다.
6. 쓰레드 그룹(thread group)
서로 관련된 쓰레드를 그룹으로 다루기 위한것.
폴더를 생성해서 관련된 파일을 묶어 관리하는것처럼 쓰레드도 그룹으로 묶어서 관리.
쓰레드를 쓰레드 그룹에 포함시키려면 Thread생성자를 이용해야함1
2Thread(ThreadGroup group, String name)
Thread(ThreadGroup gorup, Runnable target)
7. 데몬쓰레드(deamon thread)
데몬쓰레드는 일반 쓰레드의 작업을 돕는 보조역할.
일반쓰레드 작업이끝나면 데몬 쓰레드는 강제종료됨.
ex) 가비지컬렉터, 위드프로세서 자동저장, 화면자동갱신
무한루프와 조건문을 이용해서 실행 후 대기하고 있다가 특정 조건이 되면 작업 수행하고 다시 대기.
8. 쓰레드의 실행제어
쓰레드 프로그램이 어렵게 느껴지는 건 동기화와 스케줄링때문임.
- 쓰레드 스케쥴링과 관련된 메서드
생성자/메서드 | 설명 |
---|---|
void interupt() | sleep()이나 join()에 의해 일시정지상태인 쓰레드를 실행대기상태로 만든다. 해당 쓰레드에서는 interuptedExcetption이 발생함으로써 일시정지상태를 벗어나게 된다. |
void join() void join(long millis) void join(long millis, int nanos) |
지정된 시간동안 쓰레드가 실행되도록한다. 지정된 시간이 자나거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속한다. |
void resume() | supend()에 의해 일시정지상태에 있는 쓰레드를 실행대기 상태로 만든다. |
static void sleep(long millis) static void sleep(long millis, int nanos) |
지정된 시간( 천분의 일초 단위) 동안 쓰레드를 일시정지 시킨다. 지정한 시간이 지나고 나면 자동적으로 다시 실행대기가 된다. |
void stop() | 쓰레드를 즉시 종료시킨다. 교착상태(dead-lock)에 빠지기 쉽기 때문에 deprecated되었다. |
void suspend() | 쓰레드를 일시정지시킨다. resume()을 호출하면 다시 실행대기상태가 된다. |
satic void yield() | 실행중에 다른 쓰레드에게 양보하고 실행대기상태가 된다. |
- 쓰레드의 상태
상태 | 설명 |
---|---|
NEW | 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태 |
RUNNABLE | 실행 중 또는 실행 가능한 상태 |
BLOCKED | 동기화 블럭에 의해서 일시정지된 상태(Lock이 풀릴때까지 기다리는 상태) |
WATING, TIMED_WATING |
쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은 일시정지 상태. TIMED_WATEING은 일시정지시간이 지정된 경우를 의미한다. |
TERMINATED | 쓰레드의 작업이 종료된 상태 |
- 쓰레드 생성하고 start()을 호출하면 실행대기열에 저장. 순서를 기다림. Quequ와같은 구조로 먼저 실행대기열에 들어온 쓰레드가 먼저 실행됨.
- 실행대기상태이다가 자기 차례가 오면 실행상태가 됨.
- 주어진 실행시간이 다되거나 yeild()를 만나면 다시 실행대기 상태가 되고 다음 차례의 쓰레드가 실행상태가 됨.
- 실행중 suspend(), sleep(), wait(), join(), I/O block에 의해 일시정지상태가 딜 수 있음.
- 지정된 일시정지 시간이 지나거나 notify(), reusme(), interrup()가 호출되면 일시정지 상태를 벗어나 다시 실행대기열에 들어가 순서를 기다림.
- 실행을 모두 마치거나 stop()이 호출되면 쓰레드는 소멸됨.
join()을 사용한 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67class ThreadEx13{
static long startTime=0;
public static void main(String[] args) throws Exception{
ThreadEx13_1 th1 = new ThreadEx13_1();
ThreadEx13_2 th2 = new ThreadEx13_2();
th1.start();
th2.start();
startTime = System.currentTimeMillis();
try{
th1.join();//th1의 작업이 끝날때까지 기다린다.
th2.join();//th2의 작업이 끝날때까지 기다린다.
}catch (InterruptedException e){}
System.out.println("소요시간:" + (System.currentTimeMillis()- ThreadEx13.startTime));
}
}
class ThreadEx13_1{
public void run(){
for(int i = 0; i < 300 ; i++) {
System.out.println("-");
}
}//run()
}
class ThreadEx13_2{
public void run(){
for(int i = 0; i < 300 ; i++) {
System.out.println("|");
}
} //run()
}
```
join()을 사용하지 않으면 main 쓰레드는 바로 종료되지만, join()을 사용해서 th1과 th2의 작업이 마칠때까지 main쓰레드가 기다림
- 쓰레드가 순차적으로 실행되어야 할때 사용하는 예제.
```java
class ThreadEx14{
static long startTime=0;
public static void main(String[] args) throws Exception{
ThreadEx14_1 th1 = new ThreadEx14_1();
ThreadEx14_2 th2 = new ThreadEx14_2();
th1.start();
try{
th1.join();
}catch (InterruptedException e){}
th2.start();
}
}
class ThreadEx14_1{
public void run(){
for(int i = 0; i < 300 ; i++) {
System.out.println("-");
}
}//run()
}
class ThreadEx14_2{
public void run(){
for(int i = 0; i < 300 ; i++) {
System.out.println("|");
}
}
}아래 코드는 th1, th2, Main쓰레드 순으로 종료됨
1 | class ThreadEx15{ |
synchronized를 이용해서 객체를 동기화 하면 쓰레드가 교착상태에 빠질 수 있다.
교착상태란 구 쓰레드가 lock이 된 상태로 서로 lock가 풀리기를 무한정 기다리게 되는상황.
1 | class ThreadEx21 { |
실행결과1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17Thread-0 Local var: 1
Thread-0 Instance var: 1
Thread-0 Local var: 2
Thread-0 Instance var: 2
Thread-0 Local var: 3
Thread-0 Instance var: 3
Thread-0 Local var: 1
Thread-0 Instance var: 4
Thread-0 Local var: 2
Thread-0 Instance var: 5
Thread-0 Local var: 3
Thread-0 Instance var: 6
여기서 인스턴스변수 iv는 main, th1, th2 쓰레드 모두 접근이 가능함.(쓰레드간의 변수 공유)
lv는 지역변수라 각 쓰레드 스택내에서 생성되어 공유되지 않는다.
- 다음은 동기화가 잘 되지 않아 데이터 값이 변형된 예제.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30class ThreadEx24{
public static void main(String[] args){
RunnbaleImpl r = new RunnableImpl();
Thread th1 = new Thread(r);
Thread th2 = new Thread(r);
th1.start();
th2.start();
}
}
class Account{
int balance =1000;
public void withdraw (int money){
if(balance>=money){
try{ Thread.sleep(1000);} catch(Exception e){}
balance -=money;
}
}//withdraw
}
class RunnableEx24 implements Runnable{
Account acc = new Account();
public void run(){
while(acc.balance > 0){
//100, 200, 300중의 한 값을 임의로 선택해서 출금
int money = (int)(Math.random()*3+1)*100;
acc.withdraw(money);
System.out.println("balance:"+acc.balance);
}
}//run()
}
실행 결과1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30balance:700
balance:400
balance:200
balance:0
balance:-100
```
잔고(balance)가 임의의 출금금액(money)보다 클 경우에만 출금하도록 되어있는데 잔고에 -100이 나왔음.
if조건문 통과하고 출금직전에 다른쓰레드가 끼어들어서 먼저 출금했기때문이다.
if문과 출금하는 기능은 하나로 synchronized되어야 한다.
```java
public synchronized void withdraw (int money){
if(balance>=money){
try{ Thread.sleep(1000);} catch(Exception e){}
balance -=money;
}
}//withdraw
```
```java
public void withdraw (int money){
synchronized(this){
if(balance>=money){
try{ Thread.sleep(1000);} catch(Exception e){}
balance -=money;
}
}
}//withdraw
9.2 wait()과 notify()
쓰레드를 동기화 할때 효율을 높이기 위해 사용할 수 있다.
한쓰레드가 lock걸려 다른 쓰레드는 lock이 풀릴때까지 기다려야 되는 상황이 있음.
쓰레드에 lock을 걸는것 대신에 wait()을 호출해서 다른 쓰레드에 제어권을 넘겨주고 대기상태로 기다리다가 다른쓰레드에 의해 notify()가 호출되면 다시 실행상태가 되도록 함
wait()과 notify()는 Object클래스에서 정의되서 모든 객체에서 호출이 가능함.
동기화 블록 내에서만 사용가능. 쓰레드가 wait()을 호출하면 그때까지 걸어 놓은 lock을 풀고 대기실에 들어가기 됨.
notify()는 객체의 wating pool에 있는 쓰레드 중 하나만 깨움.
- wait(), notify(), notifyAll()
- Object에 정의 되어있다.
- 동기화 블록(synchronized)내에서만 사용할 수 있다.
- 보다 효율적인 동기화를 가능하게 한다.
1 | class Account{ |