[새싹] 현대IT&E 231023 기록 - JAVA 15~16장

최정윤·2023년 10월 23일
0

새싹

목록 보기
5/67
post-custom-banner

11장. 자바 제어자2

  • final을 붙이면 무조건 상수가 된다.

15장. 쓰레드

15.1 프로그램, 프로세스, 쓰레드

15.1.5 쓰레드는 정말 동시에 수행될까?

  • 쓰레드는 동시성과 병렬성을 갖는다.
  • 단일 쓰레드로 2개의 작업을 처리할 때 각 작업은 순차적으로 처리된다.
    • 즉 먼저 시작된 작업이 완전히 종료된 이후에 두 번재 작업이 실행되는 것이다.

15.2 쓰레드의 생성 및 실행

  • 쓰레드를 설정할 때 누가 먼저 나올지는 알 수 없다.
  • Thread.sleep을 활용하여 시간차를 준다.

CreateAndStartThread_M2C1.java

package thread;

class SMIFileRunnable implements Runnable {
	@Override
	public void run() {
		String[] strArray = {"하나","둘","셋","넷","다섯"};
		try {
			Thread.sleep(10);} catch (InterruptedException e) {}
		for (int i = 0; i < strArray.length; i++) {
			System.out.println(" - (자막 번호) " + strArray[i]);
			try {Thread.sleep(200);} catch (InterruptedException e) {}
		}
	}
}

public class CreateAndStartThread_M2C1 {
	public static void main(String[] args) {
		Runnable smiFileRunnable = new SMIFileRunnable();
		Thread thread = new Thread(smiFileRunnable);
		thread.start();
		
		int[] intArray = {1, 2, 3, 4, 5};
		
		for (int i = 0; i < intArray.length; i++) {
			System.out.print("(비디오 프레임) " + intArray[i]);
			try {Thread.sleep(200);} catch (InterruptedException e) {}
		}

	}

}

CreateAndStartThread_M2C2.java

package thread;

class MIFileRunnable implements Runnable {
	@Override
	public void run() {
		String[] strArray = {"하나", "둘", "셋", "넷", "다섯"};
		try {Thread.sleep(10);} catch (interruptedException e) {}
		for (int i = 0; i < strArray.length; i++) {
			System.out.println(" - (자막 번호) " + strArray[i]);
			try {Thread.sleep(200);} catch (InterruptedException e) {}
		}
	}
}

class VideoFileRunnable implements Runnable {
	@Override
	public void run() {
		int[] intArray = {1,2,3,4,5};
		for (int i = 0; i < intArray.length; i++) {
			System.out.print("(비디오 프레임)" + intArray[i]);
			try {Thread.sleep(200);} catch (InterruptedException e) {}
		}
	}
}

public class CreateAndStartThread_M2C2 {
	public static void main(String[] args) {
		Runnable smiFileRunnable = new SMIFileRunnable();
		Thread thread1 = new Thread(smiFileRunnable);
		thread1.start();
		Runnable videoFileRunnable = new VideoFileRunnable();
		Thread thread2 = new Thread(videoFileRunnable);
		thread2.start();
	}

}

15.3 쓰레드의 속성

15.3.3 쓰레드의 이름 지정 및 가져오기

  • 여러 개의 쓰레드을 생성하고 실행하다 보면 각각의 쓰레드 구분할 필요성이 생김
  • 쓰레드를 구분하는 가장 손쉬운 방법은 쓰레드마다 이름을 부여하는 것
    • 직접 이름 부여를 위해 Thread 클래스의 인스턴스 메서드인 setName() 메서드를 사용
    • 쓰레드의 이름을 가져올 때는 인스턴스 메서드 getName()을 사용
      ThreadProperties_1.java
package thread;

import javax.swing.plaf.synth.SynthOptionPaneUI;

public class ThreadProperties_1 {
	public static void main(String[] args) {
		Thread curThread = Thread.currentThread();
		System.out.println("현재 쓰레드의 이름 = " + curThread.getName());
		System.out.println("동작하는 쓰레드의 개수 = " + Thread.activeCount());
		
		for (int i = 0; i < 3; i++) {
			Thread thread = Thread();
			System.out.println(thread.getName());
			thread.start();
		}
		
		for (int i = 0; i < 3; i++) {
			Thread thread = new Thread();
			thread.setName(i + "번째 쓰레드");
			System.out.println(thread.getName());
			thread.start();
		}
		
		for(int i = 0; i < 3; i++) {
			Thread thread = new Thread();
			System.out.println(thread.getName());
			thread.start();
		}
		
		System.out.println("동작하는 쓰레스의 개수 = " + Thread.activeCount());
	}

}

15.3.4 쓰레드의 우선순위

  • 모든 쓰레드는 1 ~ 10 사이의 우선순위를 갖고 있다.
    • 1이 가장 낮은 순위 / 10이 가장 높은 순위 값
    • 우선순위 지정하지 않을 시 기본값은 5이다.
  • 우선순위가 높으면 상재거으로 더 많은 시간을 할당받는다.

ThreadProperties_2.java

package thread;

class MyThread extends Thread {
	@Override
	public void run() {
		for(long i = 0; i < 2_000_000_000_000_000_000L; i++) {}	// 시간 지연용
		
		System.out.println(getName() + " 우선순위: " + getPriority());
	}
}

public class ThreadProperties_2 {
	public static void main(String[] args) {
		// CPU 코어 수
		System.out.println("코어 수: " + Runtime.getRuntime().availableProcessors());
		
		// 우선순위 자동 지정
		for(int i = 0; i < 3; i++) {
			Thread thread = new MyThread();
			thread.start();
		}
		
		try {Thread.sleep(1000);} catch (InterruptedException e) {}
		// 우선순위 직접 지정
		for(int i = 0; i < 10; i++) {
			Thread thread = new MyThread();
			thread.setName(i + "번째 쓰레드");
			if(i == 9) thread.setPriority(10);
			thread.start();
		}
	}

}

15.3.5 쓰레드의 데몬 설정

  • 쓰레드 객체를 실행하면 다른 쓰레드의 종료 여부와 관계없이 자신의 쓰레드가 종료될 때까지 계속 실행된다.
    • 실행된 쓰레드가 무한 반복 쓰레드라면 종료 조건 필요
  • 데몬 쓰레드: 일반 쓰레드가 모두 종료되면 함께 종료되는 쓰레드
  • 쓰레드의 데몬 설정은 Thread 클래스의 인스턴스 메서드인 setDeamon() 메서드를 사용하며, 기본값은 false이다.
    • void setDeamon(boolean on)
  • 생성한 객체의 데몬 설정 여부는 Thread 클래스의 인스턴스 메서드인 isDaemon() 메서드를 이용해 언제든지 확인할 수 있다.
    • boolean isDaemon()

ThreadProperties_3_1.java

package thread;

class MyThread extends Thread {
	@Override
	public void run() {
		System.out.println(getName() + ": " + (isDaemon()? "데몬 쓰레":"일반 쓰레"));
		for(int i = 0; i < 6; i++) {
			System.out.println(getName() + ": " + i + "초");
			try {Thread.sleep(1000);} catch (InterruptedException e) {}
		}
	}
}

public class ThreadProperties_3_1 {
	public static void main(String[] args) {
		// 일반 쓰레드
		Thread thread1 = new MyThread();
		thread1.setDaemon(false);
		thread1.setName("thread1");
		thread1.start();
		
		// 3.5초 후 main 쓰레드 종료
		try {Thread.sleep(3500);} catch (InterruptedException e) {}
		System.out.println("main Thread 종");

	}

}

ThreadProperties_3_2.java

package thread;

class MyThread_2 extends Thread {
	@Override
	public void run() {
		System.out.println(getName() + ": " + (isDaemon()? "데몬 쓰레드":"일반 쓰레드"));
		for(int i = 0; i < 6; i++) {
			System.out.println(getName() + ": " + i + "초");
			try {Thread.sleep(1000);} catch (InterruptedException e) {}
		}
	}
}

public class ThreadProperties_3_2 {
	public static void main(String[] args) {
		
		// 데몬 쓰레드
		Thread thread2 = new MyThread_2();
		thread2.setDaemon(true);
		thread2.setName("thread2");
		thread2.start();
		
		try {Thread.sleep(3500);} catch (InterruptedException e) {}
		System.out.println("main Thread 종료");
	}
}

15.4 쓰레드의 동기화

15.4.1 동기화의 개념

  • 동기화는 하나의 작업이 완전히 완료된 후 다른 작업을 수행하는 것이다.
  • 비동기는 하나의 작업 명령 이후 완료 여부와 상관없이 바로 다른 작업 명령을 수행하는 것이다.

15.4.2 동기화의 필요성

  • 멀티 쓰레드를 사용할 때 동기화가 필요한 이유?
  • 한 쓰레드가 객체를 모두 사용해야 다음 쓰레드가 사용할 수 있도록 설정하는 것을 '동기화'라고 한다.

15.4.3 동기화 방법

메서드 동기화

  • 메서드를 동기화할 때 동기화하고자 하는 메서드의 리턴 타입 앞에 synchronized 키워드만 넣으면 된다.
접근 지정자 synchronized 리턴 타입 메서드명(입력매개변수) {
	// 동기화가 필요한 코드
}

SynchronizedMethod.java

package thread;

// 공유 객체
class MyData {
	int data = 3;
	public synchronized void plusData() {
		int mydata = data;
		try {Thread.sleep(2000);} catch (InterruptedException e) {}
		data = mydata + 1;
	}
}

// 공유 객체를 사용하는쓰레드
class PlusThread extends Thread {
	MyData myData;
	public PlusThread(MyData myData) {
		this.myData = myData;
	}
	@Override
	public void run() {
		myData.plusData();
		System.out.println(getName() + "실행결과: " + myData.data);
	}
}

public class SynchronizedMethod {
	public static void main(String[] args) {
		// 공유 객체 생성
		MyData myData = new MyData();
		
		// plusThread1
		Thread plusThread1 = new PlusThread(myData);
		plusThread1.setName("plusThread1");
		plusThread1.start();
		
		try {Thread.sleep(1000);} catch (InterruptedException e) {}
		
		// plusThread 2
		Thread plusThread2 = new PlusThread(myData);
		plusThread2.setName("plusThread2");
		plusThread2.start();
	}
}

블록 동기화

  • 멀티 쓰레드를 사용하는 프로그램이라도 동기화 영역에서는 하나의 쓰레드만 실행할 수 있기 때문에 성능 면에서는 많은 손해를 보게 된다.
    • 동기화 영역은 꼭 필요한 부분에 한정해 적용하는 것이 좋다.

15.4.4 동기화의 원리

  • 모든 객체는 자신만의 열쇠를 하나씩 갖고 있다.
  • 블록 동기화코드일 때를 살펴보면 블록이 this 객체가 갖고 있는 열쇠로 잠긴다.
  • 블록 동기화의 소괄호 안에는 어떤 객체가 와도 무방하다.
  • 메소드를 동기화하는 경우에는 this 객체의 열쇠만을 사용한다.

KeyObject_2.java

package thread;

class MyData_2{
	synchronized void abc() {
		for(int i = 0; i < 3; i++) {
			System.out.println(i + "sec");
			try {Thread.sleep(1000);} catch (InterruptedException e) {}
		}
	}
	synchronized void bcd() {
		for (int i = 0; i < 3; i++) {
			System.out.println(i + "초");
			try {Thread.sleep(1000);} catch (InterruptedException e) {}
		}
	}
	void cde() {
		synchronized(new Object()) {
			for(int i = 0; i < 3; i++) {
				System.out.println(i + "번째");
				try {Thread.sleep(1000);} catch (InterruptedException e) {}
			}
		}
	}
}

public class KeyObject_2 {
	public static void main(String[] args) {
		// 공유 객체
		MyData_2 myData = new MyData_2();
		// 3개의 쓰레드가 각각의 메서드 호출
		new Thread() {
			public void run() {
				myData.abc();
			};
		}.start();
		new Thread() {
			public void run() {
				myData.bcd();
			};
		}.start();
		new Thread() {
			public void run() {
				myData.cde();
			};
		}.start();

	}

}

15.5 쓰레드의 상태

  • 쓰레드는 객체가 생성, 실행, 종료되기까지 다양한 상태를 가진다.
  • 쓰레드의 상태는 Thread.State 타입으로 정의돼 있으며, Thread의 인스턴스 메서드인 getState()로 가져올 수 있으며 Thread.State타입에 저장된 문자열 상숫값 중 하나로 리턴한다.
    • Thread.State getState()

15.5.1 쓰레드의 6가지 상태

15.5.2 NEW, RUNNABLE, TERMINATED

  • start() 메서드로 실행하면 RUNNABLE 상태가 된다.
    YieldInRunnableState.java
package thread;

class MyThread_3 extends Thread {
	boolean yieldFlag;
	@Override
	public void run() {
		while(true) {
			if(yieldFlag) {
				Thread.yield();
			} else {
				System.out.println(getName() + " 실행");
				for(long i = 0; i < 100000000L; i++) {} // 시간 지연
			}
		}
	}
}

public class YieldInRunnableState {
	public static void main(String[] args) {
		MyThread_3 thread1 = new MyThread_3();
		thread1.setName("thread1");
		thread1.yieldFlag = false;
		thread1.setDaemon(true);
		thread1.start();
		
		MyThread_3 thread2 = new MyThread_3();
		thread2.setName("thread2");
		thread2.yieldFlag = true;
		thread2.setDaemon(true);
		thread2.start();
		
		// 6초 지연(1초마다 한 번씩 양보)
		for(int i = 0; i < 6; i++) {
			try {Thread.sleep(1000);} catch (InterruptedException e) {}
			thread1.yieldFlag = !thread1.yieldFlag;
			thread2.yieldFlag = !thread2.yieldFlag;
		}
	}

}

15.5.3 TIMED_WAITING

  • RUNNABLE 상태에서 일시정지 상태로 전환하는
  • RUNNABLE 상태에서 TIMED_WAITING 상태가 됐을 때 Thread의 정적 메서드인 sleep를 호출하거나 인스턴스 메서드인 join가 호출됐을 때다.
    TimeWaiting_Sleep.java
package thread;

class MyThread extends Thread {
	@Override
	public void run() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			System.out.println(" -- sleep() 진행 중 interrupt() 발생");
			for(long i = 0; i < 1000000000L; i++) {} // 시간 지
		}
	}
}

public class TimeWaiting_Sleep {
	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		myThread.start();
		
		try {Thread.sleep(100);} catch (InterruptedException e) {}
		System.out.println("MyThread State = " + myThread.getState());
		// TIMED_WAITING
		myThread.interrupt();
		try {Thread.sleep(100);} catch (InterruptedException e) {}
		System.out.println("MyThread State = " + myThread.getState());

	}

}

15.5.4 BLOCKED

  • BLOCKED는 동기화 메서드 또는 동기화 블록을 실행하고자 할 때 이미 다른 쓰레드가 해당 영역을 실행하고 있는 경우 발생한다.
  • 동기화 영역이 잠겨 있을 때는 이미 실행하고 있는 쓰레드가 실행을 완료하고, 해당 동기화 영역의 열쇠를 반납할 때까지 기다려야 하는데, 이것이 바로 BLOCKED 상태다.

BlockState.java

package thread;

class MyBlockTest {
	// 공유 객체
	MyClass mc = new MyClass();
	// 3개의 쓰레드 필드 생성
	Thread t1 = new Thread("thread1" ) {
		public void run() {
			mc.syncMethod();
		};
	};
	Thread t2 = new Thread("thread3") {
		public void run() {
			mc.syncMethod();
		};
	};
	Thread t3 = new Thread("thread3") {
		public void run() {
			mc.syncMethod();
		};
	};
	
	void starAll() {
		t1.start();
		t2.start();
		t3.start();
	}
	
	class MyClass {
		synchronized void syncMethod() {
			try {Thread.sleep(100);} catch (InterruptedException e) {}
			System.out.println("====" + Thread.currentThread().getName() + "====");
			System.out.println("thread1->" + t1.getState());
			System.out.println("thread2->" + t2.getState());
			System.out.println("thread3->" + t3.getState());
			for(long i = 0; i < 1000000000L; i++) {}	// 시간 지연
		}
	}
}

public class BlockState {
	public static void main(String[] args) {
		MyBlockTest mbt = new MyBlockTest();
		mbt.starAll();
	}

}

15.5.5 WAITING

  • 일시정지 상태는 WAITING이다.
  • 일시정지하는 시간의 지정 없이 쓰레드 객체.join() 메서드를 호출하면 조인된 쓰레드 객체의 실행이 완료될 때까지 이를 호출한 쓰레드는 WAITING 상태가 된다.

16장. 제네릭

16.1 제네릭 클래스와 제네릭 인터페이스

  • 자바에서 다양한 종류의 클래스와 인터페이스를 내부 멤버에서 활용하기 위해서 제공되는 클래스나 인터페이스의 다양성 만큼이나 많은 가짓수의 클래스를 생성해야 한다.
  • 입력매개변수 타입의 수만큼 오버로딩을 수행해야 하는데 이런 비효율성을 한 번에 해결하는데 필요한 문법 요소가 제네릭이다.

16.2 제네릭의 문법

16.2.1 제네릭 클래스와 제네릭 인터페이스 정의하기

  • 제네릭 클래스와 제네릭 인터페이스를 정의하는 방법은 클래스명 다음에 <제네릭 타입 변수명(들)>을 삽입하는 것이다.

16.2.2 제네릭 클래스의 객체 생성

TwoGenericArguments.java

package generic;

class KeyValue<K, V> {
	private K key;
	private V value;
	public K getKey() {
		return key;
	}
	public void setKey(K key) {
		this.key = key;
	}
	public V getValue() {
		return value;
	}
	public void setValue(V value) {
		this.value = value;
	}
}

public class TwoGenericArguments {
	public static void main(String[] args) {
		KeyValue<String, Integer> kv1 = new KeyValue();
		kv1.setKey("사과");
		kv1.setValue(1000);
		String key1 = kv1.getKey();
		int value1 = kv1.getValue();
		System.out.println("key: " + key1 + " value: " + value1);
		
		KeyValue<Integer, String> kv2 = new KeyValue<>();
		kv2.setKey(404);
		kv2.setValue("Not Found(요청한 페이지를 찾을 수 없습니다.)");
		int key2 = kv2.getKey();
		String value2 = kv2.getValue();
		System.out.println("key: " + key2 + " value: " + value2);
		
		KeyValue<String, Void> kv3 = new KeyValue<>();
		kv3.setKey("키 값만 사용");
		String key3 = kv3.getKey();
		System.out.println("key: " + key3);

	}

}

16.3 제네릭 메서드

16.3.1 제네릭 메서드의 정의와 호출

  • 클래스 전체를 제네릭으로 선언하는 대신, 일반 클래스 내부의 특정 메서드만 제네릭으로 선언할 수도 있는데 이를 제네릭 메서드라고 한다.
  • 제네릭 클래스가 객체를 생성하는 시점에 실제 타입을 지정하는 것과 달리 제네릭 메서드는 호출되는 시점에 실제 제네릭 타입을 지정한다.

GenericMethod.java

package generic;

class GenericMethods{
	public <T> T method1(T t) {
		return t;
	}
	public <T> boolean method2(T t1, T t2) {
		return t1.equals(t2);
	}
	public <K,V> void method3(K k, V v) {
		System.out.println(k + ":" + v);
	}
}

public class GenericMethod {
	public static void main(String[] args) {
		GenericMethods gm = new GenericMethods();
		
		String str1 = gm.<String>method1("안녕");
		String str2 = gm.method1("안녕");
		System.out.println(str1);
		System.out.println(str2);
		
		boolean bool1 = gm.<Double>method2(2.5, 2.5);
		boolean bool2 = gm.method2(2.5, 2.5);
		System.out.println(bool1);
		System.out.println(bool2);
		
		gm.<String, Integer>method3("국어", 80);
		gm.method3("국어", 80);
	}
}

16.4 제네릭 타입 범위 제한

16.4.2 제네릭 타입 범위 제한의 종류와 타입 범위 제한 방법

제네릭 클래스의 타입 제한

BoundedTypeOfGenericClass.java

package generic;

class A{}
class B extends A{}
class C extends B{}

class D <T extends B> {
	private T t;
	public T get() {
		return t;
	}
	public void set(T t) {
		this.t = t;
	}
}

public class BoundedTypeOfGenericClass {
	public static void main(String[] args) {
		D<B> d2 = new D<>();
		D<C> d3 = new D<>();
		D d4 = new D();
		
		d2.set(new B());
		d2.set(new C());
		
		d3.set(new C());
		
		d4.set(new B());
		d4.set(new C());

	}

}

제네릭 메서드의 타입 제한

BoundedTypeOfGenericMethod.java

package generic;

class AA {
	public <T extends Number> void method1(T t) {
		System.out.println(t.intValue());
	}
}

interface MyInterface {
	public abstract void print();
}

class BB {
	public <T extends MyInterface> void method1(T t) {
		t.print();
	}
}

public class BoundedTypeOfGenericMethod {
	public static void main(String[] args) {
		AA a = new AA();
		a.method1(5.8);
		
		BB b = new BB();
		b.method1(new MyInterface() {
			@Override
			public void print() {
				System.out.println("print() 구현");
			}
		});
	}
}

16.5 제네릭의 상속

16.5.1 제네릭 클래스의 상속

  • 부모 클래스가 제네릭 클래스일 때 이를 상속한 자식 클래스도 제네릭 클래스가 된다.
  • 자식 클래스는 제네릭 타입 변수를 추가해 정의할 수도 있다.
    • 자식클래스의 제네릭 타입 변수의 개수는 항상 부모보다 같거나 많다.

16.5.2 제네릭 메서드의 상속

  • 제네릭 메서드를 포함한 일반 클래스를 상속해 자식 클래스를 생성할 때도 부모 클래스 내의 제네릭 메서드는 그대로 자식 클래스로 상속된다.
profile
개발 기록장
post-custom-banner

0개의 댓글