관리 메뉴

거니의 velog

230830_스레드 2 본문

대덕인재개발원_자바기반 애플리케이션

230830_스레드 2

Unlimited00 2023. 8. 29. 17:43

[ConstTest.java]

package kr.or.ddit.basic;

public class ConstTest {

	public static final int RED = 1;
	public static final int GREEN = 2;
	public static final int BLUE = 3;
	
	public static final int ONE = 1;
	public static final int TWO = 2;
	public static final int THREE = 3;

}

[EnumTest.java]

package kr.or.ddit.basic;

/*
 * enum(열거형) ==> 서로 관련있는 상수들의 집합을 나타낸다.
 * 			  ==> 클래스처럼 보이게 하는 상수
 * 			   
 * - 작성 위치
 * 		1) 일반 클래스처럼 독립된 java파일로 작성할 수 있다.
 * 		2) 하나의 java 파일에 클래스와 같이 작성할 수 있다.
 * 		3) 클래스 안에 내부 클래스처럼 작성할 수 있다.
 * 
 * - 열거형의 속성 및 메서드
 * 		1) name()    	    ==> 열거형 상수의 이름을 문자열로 반환한다.
 * 		2) ordinal() 	    ==> 열거형 상수가 정의된 순서값(index값)을 반환한다. (0부터 시작)
 * 		3) valueOf("상수명")  ==> 지정된 열거형에서 '상수명'과 일치하는 열거형 상수를 반환한다.
 * 		4) 열거형이름.상수명         ==> valueOf() 메서드와 같다.
 * 		5) 열거형이름.values() ==> 열거형의 모든 상수들을 배열에 담아 반환한다.
 * 
 * - 열거형 선언하기
 * 방법1)
 * 		enum 열거형이름 { 상수명1, 상수명2, ..., 상수명n }
 * 
 * 방법2) ==> 열거형 상수에 데이터를 셋팅해서 처리하는 방법
 * 
 * 		enum 열거형이름 {
 * 			상수명1(값들...), 상수명2(값들...), ..., 상수명n(값들...);
 * 
 * 			// '값들'이 저장될 변수들을 선언한다. ('값들'개수에 맞게 변수 선언을 한다.)
 * 			private 자료형이름 변수명1;
 * 			private 자료형이름 변수명2;
 * 			~~~
 * 
 * 			// 열거형의 생성자를 만든다.
 * 			// ==> 열거형 생성자는 '열거형 상수에 지정된 값들'을 '변수'에 셋팅하는 역할을 한다.
 * 			// ==> 열거형 생성자는 묵시적으로 접근 제한자가 'private'이다.
 * 
 * 			// '변수명'은 '값들'과 개수가 같고, 각각의 '값들'과 자료형이 맞아야 한다.
 * 			private 열거형이름(자료형 변수명, ...) {
 * 				위에서 선언한 변수들을 초기화하는 작업을 한다.
 * 				...
 * 			}
 * 
 * 			// 위에서 구성된 '값들'을 외부에서 불러올 수 있는 getter 메서드를 작성한다.
 * 		}
 * 
 */

public class EnumTest {
	
	// 3) 클래스 안에 내부 클래스처럼 작성할 수 있다.
	public enum Color { RED, GREEN, BLUE }
	
	public enum Count { ONE, TWO, THREE }
	
	public enum Season {
		
		// 상수명(값들...) 형식의 선언
		봄("3월부터 5월까지", 13), 여름("6월부터 8월까지", 25),
		가을("9월부터 11월까지", 15), 겨울("12월부터 2월까지", 1);
		
		// 값들이 저장될 변수 선언
		private String span;
		private int temp;
		
		// 생성자
		Season(String span, int temp) { // private Season(String span, int temp) {} 와 같다.
			this.span = span;
			this.temp = temp;
		}
		
		// getter 메서드 작성
		public String getSpan() {
			return span;
		}
		
		public int getTemp() {
			return temp;
		}
		
	}

	public static void main(String[] args) {
		
//		System.out.println("Red : " + ConstTest.RED); // Red : 1
//		System.out.println("Three : " + ConstTest.THREE); // Three : 3
//		
//		if(ConstTest.RED == ConstTest.TWO) {
//			System.out.println("......");
//		}else {
//			System.out.println("@@@@@@");
//		}
		
		// -----------------------------------------------------------
		
		Color myCol = Color.valueOf("GREEN"); // Color.GREEN; 와 같다.
		Count myCnt = Count.ONE; // Count.valueOf("ONE"); 와 같다.
		
		System.out.println("myCol : " + myCol.name()); // myCol : GREEN
		System.out.println("myCnt : " + myCnt.name()); // myCnt : ONE
		System.out.println();
		
		System.out.println("myCol ordinal : " + myCol.ordinal()); // myCol ordinal : 1
		System.out.println("myCnt ordinal : " + myCnt.ordinal()); // myCnt ordinal : 0
		System.out.println();
		
		// 서로 다른 종류의 열거형 끼리의 비교는 불가능하다. 따라서 상수값으로 직접 비교하는 논리적 오류를 해결할 수 있다.
//		if(myCol == myCnt) {
//			System.out.println("같다...");
//		}
		
		if(myCol == Color.GREEN) { // 같다...
			System.out.println("같다...");
		}
		
		// 열거형을 switch문에서 사용할 때 case문에는 '열거형이름'을 생략한 '상수명'만 지정해서 비교해야 한다.
		switch(myCnt) { // ONE 상수값
			case ONE:
				System.out.println("ONE 상수값"); break;
			case TWO:
				System.out.println("TWO 상수값"); break;
			case THREE:
				System.out.println("THREE 상수값"); break;
		}
		
		System.out.println("----------------------------------------");
		
		Season ss = Season.valueOf("봄");
		System.out.println("name : " + ss.name()); // name : 봄
		System.out.println("ordinal : " + ss.ordinal()); // ordinal : 0
		System.out.println("span : " + ss.getSpan()); // span : 3월부터 5월까지
		System.out.println("temp : " + ss.getTemp()); // temp : 13
		System.out.println();
		
		for(Season time : Season.values()) {
			System.out.println(time.name() + " == " + time + " ==> (" + time.getSpan() + ", " + time.getTemp() + ")");
		}
		System.out.println();
		
//		봄 == 봄 ==> (3월부터 5월까지, 13)
//		여름 == 여름 ==> (6월부터 8월까지, 25)
//		가을 == 가을 ==> (9월부터 11월까지, 15)
//		겨울 == 겨울 ==> (12월부터 2월까지, 1)
		
	}

}

 


<스레드>

[1. 프로세스와 쓰레드]

* 프로그램 : 실행 가능한 파일(HDD)

* 프로세스 : 실행 중인 프로그램(메모리)

- 프로세스 : 실행 중인 프로그램, 자원과 쓰레드로 구성
- 쓰레드 : 프로세스 내에서 실제 작업을 수행
  모든 프로세스는 하나 이상의 쓰레드를 가지고 있다.

프로세스(공장), 쓰레드(일꾼)으로 비유하면 편하다.

* 싱글 쓰레드 프로세스 = 자원 + 메서드
* 멀티 쓰레드 프로세스 = 자원 + 쓰레드 + 쓰레드 + ... + 쓰레드

자바에서는 main() 메서드가 하나의 쓰레드이다. 결국은 싱글 쓰레드 프로그램을 짠 것.


멀티프로세스 vs. 멀티쓰레드

- “하나의 새로운 프로세스를 생성하는 것보다 하나의 새로운 쓰레드를 생성하는 것이 더 적은 비용이 든다.

ex) 2프로세스 1쓰레드(공장 2개에 일꾼 1명씩 배치)

ex) 1프로세스 2쓰레드(공장 1개에 일꾼 2명씩 배치)

=> 컴퓨터의 자원을 덜 잡아먹으면서 똑같은 효율을 내는 것이 멀티쓰레드.

많은 프로그램들이 멀티쓰레드로 작성되어 있다. 그러나, 멀티쓰레드 프로그래밍이 장점만 있는 것은 아니다.”

장점 자원을 보다 효율적으로 사용할 수 있다.
사용자에 대한 응답성(responseness)이 향상된다.
작업이 분리되어 코드가 간결해 진다.

여러 모로 좋다
단점 동기화(synchronization)에 주의해야 한다.
=> 각각의 스레드가 어떤 자원을 공통으로 사용한다. , 메모리, 변수 등을 공유. 스레드는 동시에 실행되는데, 엄밀히 말하면 동시 실행은 아니다. cpu는 하나인데 뭔가 명령을 처리할 때 처리되는 과정에서 어떤 지점을 멈춰보면 1가지 일 밖에 안 한다.
스레드 실행시 스레드 스케쥴러가 장점 스레드에게 먼저 권한을 줌. 1초의 시간 주면 cpu가 명령어를 읽어서 죽 실행. 1초가 지나면 작업한 지점까지 표시하고 단점 스레드가 실행. 굉장히 짧은 시간동안 여러 개의 명령을 수행하므로 동시에 수행되는 것처럼 보이는 것.

동기화는 예를 들어 장점 스레드가 a 변수값을 사용하다 어떤 값을 나중에 사용하기 위해 a 변수에 값을 저장하고 제어권이 단점으로 넘어감... 단점 스레드도 a 변수값을 공유해서 쓸 수 있으니 자기가 쓰던 데이터를 나중에 쓰기 위해 a 변수에 저장했다고 치자... 그리고 제어가 다시 장점 스레드로 넘어가면? 자기가 저장한 데이터가 아니므로 원하던 결과가 안나올 수 있다.
그래서 장점 스레드는 변수값을 저장하고 보호막을 쳐둠. 단점 스레드가 변수에 저장하려고 보니 저장 안 돼서 보호막이 해제될 때까지 대기. 장점 스레드가 먼저 다 실행되고 보호막 해제. 해제 이후 단점이 다시 변수 사용 가능. 이게 동기화 작업.

교착상태(dead-lock)가 발생하지 않도록 주의해야 한다.
동기화 때문에 일어나는 작업. 장점 스레드가 변수 a에 데이터 넣고 보호막. 프로그램 실행 중 단점 스레드로 제어가 넘어감. 보호막을 해제해야 쓸 수 있는데, 보호막 해제 안 되었는데 장점 스레드가 무엇인가의 오류로 작동을 멈출 수 있다. 단점 스레드 입장에서 a 변수를 써야 하는데, 보호막 해제 못한 상태로 계속 멈춰있는 것처럼 보이는 것.

각 쓰레드가 효율적으로 고르게 실행될 수 있게 해야 한다.
각각의 스케쥴러가 스레드를 관리. 어떤 스레드는 빨리 실행하고, 어떤 스레드는 천천히 실행되도록 관리해야 함.

프로그래밍할 때 고려해야 할 사항들이 많다

[2. 쓰레드의 구현과 실행]

1. Thread클래스를 상속

2. Runnable인터페이스를 구현

class MyThread extends Thread {
public void run() { 작업내용 } // Thread 클래스의 run()을 오버라이딩
}


class MyThread implements Runnable {
public void run() { 작업내용 } // Runnable 인터페이스의 추상메서드 run()을 구현
}

-----------------------------------------------

ThreadEx1_1 t1 = new ThreadEx1_1(); // 객체 생성 후 바로 실행 가능


Runnable r = new ThreadEx1_2();
Thread t2 = new Thread(r); // 생성자 Thread

[ThreadTest01.java]

package kr.or.ddit.basic;

public class ThreadTest01 {

	public static void main(String[] args) {
		
		// '*'문자를 200개 출력하는 기능과 '$'문자를 200개 출력하는 기능 구현하기
		
		// 싱글 쓰레드 프로그램
		for(int i=1; i<=200; i++) {
			System.out.print("*");
		}
		
		System.out.println();
		System.out.println();
		
		for(int j=1; j<=200; j++) {
			System.out.print("$");
		}
		
		System.out.println();
		System.out.println();
		
	}

}


[ThreadTest02.java]

package kr.or.ddit.basic;

public class ThreadTest02 {

	public static void main(String[] args) {
		
		// 멀티 쓰레드 프로그램
		
		// Thread를 사용하는 방법
		/*
		 * - 방법 1
		 * 		1) Thread 클래스를 상속한 class를 작성한 후 이 class의 인스턴스를 생성한다.
		 * 		2) 이 생성된 인스턴스의 start() 메서드를 호출해서 실행한다.
		 */
		
		MyThread1 th1 = new MyThread1(); // 인스턴스 생성
		th1.start(); // 쓰레드 실행
//		th1.run(); // 싱글 쓰레드처럼 작동한다.
		
		/*
		 * - 방법 2-1
		 * 		1) Runnable 인터페이스를 구현한 class를 작성한 후 이 class의 인스턴스를 생성한다.
		 * 		2) Thread 클래스의 인스턴스를 생성할 때 Runnable 인터페이스를 구현한 class의 
		 * 			인스턴스를 생성자의 인수값으로 넣어준다.
		 * 		3) 생성된 Thread 클래스의 인스턴스의 start()메서드를 호출해서 실행한다.
		 */
		
		MyRunner2 r2 = new MyRunner2(); // Runnable 인터페이스를 구현한 클래스의 인스턴스 생성
		Thread th2 = new Thread(r2); // Thread의 인스턴스를 생성할 때 Runnable 구현체의 인스턴스를 주입한다.
		th2.start(); // 쓰레드 실행
//		MyRunner2 eee = new MyRunner2();
		
		/*
		 * - 방법 2-2 ==> 익명 구현체를 이용하는 방법
		 */
		
		Runnable r3 = new Runnable() {
			@Override
			public void run() {
				// '@'문자를 200개 출력하는 기능
				for(int k=1; k<=200; k++) {
					System.out.print("@");
					
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO: handle exception
					}
				}
			}
		};
		// --------------------------------------------
		Thread th3 = new Thread(r3);
		th3.start();
		
		// start() 메서드가 하는 일은 call stack 영역에 main() 메서드 위에 호출되어 
		// run() 메서드를 개별적인 call stack 메모리로 잡고 사라짐.
		// 스레드가 작업할 공간을 만드는 역할을 한다고 보면 된다.
		// start() 메서드가 4번 있으면 스레드 작업 공간을 4개 잡고 사라지는 것임.
		
		System.out.println("main() 메서드 끝...");
		
	}

}

// 방법 1
// Thread 클래스를 상속한 class를 작성한다.
class MyThread1 extends Thread {
	
	// run() 메서드를 재정의 한다.
	@Override
	public void run() {
		
		// 이 run() 메서드 안에는 쓰레드에서 처리할 내용을 작성한다.
		
		// '*'문자를 200개 출력하는 기능
		for(int i=1; i<=200; i++) {
			System.out.print("*");
			
			try {
				// Thread.sleep(시간); ==> 주어진 시간 동안 작업을 잠시 멈춘다.
				// 				'시간'은 millisecond 단위를 사용한다. (즉, 1000은 1초를 의미한다.)
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO: handle exception
			}
		}
		
	}
	
}

// 방법 2-1
// Runnable 인터페이스를 구현한 class를 작성한다.
class MyRunner2 implements Runnable {
	
	// run() 메서드를 재정의 한다.
	@Override
	public void run() {
		
		// 이 run() 메서드 안에는 쓰레드에서 처리할 내용을 작성한다.
		
		// '$'문자를 200개 출력하는 기능
		for(int j=1; j<=200; j++) {
			System.out.print("$");
			
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO: handle exception
			}
		}
		
	}
	
}


[ThreadTest03.java]

package kr.or.ddit.basic;

public class ThreadTest03 {

	public static void main(String[] args) {
		
		// 쓰레드가 수행되는 시간을 체크해 보자...
		Thread th = new Thread(new MyRunner());
		
		// 1970년 1월 1일 0시 0분 0초(표준시간)로부터 현재까지 경과한 시간을 
		// millisecond(1/1000초) 단위로 반환한다. ( 예, 3분 경과 ==> 3 * 60 * 1000 )
		long startTime = System.currentTimeMillis();
		
		th.start();
		
		try {
			th.join(); // 현재 실행 위치에서 대상이 되는 쓰레드(지금은 변수 th)가 종료될 때까지 기다린다.
		} catch (InterruptedException e) {
			// TODO: handle exception
		}
		
		long endTime = System.currentTimeMillis();
		
		System.out.println("경과 시간 : " + (endTime - startTime) + "ms");
		
	}

}

class MyRunner implements Runnable {
	@Override
	public void run() {
		long sum = 0;
		for(long i=1L; i<=1_000_000_000L; i++) {
			sum += i;
		}
		System.out.println("합계 : " + sum);
	}
}


[ThreadTest04.java]

package kr.or.ddit.basic;

/*
 * 1 ~ 20억까지의 합계를 구하는 프로그램을
 * 하나의 쓰레드가 단독으로 처리할 때와 
 * 여러 개의 쓰레드가 협력해서 처리할 때의 경과 시간을 비교해 보자.
 */
public class ThreadTest04 {

	public static void main(String[] args) {
		
		// 단독으로 처리하는 쓰레드 객체 생성
		SumThread sm = new SumThread(1L, 2_000_000_000L);
		
		// 협력해서 처리할 쓰레드 객체 생성
		SumThread[] sumArr = new SumThread[] {
			new SumThread(			  1L,   500_000_000L),
			new SumThread(  500_000_001L, 1_000_000_000L),
			new SumThread(1_000_000_001L, 1_500_000_000L),
			new SumThread(1_500_000_001L, 2_000_000_000L)
		};
		
		// 단독으로 처리하기
		long startTime = System.currentTimeMillis();
		
		sm.start();
		try {
			sm.join();
		} catch (InterruptedException e) {
			// TODO: handle exception
		}
		
		long endTime = System.currentTimeMillis();
		
		System.out.println("단독으로 처리했을 때 경과 시간 : " + (endTime - startTime) + "ms");
		
		System.out.println();
		System.out.println("--------------------------------------------------------");
		
		// 협력해서 처리하기
		startTime = System.currentTimeMillis();
		
		for(SumThread th : sumArr) {
			th.start();
		}
		
		for(SumThread th : sumArr) {
			try {
				th.join();
			} catch (InterruptedException e) {
				// TODO: handle exception
			}
		}
		
		endTime = System.currentTimeMillis();
		
		System.out.println("협력해서 처리했을 때 경과 시간 : " + (endTime - startTime) + "ms");
		
	}

}

class SumThread extends Thread {
	private long start, end; // 합계를 구할 영역의 시작값과 종료값이 저장될 변수 선언.
	
	// 생성자
	public SumThread() {}
	public SumThread(long start, long end) {
		super();
		this.start = start;
		this.end = end;
	}

	@Override
	public void run() {
		long sum = 0L;
		for(long i=start; i<=end; i++) {
			sum += i;
		}
		System.out.println(start + "부터 " + end + "까지의 합계 : " + sum);
	}
}


[ThreadTest05.java]

package kr.or.ddit.basic;

import javax.swing.JOptionPane;

public class ThreadTest05 {

	public static void main(String[] args) {
		
		// 사용자로부터 데이터 입력 받기
		String str = JOptionPane.showInputDialog("아무거나 입력하세요...");
		System.out.println("입력한 값 : " + str);
		
		for(int i=10; i>=1; i--) {
			System.out.println(i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO: handle exception
			}
		}
		
	}

}

[ThreadTest06.java]

package kr.or.ddit.basic;

import javax.swing.JOptionPane;

public class ThreadTest06 {

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

}

// 데이터를 입력하는 쓰레드 클래스
class DataInput extends Thread {
	// 입력 완료 여부를 확인하기 위한 변수 선언 (쓰레드에서 공통으로 사용할 변수)
	public static boolean inputCheck = false;
	
	@Override
	public void run() {
		// 사용자로부터 데이터 입력 받기
		String str = JOptionPane.showInputDialog("아무거나 입력하세요...");
		inputCheck = true; // 입력이 완료되면 inputCheck 변수를 true로 변경한다.
		System.out.println("입력한 값 : " + str);
	}
}

// 카운트 다운을 진행하는 쓰레드 클래스
class CountDown extends Thread {
	@Override
	public void run() {
		for(int i=10; i>=1; i--) {
			// 입력이 완료되었는지 여부를 검사한다.
			// ==> 입력이 완료되면 쓰레드를 종료시킨다.
			if(DataInput.inputCheck == true) {
				return; // run() 메서드가 종료되면 쓰레드도 종료된다.
			}
			
			System.out.println(i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO: handle exception
			}
		}
		
		System.out.println("10초가 지났습니다. 프로그램을 종료합니다.");
		System.exit(0);
	}
}


[ThreadTest07.java]

package kr.or.ddit.basic;

import java.util.HashMap;

import javax.swing.JOptionPane;

/*
 * 컴퓨터와 가위 바위 보를 진행하는 프로그램을 작성하시오.
 * 
 * 컴퓨터의 가위 바위 보는 난수를 이용하여 정하고,
 * 사용자의 가위 바위 보는 showInputDialog() 메서드를 이용하여 입력 받는다.
 * 
 * 입력 시간은 5초로 제한하고 카운트 다운을 진행한다.
 * 5초 안에 입력이 없으면 게임에 진 것으로 처리하고 프로그램을 종료한다.
 * 
 * 5초 안에 입력이 있으면 승패를 구해서 결과를 출력한다.
 * 
 * 결과예시)
 * 1) 5초 안에 입력을 못했을 경우
 * 		-- 결 과 --
 * 시간 초과로 당신이 졌습니다...
 * 
 * 2) 5초 안에 입력했을 경우
 * 		-- 결 과 --
 * 		컴퓨터 : 가위
 * 		당  신 : 바위
 * 		결  과 : 당신이 이겼습니다.
 * 
 */
public class ThreadTest07 {

	public static void main(String[] args) {
		
		Thread th1 = new UserInput();
		Thread th2 = new CountDownMethod();
		
		th1.start();
		th2.start();
		
	}

}

class UserInput extends Thread {
	
	// 사용자로부터 데이터 입력 받기
	HashMap<Integer, String> dataMap = new HashMap<Integer, String>();
	
	@Override
	public void run() {
		// 컴퓨터에 랜덤값 넣기
		dataMap.put(1, "가위");
		dataMap.put(2, "바위");
		dataMap.put(3, "보");
		int randNum = (int)(Math.random()*3+1);
		String randAtk = dataMap.get(randNum);
		
		String str = JOptionPane.showInputDialog("가위 바위 보를 입력하세요.");
		
		// 사용자가 입력하면 입력한 값을 출력, 취소하면 null값 출력
		if(str == null) {
			System.out.println("게임을 종료합니다.");
			System.exit(0);
		}
		if(!(str.equals("가위") || str.equals("바위") || str.equals("보"))) {
			System.out.println("가위, 바위, 보만 입력하세요.");
			System.out.println("게임을 종료합니다.");
			System.exit(0);
		}
		
		CountDownMethod.inputCheck = true;
		
		String result = "";
		if(randAtk.equals(str)) {
			result = "비겼습니다.";
		}else if( (str.equals("가위") && randAtk.equals("보")) || (str.equals("바위") && randAtk.equals("가위")) || (str.equals("보") && randAtk.equals("바위")) ) {
			// 모두 사용자가 이긴 경우
			result = "당신이 이겼습니다.";
		}else {
			result = "당신이 졌습니다.";
		}
		
		// 결과 출력
		System.out.println("-- 결 과 --");
		System.out.println("컴퓨터 : " + randAtk);
		System.out.println("사용자 : " + str);
		System.out.println("결과 : " + result);
		System.exit(0);
	}
	
}

class CountDownMethod extends Thread {
	
	public static boolean inputCheck;
	
	@Override
	public void run() {
		
		System.out.println("카운트 시작!");
		
		for(int i=5; i>0; i--) {
			if(inputCheck == true) {
				return;
			}
			System.out.println(i + "초 남았습니다...");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO: handle exception
			}
		}
		
		System.out.println("-- 결 과 --");
		System.out.println("시간 초과로 당신이 졌습니다.");
		System.exit(0);
		
	}
	
}

'대덕인재개발원_자바기반 애플리케이션' 카테고리의 다른 글

230901_스레드 4  (0) 2023.09.01
230831_스레드 3  (0) 2023.08.31
230829_스레드 1  (0) 2023.08.29
230829_컬렉션 프레임워크 6  (1) 2023.08.29
230828_컬렉션 프레임워크 5  (1) 2023.08.28