-
3-6 자바의 기초(ch12,13)카테고리 없음 2022. 11. 6. 19:21
chapter12
#12-1
지네릭스(Generics)란?
-컴파일시 타입을 체크해 주는 기능(compile-time type check)
// Tv객체만 저장할 수 있는 ArrayList를 생성 ArrayList<Tv> tvList = new ArrayList<Tv>(); tvList.add(new Tv() ); // OK tvList.add(new Audio() ); // 컴파일 에러. Tv외에 다른 타입은 저장 불가
-객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여줌
*지네릭스의 장점
1. 타입 안정성을 제공한다.
2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해짐.
예외의 상속계속도 이후의 설명은 영상강의 참조(16:20)
#12-2,3
타입 변수
-클래스를 작성할 때, Object타입 대신 타입 변수(E)를 선언해서 사용.
ex)
public class ArrayList extends AbstractList { // 일부 생략 private transient Object[] elementData; public boolean add(Object o) { /* 내용 생략 */ } public Object get(int index) { /* 내용 생략 */ } . . . }
를
public class ArrayList<E> extends AbstractList<E> { public transient E[] elementData; public boolean add(E o) { /* 내용 생략 */ } public E get (int index) { /* 내용 생략 */ } . . . }
으로 바꿔줌!
타입 변수에 대입하기
-객체를 생성시, 타입 변수(E) 대신 실제 타입(Tv)을 지정(대입)
// 타입 변수 E 대신에 실제 타입 Tv를 대입
ArrayList<Tv> tvList = new ArrayList<Tv>();
ex)
public class ArrayList<E> extends AbstractList<E> { private transient E[ ] elementData; public boolean add(E o) { /* 내용 생략 */ } public E get (int index) { /* 내용 생략 */ } . . . }
이게
public class ArrayList extends AbstractList { // 일부 생략 public transient Tv[ ] elementData; public boolean add(Tv o) { /* 내용 생략 */ } public Tv get (int index) { /* 내용 생략 */ } . . . }
-타입 변수 대신 실제 타입이 지정되면, 형변환 생략가능
ArrayList tvList = new ArrayList(); tvList.add(new Tv() ); Tv t = (Tv)tvList.get(0);
↑
↓
ArrayList<Tv> tvList = new ArrayList <Tv>(); tvList.add(new Tv() ); Tv t = tvList.get(0); // 형변환 불필요
#12-4~6
지네릭스 용어
Box<T> : 지네릭 클래스, 'T의 Box' 또는 'T Box'라고 읽는다. T : 타입 변수 또는 타입 매개변수. (T는 타입 문자) Box : 원시 타입(raw type)
코드예시에 각각 용어설명은 강의영상 참고
지네릭 타입과 다형성
-참조 변수와 생성자의 대입된 타입은 일치해야 한다.
ArrayList<Tv> list = new ArrayList<Tv>(); // OK. 일치 ArrayList<Product> list = new ArrayList<Tv>(); // 에러. 불일치
-지네릭 클래스간의 다형성은 성립.(여전히 대입된 타입은 일치해야)
List<Tv> list = new ArrayList<Tv>(); // OK. 다형성. ArrayList가 List를 구현 List<Tv> list = new LinkedList<Tv>(); // OK. 다형성. LinkedList가 List를 구현
-매개변수의 다형성도 성립.
ArrayList<Product> list = newArrayList<Product>(); list.add(new Product() ); list.add(new Tv() ); // OK. list.add(new Audio() ); // OK. boolean add(E e) {. . .} => boolean add(Product e) {. . .}
#12-7,8
Iterator<E>
-클래스를 작성할 때, Object타입 대신 T와 같은 타입 변수를 사용
예시들은 영상강의 참조 (00:00~)
HashMap<K , V>
-여러 개의 타입 변수가 필요한 경우, 콤마(,)를 구분자로 선언
HashMap<String, Student> map = new HashMap<String, Student>(); //생성 map.put("자바왕", new Student("자바왕", 1,1,100,100,100) ); // 데이터 저장
#12-9~11
제한된 지네릭 클래스
-extends로 대입할 수 있는 타입을 제한
class FruitBox<T extends Fruit> { ArrayList<T> list = new ArrayList<T>(); . . . } FruitBox<Apple> appleBox = new FruitBox<Apple>(); // OK FruitBox<Toy> toyBox = new FruitBox<Toy>(); // 에러. Toy는 Fruit의 자손이 아님
-인터페이스인 경우에도 extends를 사용
interface Eatable {} class FruitBox<T extends Eatable> {. . .}
지네릭스의 제약
-타입 변수에 대입은 인스턴스 별로 다르게 가능
Box<Apple> appleBox = new Box<Apple>(); // OK. apple 객체만 저장가능 Box<Grape> grapeBox = new Box<Grape>(); // OK. Grape 객체만 저장가능
-static멤버에 타입 변수 사용 불가
class Box<T> { static T item; // 에러. static int compare(T t1, T t2) {. . .} // 에러. }
-배열 생성할 때 타입 변수 사용불가. 타입 변수로 배열 선언은 가능
class Box<T> { T[ ] itemArr; // OK. T타입의 배열을 위한 참조변수 . . . T[ ] toArray() { T[ ] tmpArr = new T[itemArr.length]; // 에러. 지네릭 배열 생성불가. . . . } }
#12-12~14
와일드 카드<?>
-하나의 참조 변수로 대입된 타입이 다른 객체를 참조 가능
ArrList<? extends Product> list = new ArrayList<Tv>(); // OK ArrList<? extends Product> list = new ArrayList<Audio>(); // OK ArrayList<Product> list = new ArrayList<Tv>(); // 에러. 대입된 타입 불일치
<? extends T> : 와일드 카드의 상한 제한. T와 그 자손들만 가능 <? super T> : 와일드 카드의 하한 제한. T와 그 조상들만 가능 <?> : 제한 없음. 모든 타입이 가능. <? extends Object>와 동일
-메서드의 매개변수에 와일드 카드를 사용
static Juice makeJuice(FruitBox<? extends Fruit> box) { String tmp = ""; for(Fruit f : box.getList()) tmp += f + " "; return new Juice(tmp); } 이 중, 위 <? extends Fruit> box에 System.out.print(Juicer.makeJuice(new FruitBox<Fruit>() ) ); System.out.print(Juicer.makeJuice(new FruitBox<Apple>() ) ); 둘 다 들어올 수 있음.
(단, '? extends'가 없으면, 즉 와일드 카드를 안쓰면Fruit 밖에 못들어옴(일치하는 것만 들어올 수 있으니까))
지네릭 메서드
-지네릭 타입이 선언된 메서드(타입 변수는 메서드 내에서만 유효)
static <T> void sort(List<T> list, Comparator<? super T> c)
-클래스의 타입 매개변수 <T>와 메서드의 타입 매개변수 <T>는 별개
class FruitBox<T> { . . . static<T> void sort(List<T> list, Comparator<? super T> c) { . . . } }
-메서드를 호출할 때마다 타입을 대입해야(대부분 생략 가능)
(강의영상 참고 18:00)
-메서드를 호출할 때 타입을 생략하지 않을 때는 클래스 이름 생략 불가.
System.out.println(<Fruit>makeJuice(fruitBox)); // 에러. 클래스 이름 생략불가 System.out.println(this.<Fruit>makeJuice(fruitBox)); // OK System.out.println(Juicer.<Fruit>makeJuice(fruitBox)); // OK
#12-15,16
지네릭 타입의 형변환
-지네릭 타입과 원시 타입 간의 형변환은 바람직 하지 않다.(경고 발생)
Box<Object object = null; Box box = (Box)objBox; // OK. 지네릭 타입 -> 원시 타입. 경고 발생 objBox = (Box<Object>) box; // OK. 원시 타입 -> 지네릭 타입. 경고 발생 objBox = (Box<Object>)strBox; // 에러. Box<String> -> Box<Object> strBox = (Box<String>)objBox; // 에러. Box<Object> -> Box<String>
-와일드 카드가 사용된 지네릭 타입으로는 형변환 가능
Box<Object> objBox = (Box<Object>)new Box<String>(); // 에러. 형변환 불가능 Box<? extends Object> wBox = (Box<? extends Object>)new Box<String>(); // OK Box<? extends Object> wBox = new Box<String>(); // 위 문장과 동일 // 매개변수로 FruitBox<Fruit>, Fruit<Apple>, FruitBox<Grape> 등이 가능 static Juice makeJuice(FruitBox<? extends Fruit> box) {. . .} FruitBox<? extends Fruit> box = new FruitBox<Fruit>(); // OK FruitBox<? extends Fruit> box = new FruitBox<Apple>(); // OK
지네릭 타입의 제거
-컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다.
1)지네릭 타입의 경계(bound)를 제거
class Box<T extends Fruit> { void add(T t) { . . . } }
↓
class Box { void add(Fruit t) { . . . } }
2)지네릭 타입 제거 후에 타입이 불일치하면, 형변환을 추가
T get(int i) { return list.get(i); }
↓
Fruit get(int i) { return (Fruit)list.get(i); }
3)와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가
static Juice make Juice(FruitBox<? extends Fruit> box) { String tmp = ""; for(Fruit f : box.getList() ) tmp += f + " "; return new Juice(tmp); }
#12-17~20
열거형(enum)
-관련된 상수들을 같이 묶어 놓는 것. Java는 타입에 안전한 열거형을 제공
class Card{ static final int CLOVER = 0; static final int HEART = 1; static final int DIAMOND = 2; static final int SPADE = 3; static final int TWO = 0; static final int THREE = 1; static final int FOUR = 2; final int kind; final int num; }
을
class Card { enum Kind { CLOVER, HEART, DIAMOND, SPADE } // 열거형 Kind를 정의 enum Value { TWO, THREE, FOUR } // 열거형 Value를 정의 final Kind kind; // 타입이 int가 아닌 Kind임에 유의하자. final Value value; }
이렇게 줄일 수 있다.
하지만
if(Card.Kind CLOVER == Card.Value.TWO) { // 컴파일 에러. 타입이 달라서 비교 불가}
열거형의 정의와 사용
-열거형을 정의하는 방법
enum 열거형이름 { 상수명1, 상수명2, . . . }
-열거형 타입의 변수를 선언하고 사용하는 방법
class Unit { int x, y; // 유닛의 위치 Direction dir; // 열거형 인스턴스 변수를 선언 void init() { dir = Direction.EAST; // 유닛의 방향을 EAST로 초기화 } }
-열거형 상수의 비교에 == 와 compareTo() 사용가능
ex)
if(dir==Direction.EAST) { x++; } else if (dir > Direction.WEST) { // 에러. 열거형 상수에 비교연산자 사용불가. . . . } else if (dir.compareTo(Direction.WEST) > 0) { // compareTo()는 가능 }
열거형의 조상 - java.lang.Enum
-모든 열거형은 Enum의 자손이며, 아래의 메서드를 상속받는다.
(영상강의 참조 07:30)
#12-21,22
열거형에 멤버 추가하기
-불연속적인 열거형 상수의 경우, 원하는 값을 괄호()안에 적는다.
enum Direction { EAST(1), SOUTH(5), WEST(-1), NORTH(10) }
-괄호()를 사용하려면, 인스턴스 변수와 생성자를 새로 추가해 줘야 한다.
ex)
enum Direction { EAST(1), SOUTH(5), WEST(-1), NORTH(10) ; // 끝에 ' ; ' 를 추가해야 한다. private final int value; // 정수를 저장할 필드(인스턴스 변수)를 추가 Direction(int value) { this. value = value; } // 생성자를 추가 public int getValue() { return value; } }
-열거형의 생성자는 묵시적으로 private이므로, 외부에서 객체생성 불가
ex)
Direction d = new Direction(1); // 에러. 열거형의 생성자는 외부에서 호출불가
#12-23~24
애너테이션이란?
-주석처럼 프로그래밍 언어에 영향을 미치지 않으며, 유용한 정보를 제공
(예시와 설명은 영상강의 참조. 설명이 길다)
-애너테이션의 사용예
@Test // 이 메서드가 테스트 대상임을 테스트 프로그램에게 알린다. public void method() { . . . }
표준 애너테이션
(표는 영상강의 참조 12:20 )
#12-25~28
@Override
-오버라이딩을 올바르게 했는지 컴파일러가 체크하게 한다.
-오버라이딩 할 때 메서드이름을 잘못적는 실수를 하는 경우가 많다.
ex)
Class Parent { void parentMethod() { } } class Child extends Parent{ void parentmethod() { } // 오버라이딩하려 했으나 실수로 이름을 잘못적음(대문자 M이 아닌 소문자m) }
-오버라이딩할 때는 메서드 선언부 앞에 @Overrride를 붙이자.
ex)
class Child extends Parent{ void parentmethod() { } }
↓
class Child extends Parent{ @Override void praentmethod() { } }
=> 이후 컴파일 결과에서 뜸
Deprecated
-앞으로 사용하지 않을 것을 권장하는 필드나 메서드에 붙인다.
-@Deprecated의 사용 예, Date클래스의 getDate()
ex)
@Deprecated public int getDate() { return normalize().getDayOfMonth(); }
-@Deprecated가 붙은 대상이 사용된 코드를 컴파일하면 나타나는 메시지
Note: AnnotationEx2.java uses or overrides a deprecated API. Note: Recompile with -Xlint:deprecation for details.
FunctionalInterface
-함수형 인터페이스에 붙이면, 컴파일러가 올바르게 작성했는지 체크
함수형 인터페이스에는 하나의 추상메서드만 가져야 한다는 제약이 있음.
ex)
@FunctionalInterface public interface Runnable { public abstract void run() ; // 추상 메서드 }
@SuppressWarnings
-컴파일러의 경고메시지가 나타나지 않게 억제한다.
-괄호()안에 억제하고자 하는 경고의 종류를 문자열로 지정
ex)
@SuppressWarnings ("unchecked") // 지네릭스와 관련된 경고를 억제 ArrayList list = new ArrayList(); // 지네릭 타입을 지정하지 않았음. list.add(obj); // 여기서 경고가 발생
-둘 이상의 경고를 동시에 억제하려면 다음과 같이 한다.
@SuppressWarnings( {"deprecation", "unchecked", "varargs"} )
-'-Xlint'옵션으로 컴파일하면, 경고메시지를 확인할 수 있다.
괄호[]안이 경고의 종류. 아래의 경우 rawtypes(강의영상 16:00 참고)
#12-29~33
메타 애너테이션
-메타 애너테이션은 '애너테이션을 위한 애너테이션'
(표는 영상강의 참조)
-메타 애너테이션은 java.lang.annotation패키지에 포함
아래부터 예시 설명은 영상강의 참조
@Target
-애너테이션을 정의할 때, 적용대상 지정에 사용
@Retention
-애너테이션이 유지(retention)되는 기간을 지정하는데 사용
-컴파일러에 의해 사용되는 애너테이션의 유지 정책은 SOURCE이다.
-실행시에 사용 가능한 애너테이션의 정책은 RUNTIME이다.
@Documented, @Inherited
-javadoc으로 작성한 문서에 포함시키려면 @Documented를 붙인다.
-애너테이션을 자손 클래스에 상속하고자 할 때, @Inherited를 붙인다.
@Repeatable
-반복해서 붙일 수 있는 애너테이션을 정의할 때 사용
-@Repeatable이 붙은 애너테이션은 반복해서 붙일 수 있다.
-@Repeatable인 @ToDo를 하나로 묶을 컨테이너 애너테이션도 정의해야 함
#12-34~37
애너테이션 타입 정의하기
-애너테이션을 직접 만들어 쓸 수 있다.
@interface 애너테이션이름{ 타입 요소이름(); // 애너테이션의 요소를 선언한다. . . . }
-애너테이션의 메서드는 추ㅏㅇ 메서드이며, 애너테이션을 적용할 때 지정(순서X)
ex)
@interface TestInfo{ int count(); String testedBy(); String[] testTools(); TestType testType(); // enum TestType { FIRST, FINAL } DateTime testDate(); // 자신이 아닌 다른 애너테이션(@DateTime)을 포함할 수 있다. }
애너테이션의 요소
-적용시 값을 지정하지 않으면, 사용될 수 있는 기본값 지정 가능(null제외)
ex)
@interface TestInfo { int count() default 1; // 기본값을 1로 지정 } @TestInfo // @TestInfo(count=1)과 동일 public class NewClass {. . .}
-요소가 하나이고, 이름이 value일 때는 요소의 이름 생략가능
ex)
@interface TestInfo { String value(); } @TestInfo("passed") // @TestInfo(value="passed")와 동일 class NewClass {. . .}
-요소의 타입이 배열인 경우, 괄호{}를 사용해야 한다.
ex)
@interface TestInfo { String[ ] testTools(); }
(이후 예시는 영상강의 참조, 요약만 서술할 것)
모든 애너테이션의 조상 - java.lang.annotation.Annotation
-Annotation은 모든 애너테이션의 조상이지만 상속은 불가
-사실 Annotation은 인터페이스이다.
마커 애너테이션 - Marker Annotation
-요소가 하나도 정의되지 않은 애너테이션
애너테이션 요소의 규칙
-애너테이션의 요소를 선언할 때 아래의 규칙을 반드시 지켜야 한다.
=>요소의 타입은 기본형, String, enum, 애너테이션, Class만 허용됨
=>괄호()안에 매개변수를 선언할 수 없다.
=>예외를 선언할 수 없다.
=>요소를 타입 매개변수로 정의할 수 없다.
chapter13
#13-1
프로세스와 쓰레드(process & Thread)
-프로세스 : 실행중인 프로그램, 자원(resources)과 쓰레드로 구성
-쓰레드 : 프로세스 내에서 실제 작업을 수행.
모든 프로세스는 최소한 하나의 쓰레드를 가지고 있다.
' 프로세스 : 쓰레드 = 공장 : 일꾼 '
으로 비유할 수 있음.
-싱글 쓰레드 프로세스 = 자원 + 쓰레드
-멀티 쓰레드 프로세스 = 자원 + 쓰레드 + 쓰레드 + ... + 쓰레드
"하나의 새로운 프로세스를 생성하는 것보다
하나의 새로운 쓰레드를 생성하는 것이 더 적은 비용이 든다."
-2 프로세스 1 쓰레드 vs 1 프로세스 2 쓰레드
=>후자가 더 적은 비용이 든다.
-대부분의 프로그램이 멀티쓰레드로 작성되어 있다.
그러나, 멀티쓰레드 프로그래밍이 장점만 있는 것은 아니다.
장점:
1)시스템 자원을 보다 효율적으로 사용할 수 있다
2)사용자에 대한 응답성(respnseness)이 향상도니다.
3)작업이 분리되어 코드가 간결해 진다.
"여러 모로 좋다."
단점:
1)동기화(synchronization)에 주의해야 한다.
2)교착상태(dead-lock)가 발생하지 않도록 주의해야 한다.
3)각 쓰레드가 효율적으로 고르게 실행될 수 있게 해야 한다.
"프로그래밍할 때 고려해야 할 사항들이 많다."
#13-3~6
쓰레드의 구현과 실행
1)Thread를 상속
ex)
class MyThread extends Thread { public void run() { // Thread클래스의 run()을 오버라이딩 /* 작업내용 */ } }
2)Runnable인터페이스를 구현
ex)
class MyThread2 implements Runnable { public void run() { // Runnable인터페이스의 추상메서드 run()을 구현 /* 작업내용 */ } }
쓰레드의 실행 - start()
-쓰레드를 생성한 후에 start()를 호출해야 쓰레드가 작업을 시작한다.
ex)
ThreadEx1_1 t1 = new ThreadEx1_1(); // 쓰레드 t1을 생성한다. ThreadEx1_1 t2 = new ThreadEx1_1(); // 쓰레드 t2를 생성한다. t1.start(); // 쓰레드 t1을 실행시킨다. t2.start(); // 쓰레드 t2를 실행시킨다.
start()와 run()
(영상강의 14:43)
#13-7~13
main쓰레드
-main메서드의 코드를 수행하는 쓰레드
-쓰레드는 '사용자 쓰레드'와 '데몬 쓰레드' 두 종류가 있다.
"실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다."**
싱글쓰레드와 멀티쓰레드
-싱글쓰레드
class ThreadTest{ public static void main(String args[]){ for(int i=0; i<300; i++) { System.out.println("-"); } for(int i=0; i<300; i++) { System.out.println("|"); } } // main }
-멀티쓰레드
class ThreadTest{ public static void main(String args[]){ MyThread1 th1 = new MyThread1(); MyThread1 th2 = new MyThread2(); th1.strat(); th2.strat(); } } class MyThread1 extends Thread { public void run() { for(int i=0; i<300; i++) { System.out.println("-"); } } // run() } class MyThread2 extends Thread { public void run() { for(int i=0; i<300; i++) { System.out.println("|"); } } // run() }
쓰레드의 I/O블락킹(blocking)
(위에서 I/O는 Input/Output, 즉 입력/출력)
-싱글쓰레드
class ThreadEx6 { public static void main(String args[]){ String input = JOptionPane.showInputDialog("아무 값이나 입력하세요"); System.out.println("입력하신 값은 " + input + "입니다."); for(int i=10; i>0; i--) { System.out.println(i); try { Thread.sleep(1000); } catch(Exception e) {} } } // main }
-멀티쓰레드
class ThreadEx7 { public static void main(String args[]){ ThreadEx7_1 th1 = new ThreadEx7_1(); th1.start(); String input = JOptionPane.showInputDialog("아무 값이나 입력하세요"); System.out.println("입력하신 값은 " + input + "입니다."); } } class ThreadEx7_1 extends Thread { public void run() { for(int i=10; i>0; i--) { System.out.println(i); try { Thread.sleep(1000); } catch(Exception e) {} } } // run() }
#13-14~17
쓰레드의 우선순위(priority of thread)
-작업의 중요도에 따라 쓰레드의 우선순위를 다르게 하여 특정 쓰레드가 더 많은 작업시간을 갖게 할 수 있다.
void setPriority(int newPriority) : 쓰레드의 우선순위를 지정한 값으로 변경한다 int getPriority() : 쓰레드의 우선순위를 반환한다.
public static final int MAX_PRIORITY = 10 //최대우선순위 public static final int MIN_PRIORITY = 10 //최소우선순위 public static final int NORM_PRIORITY = 10 //보통우선순위
쓰레드 그룹
-서로 관련된 쓰레드를 그룹으로 묶어서 다루기 위한 것
-모든 쓰레드는 반드시 하나의 쓰레드 그룹에 포함되어 있어야 한다.
-쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 'main쓰레드 그룹'에 속한다.
-자신을 생성한 쓰레드(부모 쓰레드)의 그룹과 우선순위를 상속받는다.
Thread(ThreadGroup group, String name) Thread(ThreadGroup group, Runnable target) Thread(ThreadGroup group, Runnable target, String name) Thread(ThreadGroup group, Runnable target, String name, long stackSize)
ThreadGroup getThreadGroup() 쓰레드 자신이 속한 쓰레드 그룹을 반환한다. void uncaughtExcption(Thread t, Throwable e) 처리되지 않은 예외에 의해 쓰레드 그룹의 쓰레드가 실행이 종료되었을 때, JVM에 의해 이 메서드가 자동적으로 호출된다.
쓰레드 그룹의 메서드
(표는 강의영상 참고 13:50)
#13-18~21
데몬 쓰레드(daemon thread)
-일반 쓰레드(non-daemon thread)의 작업을 돕는 보조적인 역할을 수행.
-일반 쓰레드가 모두 종료되면 자동적으로 종료된다.
-가비지 컬렉터, 자동저장, 화면 자동갱신 등에 사용된다.
-무한루프와 조건문을 이용해서 실행 후 대기하다가 특정조건이 만족되면
작업을 수행하고 다시 대기하도록 작성한다.
ex)
public void run() { while(true) { try { Thread.sleep(3 * 1000); // 3초마다 } catch(InterruptedException e) { } // autoSave의 값이 true이면 autoSave()를 호출한다. if(autoSave) { autoSave(); } } }
boolean isDaemon() - 쓰레드가 데몬 쓰레드인지 확인한다. 데몬 쓰레드이면 true를 반환 void setDaemon(boolean on) - 쓰레드를 데몬 쓰레드로 또는 사용자 쓰레드로 변경 매개변수 on을 true로 지정하면 데몬 쓰레드가 된다.
*setDaemon(boolean on)은 반드시 start()를 호출하기 전에 실행되어야 한다.
그렇지 않으면 IllegalThreadStateException이 발생한다.
쓰레드의 상태
쓰레드에는 5가지의 상태가 있음
1)NEW : 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태 2)RUNNABLE : 실행 중 또는 실행 가능한 상태 3)BLOCKED : 동기화블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태) 4)WAITING, : 쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrunnable) 일시정지 TIMED_WAITING 상태. TIMED_WATING은 일시정지시간이 지정된 경우를 의미 5)TERMINATED : 쓰레드의 작업이 종료된 상태
(강의 영상에 나오는 그림 참고 12:40)
쓰레드의 실행제어
-쓰레드의 실행을 제어할 수 있는 메서드가 제공된다.
(표는 강의영상 20:00 참고)
#13-22~25
sleep()
-현재 쓰레드를 지정된 시간동안 멈추게 한다.
static void sleep(long millis) // 천분의 일초 단위 static void sleep(long millis, int nanos) // 천분의 일초 + 나노초
-예외처리를 해야 한다.(InterruptedException이 발생하면 깨어남)
try { Thread.sleep(1, 500000); // 쓰레드를 0.0015초 동안 멈추게 한다. } catch(InterruptedException e) { }
근데 항상 이렇게 예외처리 하기 불편하니까
void delay(long millis) { try{ Thread.sleep(millis); } catch(InterruptedException e) { } }
이렇게 메소드를 따로 만들어줌
-특정 쓰레드를 지정해서 멈추게 하는 것은 불가능하다.
try { th1.sleep(2000); } catch(InterruptedException e) { }
위 코드처럼 th1을 잠자게 하는것 같지만, 자기 자신이 잔다(코드를 실행하는 쓰레드가 잠을 잠)
그래서 th1을 슬립하는것처럼 오해할 수 있기때문에 '위 처럼 쓰면 안됨'!(에러는 안남)
try { Thread.sleep(2000); // th1이 아니라, 클래스 이름인 Thread를 넣어줌. } catch(InterruptedException e) { }
이렇게 작성해 주어야 한다.
interrupt()
-대기상태(WAITING)인 쓰레드를 실행대기 상태(RUNNABLE)로 만든다.
void interrupt() : 쓰레드의 interrupted상태를 false에서 true로 변경. boolean isInterrupted() : 쓰레드의 interrupted상태를 반환. static boolean interrupted() : 현재 쓰레드의 interrupted상태를 알려주고, false로 초기화
ex)
public static void main(String[] args) { ThreadEx13_2 th1 = new ThreadEx13_2 (); th1.start(); . . . th1.interrupt(); // interrupted() 를 호출하면, interrupted상태가 true가 된다. . . . System.out.println("isInterrupted() : " + th1.isInterrupted() ); // true }
#13-26,27
suspend(), resume(), stop()
-쓰레드의 실행을 일시정지, 재개, 완전정지 시킨다.
void suspend() : 쓰레드를 일시정지 시킨다. void resume() : suspend()에 의해 일시정지된 쓰레드를 실행대기상태로 만든다. void stop() : 쓰레드를 즉시 종료시킨다.
(실행원리는 영상강의 참조 00:50)
-suspend(), resume(), stop()은 교착상태에 빠지기 쉬워서 deprecated되었다.
class ThreadEx17_1 implements Runnable { boolean suspended = false; boolean stopped = false; public void run() { while(!stopped) { if(!suspended) { /* 쓰레드가 수행할 코드를 작성 */ } } } public void suspend() { suspended = true; } public void resume() { suspended = false; } public void stop() { stopped = true; } }
#13-28,29
join()
-지정된 시간동안 특정 쓰레드가 작업하는 것을 기다린다.
void join() // 작업이 모두 끝날 때까지 void join(long millis) // 천분의 일초 동안 void join(long millis, int nanos) // 천분의 일초 + 나노초 동안
-예외처리를 해야 한다.(InterruptedException이 발생하면 작업 재개)
public static void main(String args[]) { ThreadEx19_1 th1 = new ThreadEx19_1(); ThreadEx19_2 th2 = new ThreadEx19_2(); th1.start(); th2.start(); startTime = System.currentTimeMillis(); try { th1.join(); // main쓰레드가 th1의 작업이 끝날 때까지 기다린다. th2.join(); // main쓰레드가 th2의 작업이 끝날 때까지 기다린다. } catch(InterruptedException e) { } System.out.print("소요시간 : " + (System.currentTimeMillis() - ThreadEx19.startTime ) ); } // main
join() - 예시
public void run() { while(true) { try{ Thread.sleep(10 * 1000); // 10초를 기다린다. } catch(InterruptedException e) { System.out.println("Awaken by interrupt()."); } gc(); // garbage collection을 수행한다. System.out.println("Garbage Collected. Free Memory : " + freeMemory() ); } }
(위 코드는 가비지 컬렉터를 흉내낸 것)
yield()
-남은 시간을 다음 쓰레드에게 양보하고, 자신(현재 쓰레드)은 실행대기한다.
(그림설명은 영상강의 참조 10:40)
-yield()와 interrupt()를 적절히 사용하면, 응답성과 효율을 높일 수 있다.
(예시 코드도 영상강의 참조 11:50)
#13-30~33
쓰레드의 동기화(synchronization)
-멀티 쓰레드 프로세스에서는 다른 쓰레드의 작업에 영향을 미칠 수 있다.
-진행중인 작업이 다른 쓰레드에게 간섭받지 않게 하려면 '동기화'가 필요
"쓰레드의 동기화 - 한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하게 막는 것"
-동기화 하려면 간섭받지 않아야 하는 문장들을 '임계 영역'으로 설정
-임계영역은 락(lock)을 얻은 단 하나의 쓰레드만 출입가능(객체 1개에 락 1개)
synchronized를 이용한 동기화
-synchronized로 임계영역(lock이 걸리는 영역)을 설정하는 방법 2가지
1) 메서드 전체를 임계 영역으로 지정
public synchronized void calcSum() { // // . . . // 이 세 줄이 임계 영역(critical section) } //
2) 특정한 영역을 임계 영역으로 지정
synchronized(객체의 참조변수){ // // . . . // 이 세 줄이 임계 영역(critical section) } //
ex)
public synchronized void withdraw(int money) { if(balance >= money) { try { Thread.sleep(1000); } catch(Exception e) { } balance -= money; } }
↑
↓
public void withdraw(int money) { synchronized(this) { if(balance >= money) { try { Thread.sleep(1000); } catch(Exception e) { } balance -= money; } } // synchronized(this) }
synchronized를 이용한 동기화 - 예제
(예제는 영상강의 참고 07:35)
#13-34~36
wait()과 notify()
-동기화의 효율을 높이기 위해 wait(), notify()를 사용.
-Object클래스에 정의되어 있으며, 동기화 블록 내에서만 사용할 수 있다.
=>wait() - 객체의 lock을 풀고 쓰레드를 해당 객체의 waiting pool에 넣는다.
=>notify() - waiting pool에서 대기중인 쓰레드 중의 하나를 깨운다.
=>notifyAll() - waiting pool에서 대기중인 모든 쓰레드를 깨운다.
(예제와 자세한 설명은 영상강의 참고 01:00)
wait()와 notify() - 예제
(분량이 많으므로 강의영상을 참고 08:10~)