반응형

 

스레드 상태

 

NEW : 스레드가 생성되고 아직 start()가 호출되지 않은 상태
RUNNABLE : 실행 중 또는 실행 가능한 상태
BLOCKED : 동기화블럭에 의해서 일시정지(락이 풀릴 때까지 기다리는 상태)
WAITING, TIMES_WAITING : 스레드의 작업이 종료되지는 않았지만 실행가능하지 않은 일시정지 상태. TIMED_WAITING은 일시정지 시간이 지정된 경우임.
TERMINATED : 스레드의 작업이 종료된 상태

 

스레드 상태

 

 

package kr.or.ddit.basic;

public class T10ThreadStateTest {
	/*
	 * <스레드의 상태에 대하여...>
	 * 
	 * - NEW : 스레드가 생성되고 아직 start()가 호출되지 않은 상태
	 * - RUNNABLE : 실행 중 또는 실행 가능한 상태
	 * - BLOCKED : 동기화블럭에 의해서 일시정지(락이 풀릴 때까지 기다리는 상태)
	 * - WAITING, TIMES_WAITING : 스레드의 작업이 종료되지는 않았지만 실행가능하지 않은 일시정지 상태
	 * 							  TIMED_WAITING은 일시정지 시간이 지정된 경우임.
	 * - TERMINATED : 스레드의 작업이 종료된 상태
	 */
	public static void main(String[] args) {
		TargetThread tTh = new TargetThread();
		
		StatePrintThread spTh = new StatePrintThread(tTh);
		
		spTh.start();
	}
}

// 스레드의 상태를 출력하기 위한 스레드
class StatePrintThread extends Thread {
	private Thread targetThread; // 상태를 모니터링할 대상 스레드

	public StatePrintThread(Thread targetThread) {
		this.targetThread = targetThread;
	}
	
	@Override
	public void run() {
		while(true) {
			// 스레드의 상태값 확인하기
			Thread.State state = targetThread.getState();
			System.out.println("대상 스레드의 상태값 : " + state.name()); // NEW, RUNNABLE ...
			
			// NEW 상태인지 검사
			if(state == Thread.State.NEW) {
				targetThread.start();
			}
			
			// 대상 스레드가 종료되었는지 검사
			if(state == Thread.State.TERMINATED) {
				break;
			}
			
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

// Target용 스레드
class TargetThread extends Thread {
	@Override
	public void run() {
		// RUNNABLE 상태
		for(long i=1; i<=1000000000L; i++) {} // 시간 지연용
		
		// TIMES_WAITING 상태
		try {
			Thread.sleep(1500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		// RUNNABLE 상태
		for(long i=1; i<=1000000000L; i++) {} // 시간 지연용
	}
}

 

결과 화면1

 

 

 

스레드 예시

 

package kr.or.ddit.basic;

import java.util.ArrayList;
import java.util.List;

public class T11DisplayCharacterList {
	/*
	 * 3개(명)의 스레드가 각각 알파벳 대문자를 출력하는데
	 * 출력을 끝낸 순서대로 결과를 나타내는 프로그램 작성하기
	 */
	
	public static void main(String[] args) {
		List<DisplayCHaracter> disCharList = new ArrayList<DisplayCHaracter>();
		disCharList.add(new DisplayCHaracter("홍길동"));
		disCharList.add(new DisplayCHaracter("심청이"));
		disCharList.add(new DisplayCHaracter("빨간모자"));
		disCharList.add(new DisplayCHaracter("호랑이"));
		
		for (Thread th : disCharList) {
			th.start();
		}
		
	}
}

class DisplayCHaracter extends Thread {
	private String name;	// 스레드 이름
	private int rank;		// 순위
	
	public DisplayCHaracter(String name) {
		super(name); // super는 부모클래스의 생성자를 의미함, 이 과정을 통해서 스레드 이름을 정함
		this.name = name;
	}

	public int getRank() {
		return rank;
	}

	public void setRank(int rank) {
		this.rank = rank;
	}
	
	@Override
	public void run() {
		for(char ch='A'; ch<='Z'; ch++) {
			System.out.println(name + "의 출력문자 : " + ch);
			try {
				Thread.sleep( (int)(Math.random()*301+200)); // 200 ~ 500 => 0.2초 0.5초 사이
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(name + " 출력 끝...");
	}
}

 

결과 화면2 : 랜덤으로 먼저 출력하는 사람이 달라짐

 

 

=> 순위도 추가

package kr.or.ddit.basic;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class T11DisplayCharacterList {
	/*
	 * 3개(명)의 스레드가 각각 알파벳 대문자를 출력하는데
	 * 출력을 끝낸 순서대로 결과를 나타내는 프로그램 작성하기
	 */
	static int CurrRank = 1; // 현재 순위 정보
	
	public static void main(String[] args) {
		List<DisplayCHaracter> disCharList = new ArrayList<DisplayCHaracter>();
		disCharList.add(new DisplayCHaracter("홍길동"));
		disCharList.add(new DisplayCHaracter("심청이"));
		disCharList.add(new DisplayCHaracter("빨간모자"));
		disCharList.add(new DisplayCHaracter("호랑이"));
		
		for (Thread th : disCharList) {
			th.start();
		}
		
		// 경기 끝난 후 순위를 출력하기 위해 기다림
		for (Thread th : disCharList) {
			try {
				th.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		Collections.sort(disCharList); // 정렬하기
		
		System.out.println("경기 끝...");
		System.out.println("---------------------------");
		System.out.println("경 기 결 과");
		System.out.println();
		System.out.println("===========================");
		System.out.println("순 위\t:\t이름");
		System.out.println("---------------------------");
		for (DisplayCHaracter dc : disCharList) {
			System.out.println(dc.getRank() + "\t:\t" + dc.getName());
		}
		
	}
}

class DisplayCHaracter extends Thread implements Comparable<DisplayCHaracter>{
	private String name;	// 스레드 이름
	private int rank;		// 순위
	
	public DisplayCHaracter(String name) {
		super(name); // super는 부모클래스의 생성자를 의미함
		this.name = name;
	}

	public int getRank() {
		return rank;
	}

	public void setRank(int rank) {
		this.rank = rank;
	}
	
	@Override
	public void run() {
		for(char ch='A'; ch<='Z'; ch++) {
			System.out.println(name + "의 출력문자 : " + ch);
			try {
				Thread.sleep( (int)(Math.random()*301+200)); // 200 ~ 500 => 0.2초 0.5초 사이
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		setRank(T11DisplayCharacterList.CurrRank++);
		
		System.out.println(name + " 출력 끝...");
	}

	// 순위
	@Override
	public int compareTo(DisplayCHaracter dc) {
		return new Integer(getRank()).compareTo(dc.getRank());
	}
}

 

결과 화면3

 

 

 

yield() 메소드

 

1. 동등한 우선순위 이상의 다른 스레드에게 실행기회를 제공.(양보)
2. 스레드의 상태를 Runnable 상태로 바꿈. (실행 > 실행대기)
3. yield()메서드를 실행한다고 해서 곧바로 Runnable상태로 전이된다고 확신할 수 없음.

 

package kr.or.ddit.basic;

public class T12ThreadYieldTest {
	/*
	 * yield() 메서드에 대하여...
	 * 
	 * 1. 현재 실행 대기 중인 동등한 우선순위 이상의 다른 스레드에게 실행기회를 제공한다.(양보)
	 * 2. 현재 실행중인 스레드의 상태를 Runnable 상태로 바꾼다.
	 * 3. yield()메서드를 실행한다고 해서 현재 실행중인 스레드가 곧바로 Runnable상태로 전이된다고 확신할 수 없다.
	 */
	
	public static void main(String[] args) {
		Thread th1 = new YieldHtreadEx1();
		Thread th2 = new YieldHtreadEx2();
		
		th1.start();
		th2.start();
	}
}

class YieldHtreadEx1 extends Thread {
	public YieldHtreadEx1() {
		super("양보 스레드");
	}
	
	@Override
	public void run() {
		for(int i=0; i<10; i++) {
			// Thread.currentThread() : 현재 실행중인 스레드 객체를 리턴
			System.out.println(Thread.currentThread().getName() + " : " + i);
			
			for(int j=0; j<=1000000000; j++) {}
			
			Thread.yield(); // 양보하기
		}
	}
}

class YieldHtreadEx2 extends Thread {
	public YieldHtreadEx2() {
		super("비양보 스레드");
	}
	
	@Override
	public void run() {
		for(int i=0; i<10; i++) {
			System.out.println(Thread.currentThread().getName() + " : " + i);
			
			for(int j=0; j<=1000000000; j++) {}
		}
	}
}

 

결과 화면4

 

 

 

스레드 종료

 

 

  • 1. 메서드를 이용하지 않고 스레드 종료
package kr.or.ddit.basic;

public class T13ThreadStop {
	public static void main(String[] args) {
		ThreadStopEx1 th1 = new ThreadStopEx1();
		th1.start();
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		th1.setStop(true); // 중지 시키기
	}
}

class ThreadStopEx1 extends Thread {
	private boolean stop;
	
	public void setStop(boolean stop) {
		this.stop = stop;
	}
	
	@Override
	public void run() {
		while(!stop) {
			System.out.println("스레드 작업 중...");
		}
		System.out.println("자원 정리 중...");
		System.out.println("실행 종료.");
	}
}

 

결과 화면5: 상태 플래그를 이용하여 중지

 

 

 

  • 2. stop() 메소드를 이용하여 스레드 종료
package kr.or.ddit.basic;

public class T13ThreadStop {
	public static void main(String[] args) {
		ThreadStopEx1 th1 = new ThreadStopEx1();
		th1.start();
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		th1.stop(); // 중지 시키기
	}
}

class ThreadStopEx1 extends Thread {
	private boolean stop;
	
	public void setStop(boolean stop) {
		this.stop = stop;
	}
	
	@Override
	public void run() {
		while(!stop) {
			System.out.println("스레드 작업 중...");
		}
		System.out.println("자원 정리 중...");
		System.out.println("실행 종료.");
	}
}

 

결과 화면6: 결과 화면5의 방법과는 다름. stop() 방식을 사용하지 않는 것이 바람직하다.

 

 

 

  • 3. interrupt()메서드를 이용하여 스레드 종료

 

1. sleep()메서드나 join()메서드 등을 호출했을 때 interrupt()메서드를 호출하면 InterruptedException이 발생.

 

interrupt() : 인터럽트 걸기

package kr.or.ddit.basic;

public class T13ThreadStop {
	public static void main(String[] args) {
		ThreadStopEx2 th2 = new ThreadStopEx2();
		th2.start();
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		th2.interrupt(); // 인터럽트 걸기
		
	}
}

// interrupt()메서드를 이용하여 스레드를 멈추는 방법
class ThreadStopEx2 extends Thread {
	@Override
	public void run() {
		// 방법1 => sleep()메서드나 join()메서드 등을 호출했을 때 interrupt()메서드를 호출하면
		//		   InterruptedException이 발생한다.
		try {
			while (true) {
				System.out.println("스레드 실행 중...");
				Thread.sleep(1);
			}
		} catch (InterruptedException ex) {}
		
		System.out.println("자원 정리 중...");
		System.out.println("실행 종료.");
	}
}

 

결과 화면7: try-catch와 interrupt() 메소드 이용

 

 

2. interrupt()메서드가 호출되었는지 검사

 

2-1. 스레드의 인스턴스 객체용 메서드를 이용하는 방법

isInterrupted() : 인터럽트가 걸리면 true

package kr.or.ddit.basic;

public class T13ThreadStop {
	public static void main(String[] args) {
		ThreadStopEx2 th2 = new ThreadStopEx2();
		th2.start();
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		th2.interrupt(); // 인터럽트 걸기
	}
}

// interrupt()메서드를 이용하여 스레드를 멈추는 방법
class ThreadStopEx2 extends Thread {
	@Override
	public void run() {
		// 방법 2 => interrupt()메서드가 호출되었는지 검사하기
		while(true) {
			System.out.println("스레드 실행 중...");
			
			// 검사 방법1 => 스레드의 인스턴스 객체용 메서드를 이용하는 방법
			if(this.isInterrupted()) {
				// isInterrupted() : interrupt가 걸렸으면 true
				System.out.println("인스턴스 메서드 호출됨 : " + this.isInterrupted());
				break;
			}
		}
		
		System.out.println("자원 정리 중...");
		System.out.println("실행 종료.");
	}
}

 

결과 화면8

 

 

2-2. 스레드의 정적메서드를 이용하는 방법

Thread.interrupted() : 처음 인터럽트가 걸리면 true가 되지만 곧 false로 바뀜. (static 변수)

package kr.or.ddit.basic;

public class T13ThreadStop {
	public static void main(String[] args) {
		
		ThreadStopEx2 th2 = new ThreadStopEx2();
		th2.start();
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		th2.interrupt(); // 인터럽트 걸기
		
	}
}

// interrupt()메서드를 이용하여 스레드를 멈추는 방법
class ThreadStopEx2 extends Thread {
	@Override
	public void run() {
		// 방법 2 => interrupt()메서드가 호출되었는지 검사하기
		while(true) {
			System.out.println("스레드 실행 중...");
            
			// 검사 방법2 => 스레드의 정적메서드를 이용하는 방법
			if(Thread.interrupted()) {
				System.out.println("정적 메서드 interrupted() 호출됨 : "
								+ Thread.interrupted());
				break;
			}
		}
		
		System.out.println("자원 정리 중...");
		System.out.println("실행 종료.");
	}
}

 

결과 화면9

 

 

 

데이터 공통 사용법

 

=> 스레드들이 공유 데이터를 이용하고 싶다면 공유 객체가 필요하다.

 

 

  • 사용 방법

1. 공통 데이터 관리할 클래스 정의
2. 해당 클래스로 객체 생성
3. 각 스레드에 공유객체 제공

ex) 원주율을 계산하는 스레드가 있고, 계산된 원주율을 출력하는 스레드가 있다.
원주율을 계산한 후 이 값을 출력하는 프로그램을 작성하시오.
(이 때 원주율 데이터를 관리하기 위한 공유 객체가 필요하다.)

 

volatile를 사용해야 한다.

package kr.or.ddit.basic;

public class T14ThreadShareDataTest {
	/*
	 * 스레드에서 데이터를 공통으로 사용하는 방법
	 * 
	 * 1. 공통 데이터를 관리할 클래스를 정의한다.
	 * 2. 해당 클래스로 객체를 생성한다.
	 * 3. 각각의 스레드에 공유객체를 제공한다.
	 * 
	 * 예) 원주율을 계산하는 스레드가 있고, 계산된 원주율을 출력하는 스레드가 있다.
	 * 원주율을 계산한 후 이 값을 출력하는 프로그램을 작성하시오.
	 * (이 때 원주율 데이터를 관리하기 위한 공유 객체가 필요하다.)
	 */
	
	public static void main(String[] args) {
		ShareData sd = new ShareData();
		
		CalcPIThread cTh = new CalcPIThread(sd);
		PrintPIThread pTh = new PrintPIThread(sd);
		
		cTh.start();
		pTh.start();
	}
}

// 원주율 데이터를 관리하기 위한 공유클래스 정의
class ShareData {
	private double result; // 원주율 저장할 변수
	
	// volatile가 없으면 값이 출력되지 않음
	volatile private boolean isOk; //원주율 계산이 완료 되었는지 확인하기 위한 변수
	/*
	 * volatile => 선언된 변수를 컴파일러의 최적화 대상에서 제외시킨다.
	 * 	      즉, 값이 변경되는 즉시 변수에 적용시킨다.
	 */
     
	public double getResult() {
		return result;
	}
	public void setResult(double result) {
		this.result = result;
	}
	public boolean isOk() {
		return isOk;
	}
	public void setOk(boolean isOk) {
		this.isOk = isOk;
	}
}

// 원주율을 계산하기 위한 스레드
class CalcPIThread extends Thread {
	private ShareData sd;

	public CalcPIThread(ShareData sd) {
		this.sd = sd;
	}
	
	@Override
	public void run() {
		/*
		 * 원주율 = (1/1 - 1/3 + 1/5 - 1/7 + 1/9 ......) * 4;
		 * 			1  -  3  + 	5  -  7  +  9	=> 분모
		 * 			0     1     2     3     4	=> 2로 나눈 몫
		 */
		double sum = 0.0;
		for(int i=1; i<=1500000000; i+=2) {
			if( ((i/2) % 2) == 0 ) { // 2로 나눈 몫이 짝수이면..
				sum += (1.0/i);
			} else { // 2로 나눈 몫이 홀수이면..
				sum -= (1.0/i);
			}
		}
		
		sd.setResult(sum * 4); // 계산된 원주율값 설정함.
		sd.setOk(true); // 계산이 완료되었음을 설정함.
	}
}

// 계산된 원주율을 출력하기 위한 스레드
class PrintPIThread extends Thread {
	private ShareData sd;

	public PrintPIThread(ShareData sd) {
		this.sd = sd;
	}
	
	@Override
	public void run() {
		while(true) {
			if(sd.isOk()) {
				break;
			}
		}
		System.out.println();
		System.out.println("계산된 원주율 : " + sd.getResult());
		System.out.println("       PI : " + Math.PI);
	}
}

 

결과 화면10: volatile를 사용함. 클래스 PrintPIThread가 캐시에 있는 isOK값을 확인하지 말고 메모리에 있는 값을 확인하게 하여 변화되었을 때 실시간으로 작동하게 함. (but 속도가 느림)

 

 

package kr.or.ddit.basic;

public class T15SyncThreadTest {
	public static void main(String[] args) {
		ShareObject sObj = new ShareObject();
		WorkThread wTh1 = new WorkThread(sObj, "첫번째");
		WorkThread wTh2 = new WorkThread(sObj, "두번째");
		
		wTh1.start();
		wTh2.start();
	}
}

// 공유객체용 클래스
class ShareObject {
	private int sum = 0;
	
	public void add() {
		for(int i=0; i<1000000000; i++) {} // 동기화 전까지 시간벌기용
		
		int n= sum;
		n += 10;
		sum = n;
		
		System.out.println(Thread.currentThread().getName() + " 합 계 : " + sum);
	}
}

// 작업용 스레드
class WorkThread extends Thread {
	private  ShareObject sObj;

	public WorkThread(ShareObject sObj, String name) {
		super(name);
		this.sObj = sObj;
	}
	
	@Override
	public void run() {
		for(int i=1; i<=10; i++) {
			sObj.add();
		}
	}
}

 

결과 화면11

 

 

차례대로 출력되지 않기에 처리해줘야 함 => 동기화를 해줘야 한다.

 

 

반응형