7_thread
스레드 배경지식
프로세스(process)
실행 중인 프로그램
디스크에 있는 프로그램이 메모리에 적재된 상태
운영체제의 제어를 받는 상태
특징
프로세스는 자신만의 자원을 가짐
프로세스끼리는 서로 독립적
스레드(thread)
하나의 프로그램 실행 흐름
프로세스 내부에 존재
프로세스는 적어도 하나의 스레드를 가짐
특징
프로세스에 비해 필요 자원이 적음
경량 프로세스
스레드 간 자원을 공유
멀티 태스킹(multitasking)
운영체제가 여러 작업을 동시에 실행
프로세스 기반 멀티 태스킹
다수 프로세스를 동시에 실행하는 것
vi 에디터와 웹 브라우저 동시 실행
스레드 기반 멀티 태스킹
단일 프로세스가 여러 작업을 동시에 실행하는 것
VI에디터에서 텍스트 출력과 입력 동시 진행
Java의 멀티 태스킹
스레드 기반 멀티 태스킹 사용→멀티스레딩
Java애플리케이션 실행 시 하나의 프로세스 실행
프로세스 내에서 여러 스레드 실행 가능
언어 레벨에서 멀티 스레딩 구현
멀티스레딩의 장점
유휴시간(idle time)의 최소화
한 작업이 끝날 때까지 기다려야 할 때
네트워크나 인터랙티브 프로그램에서 자주 발생
예제:
네트워크 트래픽 전송시간>CPU처리시간
사용자 텍스트 입력시간>버퍼 처리 시간
전통적인 스레드 모델
Java의 스레드 모델
Java와 CPU 코어
Java는 싱글/멀티 코어 시스템에서 모두 동작
싱글 코어 CPU
다수 스레드가 1개의 CPU를 공유
실제로 병렬 처리는 안됨
멀티 코어CPU
Java의 스레드 상태
Java의 동기화
때로는 동가화가 필요한 경우가 있음
데이터 구조 공유, 스레드 간 통신
Java는 모니터(monitor)메커니즘을 이용
모니터:
스레드 간 상호배제(mutual exclusion)를 구현하는 동기화 구조
모든 오브젝트에 구현되어 있음
모니터 동작 원리
Java 스레드
Main스레드
Java프로그램 시작 시 실행되는 기본 스레드
Main스레드를 기점으로 자식 스레드가 생성
프로그램 종료 직전까지 실행되는 마지막 스레드
스레드 확인해보기
static Thread currentThread()
모든 스레드는 Thread오브젝트로 제어
현재 스레드는 다음 메서드로 접근 가능
1
2
3
4
5
6
7
8
//CurrentThreadDemo.java
class CurrentThreadDemo {
public static void main ( String [] args ) {
Thread t = Thread . currentThread ();
System . out . println ( "Current thread: " + t );
}
}
//>>Current thread: Thread[main,5,main]
다른 스레드 생성하기
1. Runnable 인터페이스
클래스가 Runnable인터페이스를 구현하는 방법
public boid run()
을 멤버 메서드로 가짐
1
2
3
4
5
class MuFunnable implements Runnable {
public void run (){
//스레드가 실행할 코드
}
}
스레드 실행
Runnable 구현 클래스를 스레드 오브젝트로 생성
이후 start()
메서드를 통해 스레드 실행
1
2
3
//Runnable구현클래스인 MyRunnable객체이다.
Thread thread = new Thread ( ** new MyRunnable () ** );
thread . start (); //스레드 실행
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
//NewThread.java
class NewThread ** implements Runnable ** {
Thread t ;
NewThread () {
** t = new Thread ( this ); **
System . out . println ( "Child thread: " + t );
}
public void run () {
try {
for ( int i = 5 ; i > 0 ; i -- ) {
System . out . println ( "Child Thread: " + i );
Thread . sleep ( 500 );
}
} catch ( InterruptedException e ) {
System . out . println ( "Child interrupted." );
}
System . out . println ( "Exiting child thread." );
}
}
class ThreadDemo {
public static void main ( String [] args ) {
try {
** NewThread nt = new NewThread ();
nt . t . start (); **
for ( int i = 5 ; i > 0 ; i -- ) {
System . out . println ( "Main Thread: " + i );
Thread . sleep ( 1000 );
}
} catch ( InterruptedException e ) {
System . out . println ( "Main thread interrupted." );
}
System . out . println ( "Main thread exiting." );
}
}
2. Thread 클래스
Thread클래스를 상속하는 방법
run()
에 스레드 실행 코드 구현
1
2
3
4
5
class MyThread extends Thread { //Thread의 자식 클래스
public void run (){ //Thread클래스의 run()메서드를 오버라이딩한다.
//스레드가 실행할 코드
}
}
Thread클래스 멤버
생성자
설명
Thread()
스레드 객체를 생성
Thread(Runnable target)
Runnable구현 객체를 사용해 스레드 객체를 생성
Thread(Runnable target, String name)
Runnable구현 객체를 사용해 스레드 이름이 name인 스레드 객체를 생성
메서드 및 상수
설명
static Thread currentThread()
현재 실행 중인 스레드 객체의 참조 값을 반환
String getName()
스레드의 이름 반환
int getPriority()
스레드의 우선순위 값을 반환
boolean isInterrupted()
스레드가 인터럽트를 당했는지 여부 반환
void setName()
스레드의 이름을 설정
void setPriority()
스레드의 우선순위 설정
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
//NewThread.java
class NewThread ** extends Thread ** {
NewThread () {
** super (); **
System . out . println ( "Child thread: " + this );
}
public void run () {
try {
for ( int i = 5 ; i > 0 ; i -- ) {
System . out . println ( "Child Thread: " + i );
Thread . sleep ( 500 );
}
} catch ( InterruptedException e ) {
System . out . println ( "Child interrupted." );
}
System . out . println ( "Exiting child thread." );
}
}
class ExtendThread {
public static void main ( String [] args ) {
** NewThread nt = new NewThread ();
nt . start (); **
try {
for ( int i = 5 ; i > 0 ; i -- ) {
System . out . println ( "Main Thread: " + i );
Thread . sleep ( 1000 );
}
} catch ( InterruptedException e ) {
System . out . println ( "Main thread interrupted." );
}
System . out . println ( "Main thread exiting." );
}
}
Runnable 인터페이스 사용
Thread 클래스 사용
run() 메서드만 구현하면 될 때
run() 외 메서드를 오버라이딩 할 필요가 있을 때
다른 클래스를 상속해야할 때
isAlive()
와 join()
final boolean isAlive()
스레드가 실행 중일 때 true or false
final void join() throws InterruptedException
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
//DemoJoin.java
class NewThread implements Runnable {
String name ;
Thread t ;
NewThread ( String threadname ) {
name = threadname ;
t = new Thread ( this , name );
System . out . println ( "New thread: " + t );
}
public void run () {
try {
for ( int i = 5 ; i > 0 ; i -- ) {
System . out . println ( name + ": " + i );
Thread . sleep ( 1000 );
}
} catch ( InterruptedException e ) {
System . out . println ( name + " interrupted." );
}
System . out . println ( name + " exiting." );
}
}
class DemoJoin {
public static void main ( String [] args ) {
NewThread nt1 = new NewThread ( "One" );
nt1 . t . start ();
System . out . println ( "Thread One is alive: " + ** nt1 . t . isAlive () ** );
try {
** nt1 . t . join (); **
} catch ( InterruptedException e ) {
System . out . println ( "Main thread Interrupted" );
}
System . out . println ( "Thread One is alive: " + ** nt1 . t . isAlive () ** );
System . out . println ( "Main thread exiting." );
}
}
/*>>
New thread: Thread[One,5,main]
Thread One is alive: true
One: 5
One: 4
One: 3
One: 2
One: 1
One exiting.
Thread One is alive: false
Main thread exiting.
*/
스레드 우선순위
스케줄러가 어떤 스레드를 우선시할지 결정할때(어떤 스레드를 CPU에 돌릴까?)
우선 순위가 높은 스레드는 낮은 스레드를 선점함
높은 우선 순위를 갖는 스레드가 더 자주 CPU사용
스레드 우선순위 설정
final void setPriority(int level)
level
은 스레드의 우선순위
Thread클래스에서 아래 상수를 제공
우선순위 상수
설명
MAX_PRIORITY
최고 우선순위인 10
NORM_PRIORITY
중간 우선순위인 5
MIN_PRIORITY
최저 우선순위인 1
스레드 동기화
두개 이상의 스레드가 공유 자원을 접근시
하나의 스레드만 자원을 사용하도록 보장 필요
Java는 언어레벨에서 동기화 지원→synchronized
키워드 사용
레이스 컨디션(race condition):
동기화가 설정 안되면 발생하는 문제
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
//Synch.java
class Callme {
void call ( String msg ) {
System . out . print ( "[" + msg );
try {
Thread . sleep ( 1000 );
} catch ( InterruptedException e ) {
System . out . println ( "Interrupted" );
}
System . out . println ( "]" );
}
}
class Caller implements Runnable {
String msg ;
Callme target ;
Thread t ;
public Caller ( Callme targ , String s ) {
target = targ ;
msg = s ;
t = new Thread ( this );
}
public void run () {
target . call ( msg );
}
}
class Synch {
public static void main ( String [] args ) {
Callme target = new Callme ();
Caller ob1 = new Caller ( target , "Hello" );
Caller ob2 = new Caller ( target , "World" );
ob1 . t . start ();
ob2 . t . start ();
try {
ob1 . t . join ();
ob2 . t . join ();
} catch ( InterruptedException e ) {
System . out . println ( "Interrupted" );
}
}
}
/*>>
[World[Hello]
]
*/
/*>>
[Hello[World]
]
*/
→ call()
에 대해 레이스 컨디션이 발생
동기화 메서드
오브젝트 코드에 임계영역을 설정할 때 사용
스레드가 임계영역에 진입 시 락을 설정
다른 스레드는 락이 풀릴 때 까지 대기
1
2
3
public synchronized void 메서드 (){
//임계영역 코드
}
1
2
3
4
5
6
7
8
9
10
11
12
//Synch.java 수정
class Callme {
** synchronized ** void call ( String msg ) {
System . out . print ( "[" + msg );
try {
Thread . sleep ( 1000 );
} catch ( InterruptedException e ) {
System . out . println ( "Interrupted" );
}
System . out . println ( "]" );
}
}
동기화 블록
동기화 메서드는 클래스 내부 코드 접근 필요
1
2
3
synchronized ( 공유객체 ){
//임계영역 코드
}
오브젝트 참조자로 동기화 설정 가능
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
//Synch1.java
class Callme {
void call ( String msg ) {
System . out . print ( "[" + msg );
try {
Thread . sleep ( 1000 );
} catch ( InterruptedException e ) {
System . out . println ( "Interrupted" );
}
System . out . println ( "]" );
}
}
class Caller implements Runnable {
String msg ;
** Callme target ; **
Thread t ;
public Caller ( Callme targ , String s ) {
target = targ ;
msg = s ;
t = new Thread ( this );
}
public void run () {
** synchronized ( target ) {
target . call ( msg );
} **
}
}
class Synch1 {
public static void main ( String [] args ) {
Callme target = new Callme ();
Caller ob1 = new Caller ( target , "Hello" );
Caller ob2 = new Caller ( target , "World" );
ob1 . t . start ();
ob2 . t . start ();
try {
ob1 . t . join ();
ob2 . t . join ();
} catch ( InterruptedException e ) {
System . out . println ( "Interrupted" );
}
}
}
스레드 간 통신
생산자-소비자문제:
공유 자원을 생산하고 소비하는 두 스레드 간 어떻게 협업을 할 것인가
임계 영역 접근에 대한 스레드 간 통신이 필요
Java는 Object클래스에 관련 메서드를 지원
wait()
호출자 스레드가 락을 포기하고 잠듦
notify()
wait() 를 호출한 스레드 중 하나를 깨움
notifyAll()
wait() 를 호출한 모든 스레드를 깨움
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
//PC.java
class Q {
int n ;
** synchronized ** int get () {
System . out . println ( "Got: " + n );
return n ;
}
** synchronized ** void put ( int n ) {
this . n = n ;
System . out . println ( "Put: " + n );
}
}
class ** Producer ** implements Runnable {
Q q ;
Thread t ;
Producer ( Q q ) {
this . q = q ;
t = new Thread ( this , "Producer" );
}
public void run () {
int i = 0 ;
while ( true )
** q . put ( i ++ ); **
}
}
class ** Consumer ** implements Runnable {
Q q ;
Thread t ;
Consumer ( Q q ) {
this . q = q ;
t = new Thread ( this , "Consumer" );
}
public void run () {
while ( true )
** q . get (); **
}
}
/*
Put: 1
Got: 1
Got: 1
Got: 1
Got: 1
Got: 1
Put: 2
Put: 3
Put: 4
…
*/
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
//PC.java 수정
class Q {
int n ;
** boolean valueSet = false ; **
synchronized int get () {
try {
if ( **! valueSet ** )
** wait (); **
} catch ( InterruptedException e ) {
System . out . println ( "InterruptedException" );
}
System . out . println ( "Got: " + n );
** valueSet = false ;
notify (); **
return n ;
}
synchronized void put ( int n ) {
try {
if ( ** valueSet ** )
** wait (); **
} catch ( InterruptedException e ) {
System . out . println ( "InterruptedException" );
}
this . n = n ;
** valueSet = true ; **
System . out . println ( "Put: " + n );
** notify (); **
}
}
데드락(deadlock)
두 스레드가 공유자원에 대해 서로 기다리는 상태
멀티스레딩 환경에서 발생할 수 있는 대표적 문제
단, 확률적으로 발생하기 때문에 디버깅이 어려움
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
//Deadlock.java
class A {
** synchronized ** void foo ( B b ) {
String name = Thread . currentThread (). getName ();
System . out . println ( name + " entered A.foo" );
System . out . println ( name + " call B.last()" );
** b . last (); **
}
synchronized void last () {
System . out . println ( "Inside A.last" );
}
}
class B {
** synchronized ** void bar ( A a ) {
String name = Thread . currentThread (). getName ();
System . out . println ( name + " entered B.bar" );
System . out . println ( name + " call A.last()" );
** a . last (); **
}
synchronized void last () {
System . out . println ( "Inside B.last" );
}
}
class Deadlock implements Runnable {
A a = new A ();
B b = new B ();
Thread t ;
Deadlock () {
Thread . currentThread (). setName ( "MainThread" );
t = new Thread ( this , "RacingThread" );
}
public void run () {
b . bar ( a ); // get lock on b in other thread.
System . out . println ( "Back in other thread" );
}
void deadlockStart () {
t . start ();
a . foo ( b ); // get lock on a in this thread.
System . out . println ( "Back in main thread" );
}
public static void main ( String [] args ) {
Deadlock dl = new Deadlock ();
dl . deadlockStart ();
}
}
스레드 라이프 사이클
스레드가 스케줄링 될 때 따르는 흐름
스레드 상태
java는 스레드 상태를 상수로 관리
상태
상수
설명
객체 생성
NEW
객체를 생성한 후 아직 시작하지 않은 상태
실행대기
RUNNABLE
실행 준비가 된 상태
중지
BLOCKED
입출력 등 락(Lock)이 풀릴 때 까지 기다리는 상태
WAITING
다른 스레드가 통지할 때까지 기다리는 상태
TIME_WAITING
주어진 시간동안 기다리는 상태
종료
TERMINATED
실행을 마친 상태
스레드 상태 조작 메서드
메서드
설명
void interrupt()
실행 중인 스레드에 인터럽트를 걸어 중지시킴
void join()
주어진 시간이 지나거나 대응하는 스레드가 종료될 때까지 대기시킴
void resume()
중지 상태의 스레드를 실행 대기 상태로 전환시킴
static void sleep()
주어진 시간 동안 중지
void start()
스레드를 실행 대기시킴
void stop()
스레드 종료
void suspend()
스레드 중지
static void yield()
우선순위가 동일한 스레드에 실행을 양보