장점 | 단점 |
---|---|
- 자원을 보도 효율적으로 사용할 수 있다. - 사용자에 대한 응답성(responseness)이 향상된다. - 작업이 분리되어 코드가 간결해진다. | - 개발시 고려할 사항들이 많다. - 동기화(synchronization)에 주의해야 한다. - 교착상태(dead-lock)가 발생하지 않도록 주의해야 한다. - 각 스레드가 효율적으로 고르게 실행될 수 있게 해야 한다. |
class JobThread extends Thread{
public void run(){/* Task 구현 */}
}
class JobThread implements Runnable{
public void run(){/* Task 구현 */}
}
@FunctionalInterface
public interface Runnable{
public abstract void run();
}
System.out.println(getName());
System.out.prinln(Thread.currentThread.getName());
=>Thread t = Thread.currentThread();
String name = t.getName();
Runnable r = new ThreadEx02();
//생성자 Thread(Runnable target)
Thread t2 = new Thread(r);
ThreadEx new ThreadEX();
t.start();
t.start(); //java.lang.IllegalThreadStateException 발생
public class ThreadTester{
public static void main(String[] args){
HelloRunner r = new HelloRunner();
Thread t = new Thread(r);
t.start();
}
}
class HelloRunner implments Runnable{
int i;
@Override
public void run(){
i = 0;
while(true){
System.out.println("Hello " + i++);
if(i == 30) break;
}
}
}
예제
public class SingleThreadTest{
public static void main(String[] args){
for(int i=0; i<50; i++){
System.out.println("-");
}
for(int i=0; i<50; i++){
System.out.println("|");
}
}
}
[결과]
--------------------------------------------------||||||||||||||||||||||||||||||||||||||||||||||||||
public class MultiThreadTest {
public static void main(String args[]){
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.start();
t2.start();
}
}
class MyThread1 extends Thread {
@Override
public void run() {
for(int i=0; i<50; i++) {
System.out.print("-");
}
}
}
class MyThread2 extends Thread {
@Override
public void run() {
for(int i=0; i<50; i++) {
System.out.print("|");
}
}
}
[결과]
————||||||||||||||||||||||||||||||||||||||||||||||||||-----------------------------------------
-||||||||-------------------------------------------------||||||||||||||||||||||||||||||||||||||||||
예제
public class ThreadPriorityEx extends Thread{
public void run(){
System.out.println("in run() method");
}
public static void main(String argvs[]){
System.out.println("메인 스레드 현재 우선순위:"+
Thread.currentThread().getPriority()); // 5
// 메인 스레드의 우선순위를 7로 설정한다
Thread.currentThread().setPriority(7);
// currentThread() 메서드를 사용해서
// 현재 스레드를 찾아서 getPriority()로 우선순위를 얻는다
System.out.println("메인 스레드 변경된 우선순위:"+
Thread.currentThread().getPriority());
ThreadPriorityEx t = new ThreadPriorityEx();
// t 스레드는 메인스레드의 child 스레드이기에 우선순위도 7이다
System.out.println("스레드 t의 우선순위:"+
t.getPriority());
t.start();
}
}
[결과]
메인 스레드 현재 우선순위: 5
메인 스레드의 우선순위: 7
스레드 t의 우선순위 : 7
in run() method
public class ThreadPriorityTest extends Thread{
public void run(){
System.out.println("in run() method");
}
public static void main(String[] args){
ThreadPriorityEx t1 = new ThreadPriorityEx();
ThreadPriorityEx t2 = new ThreadPriorityEx();
ThreadPriorityEx t3 = new ThreadPriorityEx();
//스레드의 우선순위는 기본값인 5이다
System.out.println("t1의 우선순위 : " + t1.getPriority());
System.out.println("t2의 우선순위 : " + t2.getPriority());
System.out.println("t3의 우선순위 : " + t3.getPriority());
//우선순위 변경
t1.setPriority(6);
t2.setPriority(3);
t3.setPriority(11);
System.out.println("t1 스레드 우선순위 : " + t1.getPriority());
System.out.println("t2 스레드 우선순위 : " + t2.getPriority());
System.out.println("t3 스레드 우선순위 : " + t3.getPriority());
System.out.println("현재 실행중인 스레드 : " + Thread.currentThread().getName());
System.out.println("메인 스레드의 우선순위 : "
+ Thread.currentThread().getPriority());
//메인스레드 우선순위 10으로 변경
Thread.currentThread().setPriority(10);
System.out.println("메인 스레드의 우선순위 : "
+ Thread.currentThread().getPriority());
}
}
[결과]
t1의 우선순위 : 5
t2의 우선순위 : 5
t3의 우선순위 : 5
t1 스레드 우선순위 : 6
t2 스레드 우선순위 : 3
t3 스레드 우선순위 : 9
현재 실행중인 스레드 : main
메인 스레드의 우선순위 : 5
메인 스레드의 우선순위 : 10
생성자/메서드 | 설명 |
---|---|
ThreadGroup(String name) | 주어진 이름으로 새로운 스레드 그룹 생성 |
ThreadGroup(ThreadGroup parent, String name) | 주어진 부모그룹과 이름으로 새로운 스레드 그룹 생성 |
int activeCount() | 스레드 그룹 및 하위 그룹의 활성 스레드 수 반환 |
void destroy() | 스레드 그룹 및 하위 그룹까지 모두 삭제 |
int getMaxPriority() | 스레드 그룹의 최대 우선 순위를 반환 |
void setMaxPriority(int pri) | 스레드 그룹의 최대 우선 순위를 설정 |
String getName() | 스레드 그룹의 이름을 반환 |
ThreadGroup getParent() | 스레드 그룹의 상위 스레드 그룹을 반환 |
void list() | 스레드 그룹, 하위 그룹 및 스레드에 대한 정보를 출력 |
boolean isDaemon() | 스레드 그룹이 데몬 스레드 그룹인지 확인 |
void setDaemon(boolean daemon) | 스레드 그룹을 데몬 스레드 그룹으로 설정 또는 해제 |
boolean isDestroyed() | 스레드 그룹이 삭제되었는지 확인 |
void interrup() | 스레드 그룹에 속한 모든 스레드를 중단 |
예제
public class ThreadGroupEx01 implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args){
ThreadGroupEx01 runnable = new ThreadGroupEx01();
ThreadGroup tg1 = new ThreadGroup("Parent ThreadGroup");
Thread t1 = new Thread(tg1, runnable, "one");
t1.start();
Thread t2 = new Thread(tg1, runnable, "two");
t2.start();
Thread t3 = new Thread(tg1, runnable, "three");
t3.start();
System.out.println("Thread Group Name: "+tg1.getName());
tg1.list();
}
}
[결과]
one
two
three
Thread Group Name: Parent ThreadGroup
java.lang.ThreadGroup[name=Parent ThreadGroup,maxpri=10]
public class DaemonThreadEx01 extends Thread{
public void run(){
if(Thread.currentThread().isDaemon()){
System.out.println("daemon thread work");
}else{
System.out.println("user thread work");
}
}
public static void main(String[] args){
DaemonThreadEx01 t1 = new DaemonThreadEx01();
DaemonThreadEx01 t2 = new DaemonThreadEx01();
DaemonThreadEx01 t3 = new DaemonThreadEx01();
//Daemon thread로 설정
t1.setDaemon(true);
t1.start();
t2.start();
t3.start();
}
}
[결과]
daemon thread work
user thread work
user thread work
생성자/메서드 | 설명 |
---|---|
void interrupt() | sleep(), join(), wait()에 의해 일시정지 상태인 스레드르 꺠워서 실행대기 상태로 만든다. (Thread state가 WAITING → RUNNABLE) 해당 스레드에서는 interruptedException이 발생되면서 WAITING 상태를 벗어나게 된다. |
void join() void join(long millis) | join()을 호출한 스레드가 종료될 때까지 기다리게 한다. 즉 스레드를 Running → Waiting 상태로 만든다. 파라미터가 있는 join()은 대기 시간을 지정할 수 있고, 대기 시간이 끝나면 실행을 계속한다. |
void resume() | suspend()에 의해 일시정지 상태에 있는 스레드를 실행대기 상태로 만든다. |
static void sleep(long millis) static void sleep(long millis, int nanos) | 지정된 시간(천분의 일초 단위)동안 스레드를 일시정지시킨다. 지정한 시간이 지나고 나면, 자동적으로 다시 실행대기 상태가 된다. |
void stop() | 스레드를 즉시 종료시킨다. 교착상태(dead-lock)에 빠지기 쉽기 때문에 deprecated 되었다. |
boolean isAlive() | 스레드가 시작되었고 아직 끝나지 않았으면 true, 끝났으면 false를 반환한다. |
void suspend() | 스레드를 일시정지시킨다. resume()에 의해 다시 실행대기 상태가 된다. |
static void yield() | 다른 스레드에게 양보(yield)하고 자신은 실행대기 상태가 된다. |
상태 | 설명 |
---|---|
NEW | 스레드가 생성되었지만 스레드가 아직 실행할 준비가 되지 않았다. |
RUNNABLE | 실행할 준비가 된 스레드가 실행 가능한 상태로 스케줄링을 기다리는 상태 |
BLOCKED | 동기화블럭이나 I/O 작업에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태) |
WAITING | 다른 스레드가 특정 작업을 완료하기를 기다리며, 다른 스레드가 notify(), notifyAll()을 호출하기를 기다리고 있는 상태 |
TIME_WAITING | TIME_WAITING은 일시정지 시간이 지정된 경우의 상태로 스레드가 sleep() 메서드 호출로 지정한 밀리초 동안 Sleep하고 있는 상태 |
TERMINATED | 스레드가 종료된 상태 |
public enum State{
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
suspend(), resume(), stop()은 쓰레드를 교착상태(dead-lock)로 빠뜨릴 가능성이 있기 때문에 deprecated 되었으므로 사용하지 않는 것이 좋으며, 아래의 코드처럼 stopped와 suspended라는 boolean 타입의 변수를 선언하고 이 변수의 값을 변경함으로써 작업이 중지되고 종료되도록 변경할 수 있다.
public class ThreadSuspendResumeTest{
public static void main(String[] args){
ThreadSuspendResume ts1 = new ThreadSuspendResume();
ThreadSuspendResume ts2 = new ThreadSuspendResume();
t1.start();
t2.start();
try{
Thread.sleep(2000);
ts1.suspend();
Thread.sleep(2000);
ts2.suspend();
Thread.sleep(2000);
ts1.resume();
Thread.sleep(2000);
ts1.stop();
ts2.stop();
}catch(InterruptedException e){}
}
}
public class ThreadSuspendResumeTest implements Runnable{
boolean suspended = false;
boolean stopped = false;
Thread t;
@Override
public void run(){
while(!stopped){
if(!suspended){
//구현부
try{
Thread.sleep(1000);
}catch(InterruptedException e){}
}
}
}
public void suspend(){
suspended = true;
}
public void resume(){
suspended = false;
}
public void stop(){
stopped = true;
}
public void start(){
t.start();
}
}
while(!stopped){
if(!suspended){
System.out.println(Thread.currentThread().getName());
try{
Thread.sleep(1000);
}catch(InterruptedException e){}
}
}
while(!stopped){
if(!suspended){
System.out.println(name);
try{
Thread.sleep(1000);
}catch(InterruptedException e){
System.out.println(name + " - interrupted");
}
}else{
Thread.yield();
}
}
예제: ExecutorService 및 Executors를 사용하는 Java 스레드 풀의 사용 예
public class ThreadPoolWorkerThread implements Runnable{
private String message;
public ThreadPoolWorkerThread(String s){
this.message=s;
}
public void run() {
System.out.println(Thread.currentThread().getName() +
" (Start) message = "+message);
// 스레드를 1초간 sleep하는 processmessage 메소드 호출
processmessage();
System.out.println(Thread.currentThread().getName()+" (End)");
}
private void processmessage() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadPoolTest {
public static void main(String[] args) {
// 스레드 풀을 생성 (5 스레드)
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
Runnable worker = new ThreadPoolWorkerThread("" + i);
// ExecutorService의 execute 메소드 호출
executor.execute(worker);
}
// shutdown한다. 이미 executor에 제공된 task는 실행되지만
// 새로운 작업은 수용 안한다
executor.shutdown();
// shutdown후 모든 작업이 종료되었는지 여부를 확인한다
while (!executor.isTerminated()) {}
System.out.println("모든 소레드 종료");
}
}
[결과]
pool-1-thread-4 (Start) message = 3
pool-1-thread-1 (Start) message = 0
pool-1-thread-2 (Start) message = 1
pool-1-thread-3 (Start) message = 2
pool-1-thread-5 (Start) message = 4
pool-1-thread-5 (End)
pool-1-thread-3 (End)
pool-1-thread-1 (End)
pool-1-thread-3 (Start) message = 6
pool-1-thread-2 (End)
pool-1-thread-2 (Start) message = 8
pool-1-thread-5 (Start) message = 5
pool-1-thread-4 (End)
pool-1-thread-4 (Start) message = 9
pool-1-thread-1 (Start) message = 7
pool-1-thread-3 (End)
pool-1-thread-4 (End)
pool-1-thread-2 (End)
pool-1-thread-1 (End)
pool-1-thread-5 (End)
모든 소레드 종료
//CASE 1) T1, T2에 객체가 공유됨
RunnableImpl r = new RunnableImpl();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
//CASE 2) T1, T2가 공유하는 객체 없음
MyThread t1 = new MyThread();
MyTHread t2 = new MyThread();
t1.start();
t2.start();
public class SynchronizedTest{
public static void main(String[] args){
Runnable r = new RunnableSyncEx();
new Thread(r).start();
new Thread(r).start();
}
}
class Account{
private int balance = 1000;
public int getBalance(){
return balance;
}
public synchronized void withdraw(int money){
if(balance >= money){
try{
Thread.sleep(1000);
}catch(InterruptedException e){}
balance -= money;
}
} //withdraw
}
class RunnableSyncEx implements Runnable{
Account acc = new Account();
@Override
public void run(){
while(acc.getBalance() > 0){
//임의의 값으로 출금(withdraw) 처리
int money = (int)(Math.random() * 3 + 1) * 100;
acc.withdraw(money);
System.out.println(Thread.currentThread().getName()
+" => 잔액 :"+ acc.getBalance());
}
}
}
[결과]
//Synchronized 미사용 결과
Thread-1 => 잔액 :600
Thread-0 => 잔액 :800
Thread-1 => 잔액 :400
Thread-0 => 잔액 :400
Thread-1 => 잔액 :300
Thread-0 => 잔액 :0
Thread-1 => 잔액 :-200
//Synchronized 사용 결과
Thread-1 => 잔액 :700
Thread-0 => 잔액 :500
Thread-1 => 잔액 :300
Thread-1 => 잔액 :0
Thread-0 => 잔액 :0
class AccountEx{
private int balance = 1000;
public int getBalance(){
return balance;
}
public synchronized void withdraw(int money){
while(balance < money){
try{
//잔고 부족으로 출고 job 스레드는 lock 해제 후
//waiting pool로 들어가며 제어권을 넘긴다
wait();
}catch(InterruptedException e){}
}
balance -= money;
} //withdraw
public synchronized void deposit(int money){
balance += money;
//잔고가 증가되면 notify()를 호출해서 waiting pool의
//스레드를 깨워서 job을 이어서 처리하도록 한다
notify();
}
}
Set<Thread> threads = Thread.getAllStackTraces().keySet();
System.out.printf("%-15s \t %-15s \t %-15s \t %s\n", "Name", "State", "Priority", "isDaemon");
for (Thread t : threads) {
System.out.printf("%-15s \t %-15s \t %-15d \t %s\n", t.getName(), t.getState(), t.getPriority(), t.isDaemon());
}
[결과]
Name | State | Priority | isDeamon |
---|---|---|---|
main | RUNNABLE | 5 | false |
Thread-0 | TERMINATED | 5 | false |
Reference Handler | RUNNABLE | 10 | true |
Monitor Ctrl-Break | RUNNABLE | 5 | true |
Finalizer | WAITING | 8 | true |
Thread-2 | TERMINATED | 5 | false |
Thread-1 | BLOCKED | 5 | false |
Common-Cleaner | TIMED_WAITING | 8 | true |
예제 - Dead Lock 발생 예제: Dead Lock에서는 resource1, 2에 접근한 패턴이 주요 문제이므로 해결하려면 공유 리소스의 접근 순서만 변경하면 된다.
public class TestDeadlockEx1{
public static void main(String[] args){
final String resource1 = "테스트 리소스 1";
final String resource2 = "테스트 리소스 2";
//t1은 resource1을 잠그고 resource2를 잠그려고 시도한다
Thread t1 = new Thread(){
public void run(){
synchronized (resource1){
System.out.println("Thread 1: locked resource 1");
try{Thread.sleep(100);}catch(Exception e){}
synchronized(resource2){
System.out.println("Thread 1: locked resource 2");
}
}
}
};
//t2는 resource2를 잠그고 resource1을 잠그려고 시도한다.
Thread t2 = enw Thread(){
public void run(){
synchronized (resource2){
System.out.println("Thread 2: locked resource 1");
try{Thread.sleep(100);}catch(Exception e){}
synchronized(resource1){
System.out.println("Thread 2: locked resource 2");
}
}
}
};
t1.start();
t2.start();
}
}
[결과]
//서로 리소스를 잠그고 기다리는 상황으로 스레드가 종료되지 않는다
//Thread t1이 resource1을 잠그고 resource2 사용을 위해 기다린다
//Thread t2가 resource2을 잠그고 resource1 사용을 위해 기다린다
Thread 2: locked resource 2
Thread 1: locked resource 1
동기화를 통해 스레드가 특정 메서드 또는 블록을 동시에 실행하지 않고 동기화하도록 만들 수 있다. 메서드 또는 블록이 동기화된 것으로 선언되면 하나의 스레드만 해당 메서드 또는 블록에 들어갈 수 있다. 한 스레드가 동기화된 메서드 또는 블록을 수행 중이면 해당 메서드 또는 블록을 실행하려는 다른 스레드는 첫 번째 스레드가 해당 메서드 또는 블록을 실행할 때까지 기다려야 하며 스레드 간섭을 피하고 스레드 안전성을 달성한다.
Java의 동기화는 객체 잠금 또는 모니터라는 엔티티를 중심으로 구축된다. 다음은 잠금 또는 모니터에 대한 간략한 설명이다.
동기화된 블록은 하나의 인수를 가지며 이를 뮤텍스라고 한다. 동기화된 블록이 비정적 메서드, 인스턴스 이니셜라이저 또는 생성자와 같은 비정적 정의 블록 내에서 정의된 경우 이 뮤텍스는 해당 클래스의 인스턴스여야 한다. 동기화된 블록이 정적 메서드 또는 정적 초기화 프로그램과 같은 정적 정의 블록 내부에 정의된 경우 이 뮤텍스는 ClassName.class와 같아야 한다.
동기화된 정적 메서드에는 클래스 수준 잠금이 필요하고 동기화된 비정적 메서드에는 객체 수준 잠금이 필요한데 이 두 가지 방법을 동시에 실행할 수 있다.
스레드는 실행이 정상적으로 완료되었는지 또는 예외로 포착되었는지 여부에 관계없이 잠금을 해제해야 한다.
동기화 메서드보다 동기화 블럭이 메서드의 일부만 동기화하기 때문에 성능이 향상된다.
스레드는 다른 스레드의 알림을 기다리는 경우 WAITING 상태가 된다. 다른 스레드가 원하는 lock을 해제하기를 기다리는 경우 스레드는 BLOCKED 상태가 된다.
스레드가 객체에서 wait() 또는 join() 메서드를 호출할 때 WAITING 상태가 된다. 대기 상태로 전환하기 전에 스레드는 보유하고 있는 객체의 lock을 해제한다. 동일한 객체에서 다른 스레드 호출이 notify() 또는 notifyAll()될 때까지 WAITING 상태로 유지된다.
다른 스레드가 동일한 객체에 대하 notify() 또는 notifyAll()을 호출하면 해당 객체의 잠금을 기다리는 스레드 중 하나 또는 모든 것이 통지된다. 알림을 받은 모든 스레드는 객체를 즉시 잠그지 않는다. 현제 스레드가 lock을 해제하면 우선순위에 따라 객체 lock을 받는다. 그 때까지 그들은 차단된 상태에 있을 것이다.