Java 고급 (38)

반응형

 

스레드 상태

 

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

 

 

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

 

 

반응형
반응형

 

Process와 Thread

 

Process

: 실행 중인 프로그램

 

 

Multi Process

: 여러 개의 Thread를 실행하여 작동하는 프로그램

ex) 채팅

 

 

Multi Tasking

: 두 개 이상의 프로세스를 실행하여 일을 처리하는 것.

 

 

Thread

: 프로세스(process) 내에서 실제로 작업을 수행하는 주체

 

스레드는 비가역적이므로 종료한 스레드를 다시 불러와 실행할 수 없다. 똑같은 작업을 하고 싶을 시 새로운 스레드를 만들어 작동시켜야 한다.

 

 

싱글 스레드 프로그램 예시

 

 

package kr.or.ddit.basic;

public class T01ThreadTest {
	public static void main(String[] args) {
		// 싱글 스레드 프로그램
		for(int i=1; i<=200; i++) {
			System.out.print("*");
		}
		
		System.out.println();
		
		for (int i = 0; i <= 200; i++) {
			System.out.print("$");
		}
	}
}

 

결과 화면1

 

 

 

멀티 스레드 프로그램 예시

 

스레드 구조적 작동 방법

 

 

- 스레드 생성 방법
1. Thread클래스를 상속한 클래스의 인스턴스를 생성 > 해당 인스턴스의 start()메서드 호출.
2. Runnable인터페이스를 구현한 클래스의 인스턴스를 생성 > Thread객체의 인스턴스 생성 시 생성자의 파라미터값으로 넣어줌 > 생성된 Thread객체의 start() 메서드 호출.
3. 익명클래스를 이용하는 방법
  Runnable인터페이스를 구현한 익명 클래스를 Thread객체 생성 시 생성자의 파라미터값으로 넣어줌 > 생성된 Thread객체의 start()메서드 호출.

 

 

 

  • 방법 1 이용

두 개의 스레드를 사용

Thread 메소드에서 작동할 것들은 run() 에 넣어놔야함

package kr.or.ddit.basic;

public class T02ThreadTest {
	public static void main(String[] args) {
		// 멀티 스레드 프로그램
		
		// 스레드 생성하기
		
		// 방법1 : Thread클래스를 상속한 클래스의 인스턴스를 생성한 후 해당 인스턴스의 start()메서드를 호출한다.
		Thread th1 = new MyThread1();
		th1.start();
	}
}

class MyThread1 extends Thread {
	
	@Override
	public void run() {
		for(int i=1; i<=200; i++) {
			System.out.print("*");
			
			try {
				// Thread.sleep(시간) => 주어진 시간동안 작업을 잠시 멈춤.
				// 시간은 밀리세컨드 단위 사용 (1000 밀리세컨드 = 1초).
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

 

결과 화면2 : 100 밀리세컨드 시간 간격으로 * 을 찍음

 

 

 

  • 방법 2 이용
package kr.or.ddit.basic;

public class T02ThreadTest {
	public static void main(String[] args) {
		// 멀티 스레드 프로그램
		
		// 스레드 생성하기
		
		// 방법1 : Thread클래스를 상속한 클래스의 인스턴스를 생성한 후 해당 인스턴스의 start()메서드를 호출한다.
		Thread th1 = new MyThread1();
		th1.start();
		
		// 방법2 : Runnable인터페이스를 구현한 클래스의 인스턴스를 생성한 후 이 인스턴스를 Thread객체의 인스턴스를
		//		  생성할 때 생성자의 파라미터값으로 넣어준다. 이때 생성된 Thread객체의 start() 메서드를 호출한다.
		Runnable r = new MyThread2();
		Thread th2 = new Thread(r);
		th2.start();
	}
}

class MyThread1 extends Thread {
	
	@Override
	public void run() {
		for(int i=1; i<=200; i++) {
			System.out.print("*");
			
			try {
				// Thread.sleep(시간) => 주어진 시간동안 작업을 잠시 멈춤.
				// 시간은 밀리세컨드 단위 사용 (1000 밀리세컨드 = 1초).
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

class MyThread2 implements Runnable {
	@Override
	public void run() {
		for(int i=1; i<=200; i++) {
			System.out.print("$");
			
			try {
				// Thread.sleep(시간) => 주어진 시간동안 작업을 잠시 멈춤.
				// 시간은 밀리세컨드 단위 사용 (1000 밀리세컨드 = 1초).
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

 

결과 화면3

 

 

 

  • 방법 3 이용
package kr.or.ddit.basic;

public class T02ThreadTest {
	public static void main(String[] args) {
		// 멀티 스레드 프로그램
		
		// 스레드 생성하기
		
		// 방법1 : Thread클래스를 상속한 클래스의 인스턴스를 생성한 후 해당 인스턴스의 start()메서드를 호출한다.
		Thread th1 = new MyThread1();
		th1.start();
		
		// 방법2 : Runnable인터페이스를 구현한 클래스의 인스턴스를 생성한 후 이 인스턴스를 Thread객체의 인스턴스를
		//		  생성할 때 생성자의 파라미터값으로 넣어준다. 이때 생성된 Thread객체의 start() 메서드를 호출한다.
		Runnable r = new MyThread2();
		Thread th2 = new Thread(r);
		th2.start();
		
		// 방법3 : 익명클래스를 이용하는 방법
		//	  Runnable인터페이스를 구현한 익명 클래스를 Thread객체 생성 시 생성자의 파라미터값으로 넣어준다.
		//	  이 때 생성된 Thread객체의 start()메서드를 호출한다.
		Thread th3 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				for(int i=1; i<=200; i++) {
					System.out.print("@");
					
					try {
						// Thread.sleep(시간) => 주어진 시간동안 작업을 잠시 멈춤.
						// 시간은 밀리세컨드 단위 사용 (1000 밀리세컨드 = 1초).
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		});
		th3.start();
	}
}

class MyThread1 extends Thread {
	
	@Override
	public void run() {
		for(int i=1; i<=200; i++) {
			System.out.print("*");
			
			try {
				// Thread.sleep(시간) => 주어진 시간동안 작업을 잠시 멈춤.
				// 시간은 밀리세컨드 단위 사용 (1000 밀리세컨드 = 1초).
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

class MyThread2 implements Runnable {
	@Override
	public void run() {
		for(int i=1; i<=200; i++) {
			System.out.print("$");
			
			try {
				// Thread.sleep(시간) => 주어진 시간동안 작업을 잠시 멈춤.
				// 시간은 밀리세컨드 단위 사용 (1000 밀리세컨드 = 1초).
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

 

결과 화면4 : 메인 스레드 포함하여 총 4개의 스레드로 구정된 프로그램

 

 

- 방법 2와 방법 3의 차이

public class Test {
	public static void main(String[] args) {
		// extends Thread 를 사용할 때
		스레드이름1 th1 = new 스레드이름1();

		// implements Runnable 를 사용할 때
		Thread th2 = new Thread(new 스레드이름2());
}

class 스레드이름1 extends Thread {
	public 스레드이름1() {
	}
}

class 스레드이름2 implements Runnable {
	public 스레드이름2() {
	}
}

 

 

 

  • 스레드 사용법

extends Thread 또는 implements Runnable 하여 작동하여야 한다.

사용법
start() 스레드 작업 시작
run() 스레드 작업 진행 (main 메소드와 같은 기능으로 생각하면 됨)
Thread.sleep() 특정 시간동안 작업을 잠시 멈춤
join() 작업중인 스레드가 종료될 때까지 기다림
System.exit(0); 프로그램 종료

 

 

 

  • 스레드 예시

 

- System.currentTimeMillis() 이용 => 처리시간

package kr.or.ddit.basic;

public class T03ThreadTest {
	public static void main(String[] args) {
		// 스레드의 수행시간 체크하기
		Thread th = new Thread(new MyRunner());
		
		// UTC(Universal Time Coordinated 협정 세계 표준 시)를 사용하여
		// 1970년 1월 1일 0시 0분 0초를 기준으로 경과한 시간을 밀리세컨드(1/1000초) 단위로 나타낸다.
		long startTime = System.currentTimeMillis();
		
		th.start(); // 스레드 작업 시작
		
		try {
			th.join();
			// 현재 실행중인 스레드에서 작업중인 스레드(지금은 th스레드)가 종료될 때까지 기다린다.
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		long endTime = System.currentTimeMillis();
		
		System.out.println("경과 시간 : " + (endTime - startTime) + " ms");
	}
}

// 1 ~ 1000000000 까지의 합계를 구하기 위한 클래스
class MyRunner implements Runnable {
	@Override
	public void run() {
		long sum = 0;
		for (int i = 0; i <= 1000000000; i++) {
			sum += i;
		}
		System.out.println("합계 : " + sum);
	}
}

 

결과 화면5

 

 

=> 단일 스레드보다 멀티 스레드로 할 시 더 빠르게 작업할 수 있음

package kr.or.ddit.basic;

public class T04ThreadTest {
	/*
	 * 1 ~ 20억까지의 합계를 구하는 데 걸린 시간을 확인해보기
	 * 
	 * 전체 합계를 구하는 작업을 단일 스레드로 처리했을 때와 여러개의 스레드로 분할해서
	 * 작업할 때의 시간을 확인해보자.
	 */
	
	public static void main(String[] args) {
		// 단일 스레드로 처리할 때...
		SumThread sm = new SumThread(1, 2000000000);
		long startTime = System.currentTimeMillis();
		sm.start();
		try {
			sm.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		long endTime = System.currentTimeMillis();
		
		System.out.println("단독으로 처리 할 때의 처리시간(ms) : " + (endTime - startTime));
		System.out.println("\n\n");
		
		// 여러 스레드가 나누어 작업할 때
		SumThread[] sumThs = new SumThread[] {
				new SumThread(			1L,  500000000L),
				new SumThread(	500000000L, 1000000000L),
				new SumThread( 1000000000L, 1500000000L),
				new SumThread( 1500000000L, 2000000000L)
		};
		
		startTime = System.currentTimeMillis();
		
		for (Thread th : sumThs) {
			th.start();
		}
		
		for (Thread th : sumThs) {
			try {
				th.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		endTime = System.currentTimeMillis();
		System.out.println("여러 스레드로 나누어 처리 했을 때의 처리시간(ms) : "
				+ (endTime - startTime));
	}
}

class SumThread extends Thread {
	private long min, max;
	
	public SumThread(long min, long max) {
		this.min = min;
		this.max = max;
	}
	
	@Override
	public void run() {
		long sum = 0;
		for (long i = min; i < max; i++) {
			sum += i;
		}
		System.out.println(min + " ~ " + max + " 까지의 합 : " + sum);
	}
}

 

결과 화면6

 

 

- JOptionPane.showInputDialog 이용 => 입력 받기

package kr.or.ddit.basic;

import javax.swing.JOptionPane;

/**
 * 단일스레드에서 사용자 입력 처리
 */
public class T05ThreadTest {
	public static void main(String[] args) {
		String input = JOptionPane.showInputDialog("아무거나 입력하세요.");
		
		System.out.println("입력한 값은 " + input + "입니다.");
		
		for(int i=10; i>=1; i--) {
			System.out.println(i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

 

실행 시 화면이 뜸

 

결과 화면7 : 값 입력 후 첫줄 출력 후 10부터 1초 간격으로 1까지 출력됨

 

 

package kr.or.ddit.basic;

import javax.swing.JOptionPane;

public class T06ThreadTest {
	public static void main(String[] args) {
		Thread th1 = new DataInput();
		Thread th2 = new CountDown();
		
		th1.start(); 
		th2.start(); 
	}
}

/**
 * 사용자 입력을 받기 위한 스레드
 */
class DataInput extends Thread {
	@Override
	public void run() {
		String input = JOptionPane.showInputDialog("아무거나 입력하세요.");
		
		System.out.println("입력한 값은 " + input + "입니다.");
	}
}

/**
 * 카운트다운을 처리하기 위한 스레드
 */
class CountDown extends Thread {
	@Override
	public void run() {
		for(int i=10; i>=1; i--) {
			System.out.println(i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

 

결과 화면8-1 : 동시에 작동되며 값을 입력하지 않을 시 프로그램이 종료되지 않음

 

결과 화면8-2 : 값 입력시, main스레드 > CountDown스레드 > DataInput 스레드 순으로 종료됨

 

 

package kr.or.ddit.basic;

import javax.swing.JOptionPane;

public class T06ThreadTest {
	public static boolean inputCheck = false;
	
	public static void main(String[] args) {
		Thread th1 = new DataInput();
		Thread th2 = new CountDown();
		
		th1.start(); 
		th2.start(); 
	}
}

/**
 * 사용자 입력을 받기 위한 스레드
 */
class DataInput extends Thread {
	@Override
	public void run() {
		String input = JOptionPane.showInputDialog("아무거나 입력하세요.");
		
		T06ThreadTest.inputCheck = true;
		
		System.out.println("입력한 값은 " + input + "입니다.");
	}
}

/**
 * 카운트다운을 처리하기 위한 스레드
 */
class CountDown extends Thread {
	@Override
	public void run() {
		for(int i=10; i>=1; i--) {
			
			// 입력이 완료되었는지 여부를 확인하고 입력이 완료된 경우에는
			// run()메서드를 종료한다.
			if(T06ThreadTest.inputCheck) {
				return; // run()메서드가 종료되면 해당 스레드도 종료된다.
			}
			
			System.out.println(i);
			
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		// 10초가 경과되었는데도 입력이 없으면 프로그램을 종료한다.
		System.out.println("10초가 지났습니다. 프로그램을 종료합니다.");
		System.exit(0); // 프로그램 종료시키는 명령어
	}
}

 

결과 화면9-1 : 카운트 다운 실행 중에 입력 시 모든 스레드가 종료됨

 

결과 화면9-2 : 카운트 다운 시간이 전부 지나면 자동적으로 종료됨

 

 

 

- setPriority() 이용 => 우선 순위 변경

package kr.or.ddit.basic;

public class T08ThreadPriorityTest {
	public static void main(String[] args) {
		System.out.println("최대 우선순위 : " + Thread.MAX_PRIORITY);
		System.out.println("최소 우선순위 : " + Thread.MIN_PRIORITY);
		System.out.println("보통 우선순위 : " + Thread.NORM_PRIORITY);
		
		Thread[] ths = new Thread[] {
				new ThreadTest1(),
				new ThreadTest1(),
				new ThreadTest1(),
				new ThreadTest1(),
				new ThreadTest1(),
				new ThreadTest2()
		};
		
		// 우선 순위는 start()메서드를 호출하기 전에 설정해야 한다.
		for(int i=0; i<ths.length; i++) {
			if(i == 5) {
				ths[i].setPriority(10);
			} else {
				ths[i].setPriority(1);
			}
		}
		
		// 우선 순위 정보 출력하기
		for (Thread th : ths) {
			System.out.println(th.getName() + "의 우선 순위 : " + th.getPriority());
		}
		
		// 스레드 구동하기
		for (Thread th : ths) {
			th.start();
		}
	}
}

// 대문자를 출력하는 스레드
class ThreadTest1 extends Thread {
	@Override
	public void run() {
		for(char ch='A'; ch<='Z'; ch++) {
			System.out.println(ch);
			
			// 아무것도 하지 않는 반복문(시간 때우기용)
			for(long i=1; i<=1000000000L; i++) {}
		}
	}
}

// 소문자를 출력하는 스레드
class ThreadTest2 extends Thread {
	@Override
	public void run() {
		for(char ch='a'; ch<='z'; ch++) {
			System.out.println(ch);
			
			// 아무것도 하지 않는 반복문(시간 때우기용)
			for(long i=1; i<=1000000000L; i++) {}
		}
	}
}

 

결과 화면10 : 실행된 순서부터 차례대로 우선순위에 맞춰 출력됨 but 100% 우선 순위에 맞춰 실행되지 않을 수 있음

 

 

 

  • save() & setDaemon 이용 => 자동 저장 및 종료

 

Daemon Thread

: 다른 일반 스레드의 작업을 돕는 보조적인 스레드. 일반 스레드가 모두 종료되면 자동으로 종료된다.

start 메서드 호출 전에 설정해야 한다.

 

// start() 작동 전에 선언 필요!
스레드객체.setDaemon(true);
스레드객체.start();

 

 

package kr.or.ddit.basic;

public class T09ThreadDaemonTest {
	public static void main(String[] args) {
		AutoSaveThread autoSave = new AutoSaveThread();
		
		// 데몬스레드 설정은 start()메서드 호출 전에 해주어야 한다.
		autoSave.setDaemon(true);
		autoSave.start();
		
		for(int i=1; i<=20; i++) {
			System.out.println("작업 " + i);
			
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		System.out.println("메인 스레드 종료...");
	}
}

/**
 * 자동 저장기능을 제공하는 스레드
 */
class AutoSaveThread extends Thread {
	public void save() {
		System.out.println("작업 내용을 저장합니다.");
	}
	
	@Override
	public void run() {
		while (true) {
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			save(); // 저장기능 호출
		}
	}
}

 

결과 화면11-1

 

결과 화면11-2 : 속성(setDaemon) 주기 전에는 계속해서 작동함

 

 

반응형
반응형

 

Annotation

 

: 프로그램 소스코드 안에서 다른 프로그램을 위한 정보를 제공하는 주석처럼 사용되는 기술을 말한다.

프로그램에 영향을 미치지 않으며 프로그램에게 유용한 정보제공한다.

 

 

- 종류

1. 표준애너테이션 : 자바에서 기본적으로 제공하는 애너테이션

2. 메타애너테이션 : 어노테이션에 붙이는 어노테이션. 어노테이션을 정의하는 데 사용하며 어노테이션의 적용대상 또는 어노테이션을 유지하는 시간 등을 규정한다.

 

 

- 애너테이션 요소의 규칙

1. 요소타입은 기본형, String, enum, annotation, Class만 허용된다.
2. ()안에 매개변수를 선언할 수 없다.
3. 예외를 선언할 수 없다.
4. 요소타입에 제너릭타입글자를 사용할 수 없다.

 

 

 

  • 사용법
@interface 애너테이션 이름 {
 		요소타입 타입요소이름(); // 반환값이 있고 매개변수는 없는 추상메서드의 형태
 			...
}

 

 

package kr.or.ddit.basic;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/*
 * Annotation 에 대하여...
 * 
 * 프로그램 소스코드 안에서 다른 프로그램을 위한 정보로 미리 약속된 형식으로 포함시킨 것(JDK1.5부터 지원)
 * 
 * 주석처럼 프로그램에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공함.
 * 
 * 종류 :	1. 표준애너테이션 
 * 		2. 메타애너테이션(애너테이션을 위한 애너테이션, 즉 애너테이션을 정의할 때 사용하는 애너테이션)
 * 
 * 애너테이션 타입 정의하기
 * @interface 애너테이션 이름 {
 * 		요소타입 타입요소이름(); // 반환값이 있고 매개변수는 없는 추상메서드의 형태
 * 			...
 * 	}
 * 
 * 애너테이션 요소의 규칙
 * 	1. 요소타입은 기본형, String, enum, annotation, Class만 허용된다.
 *  2. ()안에 매개변수를 선언할 수 없다.
 *  3. 예외를 선언할 수 없다.
 *  4. 요소타입에 제너릭타입글자를 사용할 수 없다.
 */

@Target({ElementType.METHOD, ElementType.TYPE}) // 적용가능 대상 지정함
@Retention(RetentionPolicy.RUNTIME) // 유지되는 기간 지정함.
public @interface PrintAnnotation {
	String value() default "-"; // 디폴트로 -값이 지정됨
	int count() default 20;
}

 

 

package kr.or.ddit.basic;

public class Service {
	
	// 아무것도 안 넣기에 value -, count 20이 디폴트로 주어진 것
	@PrintAnnotation
	public void method1() {
		System.out.println("메서드1이 호출되었습니다.");
	}
	
	@PrintAnnotation(value = "%")
	public void method2() {
		System.out.println("메서드2이 호출되었습니다.");
	}
	
	@PrintAnnotation(value = "#", count = 25)
	public void method3() {
		System.out.println("메서드3이 호출되었습니다.");
	}
}

 

 

 

  • PrintAnnotation 확인 예시
package kr.or.ddit.basic;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

public class AnnotationTest {
	public static void main(String[] args) {
		Method[] methodArr = Service.class.getDeclaredMethods();
		
		for (Method m : methodArr) {
			System.out.println("메서드 명 : " + m.getName());
			Annotation[] annos = m.getDeclaredAnnotations();
			
			for (Annotation anno : annos) {
				if(anno.annotationType().getSimpleName().equals("PrintAnnotation")) {
					PrintAnnotation printAnno = (PrintAnnotation) anno;
					int count = printAnno.count();
					String value = printAnno.value();
					
					for(int i=0; i<count; i++) {
						System.out.print(value);
					}
				}
			}
			System.out.println(); // 줄바꿈 처리
		}
	}
}

 

결과 화면1 : method1은 20번, method2는 20번, method3은 25번 출력됨

 

 

 

리플렉션 사용

 

어노테이션 정보를 접근해 보기 위해 리플렉션을 사용한다.

 

- 순서

1. 클래스 오브젝트생성하여 클래스의 정보를 가져옴

2. 클래스의 메타데이터 가져옴

 

 

  • Java Reflection

1. 리플렉션은 런타임 시점에 클래스 또는 멤버변수, 메서드, 생성자 등에 대한 정보를 가져오거나 수정할 수 있고, 새로운 객체를 생성하거나 메서드를 실행할 수 있다. (컴파일 시점에 해당정보를 알 수 없는 경우(소스코드의 부재)에 유용하게 사용될 수 있다.)
2. 리플렉션 API는 java.lang.reflect 패키지와 java.lang.Class를 통해 제공된다.
3. java.lang.Class의 주요 메서드
  - getName(), getSuperClass(), getInterfaces(), getModifiers() 등.
4. java.lang.reflect 패키지의 주요 클래스
  - Field, Method, Constructor, Modifier 등.

 

 

 

  • Class 오브젝트(클래스정보를 담고 있는 객체)를 생성하기
package kr.or.ddit.ref;

/**
 * Class 오브젝트(클래스정보를 담고 있는 객체)를 생성하기
 */
public class T01ClassObjectCreationTest {
	/*
	 * Java Reflection 에 대하여...
	 * 
	 * 1. 리플렉션은 런타임 시점에 클래스 또는 멤버변수, 메서드, 생성자 등에 대한 정보를 가져오거나 수정할 수 있고,
	 * 	새로운 객체를 생성하거나 메서드를 실행할 수 있다.
	 * 	(컴파일 시점에 해당정보를 알 수 없는 경우(소스코드의 부재)에 유용하게 사용될 수 있다.)
	 * 2. 리플렉션 API는 java.lang.reflect 패키지와 java.lang.Class를 통해 제공된다.
	 * 3. java.lang.Class의 주요 메서드
	 * 	- getName(), getSuperClass(), getInterfaces(), getModifiers() 등.
	 * 4. java.lang.reflect 패키지의 주요 클래스
	 * 	- Field, Method, Constructor, Modifier 등.
	 */
	
	public static void main(String[] args) throws ClassNotFoundException {
		// 방법1 : Class. forName(클래스 이름) 메서드 이용하기
		// 클래스 이름에 대한 정보를 klass에 저장
		Class<?> klass = Class.forName("kr.or.ddit.ref.T01ClassObjectCreationTest");
		
		// 방법2 : getClass() 메서드 이용하기
		klass = new T01ClassObjectCreationTest().getClass();
		
		// 방법3 : .class 이용하기
		klass = T01ClassObjectCreationTest.class;
	}
}

 

 

 

  • 클래스의 메타데이터 가져오기 - 예시 1
package kr.or.ddit.ref;

import java.io.Serializable;

public class SampleVO implements Serializable{
	private String id;
	protected String name;
	public int age;
	
	public SampleVO(String id, String name, int age) {
		super();
		this.id = id;
		this.name = name;
		this.age = age;
	}
	
	public SampleVO() {
		
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
}

 

package kr.or.ddit.ref;

import java.lang.reflect.Modifier;

/**
 * Class의 메타데이터 가져오기
 */
public class T02ClassMetadataTest {
	public static void main(String[] args) {
		
		// 클래스 오브젝트 생성하기
		Class<?> clazz = SampleVO.class;
		
		System.out.println("심플클래스명 : " + clazz.getSimpleName());
		System.out.println("클래스명 : " + clazz.getName());
		System.out.println("상위 클래스명 : " + clazz.getSuperclass().getName());
		
		// 패키지 정보 가져오기
		Package pkg = clazz.getPackage();
		System.out.println("패키지 정보 : " + pkg.getName());
		System.out.println();
		
		// 해당 클래스에서 구현하고 있는 인터페이스 목록 가져오기
		Class<?>[] interfaces = clazz.getInterfaces();
		
		System.out.println("인터페이스 목록 => ");
		for (Class<?> inf : interfaces) {
			System.out.println(inf.getName() + " | ");
		}
		System.out.println();
		
		// 클래스의 접근제어자 정보 가져오기
		int modFlag = clazz.getModifiers();
		
		System.out.println("접근제어자 정보 : " + Modifier.toString(modFlag));
	}
}

 

결과 화면2

 

 

 

  • 클래스의 메타데이터 가져오기 - 예시 2
package kr.or.ddit.ref;

import java.io.Serializable;

import kr.or.ddit.basic.PrintAnnotation;

public class SampleVO implements Serializable{
	private String id;
	protected String name;
	public int age;
	
	public SampleVO(String id, String name, int age) {
		super();
		this.id = id;
		this.name = name;
		this.age = age;
	}
	
	public SampleVO() {
		
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	@PrintAnnotation
	public void setName(String name) throws Exception{
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
}

 

package kr.or.ddit.ref;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * 클래스에 선언된 메서드의 메타정보 가져오기
 */
public class T03MethodMetadataTest {
	public static void main(String[] args) throws ClassNotFoundException {
		// Class 객체 생성
		Class<?> klass = Class.forName("kr.or.ddit.ref.SampleVO");
		
		// 클래스에 선언된 모든 메서드 정보 가져오기
		// 메소드 타입의 배열로 가져오게 됨.
		Method[] methodArr = klass.getDeclaredMethods();
		
		for (Method m : methodArr) {
			System.out.println("메서드 명 : " + m.getName());
			System.out.println("메서드 리턴타입 : " + m.getReturnType());
			
			// 해당 메서드의 접근제어자 정보 가져오기
			int modFlag = m.getModifiers();
			System.out.println("메서드 접근제어자 : " + Modifier.toString(modFlag));
			System.out.println();
			
			// 해당 메서드의 파라미터 정보 가져오기
			Class<?>[] paramArr = m.getParameterTypes();
			System.out.println("메서드 파라미터 타입 => ");
			for (Class<?> clazz : paramArr) {
				System.out.println(clazz.getName() + " | ");
			}
			System.out.println();
			
			// 해당 메서드에서 던지는 예외타입 가져오기
			Class<?>[] exTypeArr = m.getExceptionTypes();
			System.out.println("메서드에서 던지는 예외타입 목록 : ");
			for (Class<?> clazz : exTypeArr) {
				System.out.println(clazz.getName() + " | ");
			}
			System.out.println();
			
			// 해당 메서드에 존재하는 Annotation 타입정보
			Annotation[] annos = m.getDeclaredAnnotations();
			System.out.println("Annotation 타입 => ");
			for (Annotation anno : annos) {
				System.out.println(anno.annotationType().getName() + " | ");
			}
			System.out.println();
			System.out.println("-----------------------------------------------------");
		}
	}
}

 

결과 화면3-1

 

결과 화면3-2

 

 

반응형
1 ··· 6 7 8 9 10 11 12 13