chained exception
예외는 다른 예외를 유발할 수 있습니다.
여러 가지 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위해 사용합니다.
checked exception을 unchecked exception으로 포장하는 데 유용하게 사용되기도 합니다.
initCause()
getCause()
언어에서 중복되거나 필요없는 코드를 줄여줍니다.
제네릭은 클래스 또는 메서드에 사용 가능합니다.
<>안에 들어가야 할 타입을 명시해야 합니다.
** < T >를 가장 규약적으로 사용합니다.
public class Generic <T>
제네릭에서 <> 사이에 들어가는 변수명 T는 타입변수라고 합니다.
제네릭 클래스를 원시 타입이라고 합니다.
객체의 static 멤버에 사용할 수 없습니다.
제네릭 배열도 생성할 수 없습니다.
public class Generic <T,U,E>
다수의 타입 변수를 사용할 수 있습니다.
다형성, 즉 상속과 타입의 관계는 그대로 적용됩니다.
부모 클래스로 제네릭 타입 변수를 지정하고 그 안에 자식 클래스를 넘기는 것은 잘 동작합니다.
와일드 카드를 통해 제네릭의 제한을 구체적으로 정할 수 있습니다.
< ? extends T > : T와 그 자손들만 사용 가능
< ? super T > : T와 그 조상들만 사용 가능
< ? > : 제한 없음
메서드를 스코프로 제네릭을 별도로 선언할 수 있습니다.
제네릭 메소드의 제네릭 타입 변수는 해당 메소드에만 적용되기 때문에 메소드 하나를 기준으로 선언하고 사용할 수 있습니다.
같은 이름의 변수를 사용했다고 해도 제네릭 메소드의 타입 변수는 제네릭 클래스의 타입 변수와 다릅니다.
List 배열 예시
추상적 자료구조로서, 순서를 가지고, 일렬로 나열한 원소들의 모임입니다.
순서가 있고 중복을 허용한다는 점에서 집합(Set)과 구분됩니다.
배열은 프로그래밍언어에서 지원하는 자료형, 또는 컴퓨터 공학에서 사용하는 자료구조의 하나입니다. 순서대로 번호가 붙은 원소들이 연속적인 형태로 구성된 구조를 뜻합니다.
List는 인터페이스로서, 추상적으로 정의되어있습니다.
원시타입은 값 이상의 의의를 가지지는 않습니다.
추상적인 기능이 필요하거나 객체로서의 기능이 필요할 때 기본형의 원시타입을 래퍼 클래스로 사용할 수 있습니다.
기본 값을 객체화 하는 것을 박싱이라고 하며,
반대를 언박싱이라고 합니다.
JDK 1.5부터는 오토 박싱이라는 기능을 제공합니다.
프로세스 : 운영체제로부터 자원을 할당받는 작업의 단위
할당되는 것
코드
데이터
프로그램이 실행 중 저장할 수 있는 저장 공간
(전역 변수, 정적 변수, 배열 등 초기화 된 데이터)
메모리
stack : 지역 변수, 매개 변수, 리턴 변수
heap : 동적으로 필요한 변수를 저장하는 공간 (new(), mallok()) )
쓰레드 : 프로세스가 할당받은 자원을 이용하는 실행의 단위
일꾼입니다. 코드 실행의 흐름.
실행 요청이 들어오면 쓰레드를 만들어 명령을 처리합니다.
쓰레드들은 프로세스 내 주소 공간이나 메모리공간(heap)을 공유 받습니다.
각각 명령 처리를 위한 자신만의 메모리 공간(stack)도 할당 받습니다.
JVM 프로세스 위에서 실행되며, JAVA main 쓰레드부터 실행됩니다.
프로세스 안에서 하나의 쓰레드만 실행되는 것을 말합니다.
자바는 메인 쓰레드가 메인 메서드를 실행시키면서 시작됩니다.
메인쓰레드는 필요에 따라서 작업 쓰레드들을 병렬시켜 사용할 수 있습니다.
즉, 자바는 멀티 쓰레드를 지원합니다.
여러 개의 작업을 동시에 할 수 있어서 성능이 좋아집니다.
스택을 제외한 모든 영역에서 메모리를 공유하기 때문에 자원을 보다 효율적으로 사용할 수 있습니다.
응답 쓰레드와 작업 쓰레드를 분리하여 빠르게 응답을 줄 수 있습니다.(비동기)
하지만, 동기화 문제가 발생할 수 있습니다.
프로세스의 자원을 공유하면서 작업을 처리하기 떄문에 자원을 서로 사용하려고 하는 충돌이 발생할 수 있다는 뜻입니다.
또, 교착(데드락)이 발생할 수 있습니다.
서로의 자원을 양보하기를 무한히 기다리는 케이스입니다.
방법 1. 해당 클래스가 Thread를 상속하고(java.iang 제공),
run 메서드를 오버라이드 해 그 속에 로직을 구현합니다.
메인 메소드에서 쓰레드 객체를 생성하고,객체명.start();
을 실행합니다.
방법 2. runnable 인터페이스를 상속받고, 오버라이드 한 run 메소드 안에 로직을 구현합니다.
메인 메소드에서Runnable run = new 클래스명();
을 선언합니다.
Thread 객체를 선언하고 인자를 run 메서드(객체)로 기입합니다.Thread thread = new Thread(run);
그리고thread.start();
을 실행합니다.
setName() 메서드를 호출하는 이유는 스레드의 이름을 명시적으로 설정하기 위해서입니다. 스레드의 이름을 설정하는 이유는 스레드 디버깅, 로깅 및 코드 이해를 돕기 위해서입니다.
여러 스레드가 동시에 실행될 때, 각 스레드의 동작을 추적하고 디버깅하기 어렵습니다. 스레드의 이름을 설정하면 각 스레드가 어떤 작업을 수행하고 있는지 식별할 수 있어서 디버깅 및 로깅 작업이 훨씬 효과적으로 수행됩니다.
예를 들어, 위의 코드에서 setName("thread1")을 사용하면 thread1이라는 이름을 가진 스레드가 "2번 => " 뒤에 출력됩니다. 이렇게 하면 Thread-0이라는 기본 이름 대신 개발자가 정한 이름을 가진 스레드로 식별할 수 있습니다.
스레드의 이름을 객체 명칭과 혼동해서는 안됩니다. 스레드의 이름은 스레드 자체의 식별 목적을 위한 것이며, 스레드 객체의 변수명과는 관련이 없습니다.
따라서 setName() 메서드를 사용하여 스레드의 이름을 설정하면 스레드를 구분하고 추적하기가 더 쉬워집니다.
보이지 않는 곳에서 실행되는 낮은 우선순위를 가진 쓰레드를 말합니다.
보조적인 역할을 담당하며 대표적인 데몬 쓰레드로는 메모리 영역을 정리해주는 가비지 컬렉터가 있습니다. 상대적으로 다른 쓰레드에 비해 리소스를 적게 할당받습니다.
Thread 인스턴스를 생성하고, 인자를 demon으로 설정합니다.
thread.setDemon(true);
로 설정이 완료되도록 합니다.
보이는 곳에서 실행되는 높은 우선순위를 가진 쓰레드를 말합니다.
프로그램 기능을 담당하며 대표적으로 메인 쓰레드가 있습니다.
JVM은 우선 순위가 높은 사용자 쓰레드의 작업이 끝나면 데몬 쓰레드도 자동으로 종료시켜버립니다.
쓰레드 작업의 중요도에 따라서 쓰레드의 우선순위를 부여할 수 있습니다.
작업의 중요도가 높을 때 우선 순위를 높게 지정하면 더 빨리 처리할 수 있습니다.
우선순위는 JVM에서 설정한 것이라는 점을 잊지 말아야 합니다.
객체명,setPriority(수);
1~10까지 지정할 수 있으며, 더 먼저 실행될 가능성이 높을 뿐이지 100%라고 할 수는 없습니다.
쓰레드 그룹
쓰레드들은 기본적으로 그룹에 포합되어 있습니다.
모든 쓰레드들은 반드시 하나의 그룹에 포함이 되어있어야 합니다.
별도로 지정도 가능합니다.
쓰레드 그룹을 생성합니다.
Thread group1 = new ThreadGroup("group1");
그 다음 각 쓰레드를 인스턴스화 시키면서, 생성자에 예시처럼 적습니다.
new Thread(그룹 이름, 할당할 메서드, 쓰레드 이름);
갑자기 든 궁금증 해결
Thread.sleep은 왜 try catch문 안에만 위치해야 하나요?
gpt 대답
Thread.sleep() 메서드는 InterruptedException을 던질 수 있는 메서드로, InterruptedException은 스레드가 interrupt() 메서드를 호출하거나 스레드가 인터럽트 상태일 때 발생할 수 있습니다. 이것은 스레드가 잠자고 있을 때 다른 스레드에서 해당 스레드를 인터럽트하려는 경우에 유용합니다.
따라서 Thread.sleep()를 사용할 때는 InterruptedException에 대한 처리를 해주는 것이 중요합니다. 이 예외를 처리하지 않으면 컴파일러에서 오류가 발생하지 않지만, 좋은 프로그래밍 실천법에 따라 예외 처리를 해주는 것이 바람직합니다.
이렇게 예외 처리를 포함시키면 스레드가 인터럽트되었을 때 적절한 조치를 취할 수 있습니다. 예를 들어, 스레드를 종료하거나 다시 슬립을 시도하거나 기타 작업을 수행할 수 있습니다.
쓰레드는 실행과 대기를 반복하며 run() 메서드를 수행합니다.
run() 메서드가 종료되면 실행이 멈추게 됩니다.
더 자세히 보자면, start() 메서드를 통해 실행 대기 -> 실행 -> 일시정지를 반복합니다.
종료(Terminated)
쓰레드의 작업이 종료된 상태
현재 쓰레드를 지정된 시간동안 멈추게 합니다.
자기 자신에 대해서만 사용할 수 있습니다.
static 메서드로, 객체 생성 후 호출이 의미가 없으며 '현재의 쓰레드'를 일시정지 시킨다는 것을 다시 강조드립니다.
일시 정지 상태인 쓰레드를 실행 대기 상태로 만듭니다.
isInterrupted를 적절히 지정해야 합니다.
정해진 시간동안 지정한 쓰레드가 작업하는 것을 기다립니다.
시간을 지정하지 않았을 때는 지정한 쓰레드의 작업이 끝날 때까지 기다립니다.
예외처리를 해야 합니다. interrupt()를 만나면 기다리는 것을 멈추기 때문에.
리소스를 양보하는 개념입니다.
sunchronized
멀티 쓰레드의 경우 서로에게 영향을 주어 장애나 버그가 발생할 수 있습니다.
이러한 일을 방지하기 위해 한 쓰레드가 진행중인 작업을 다른 쓰레드가 침범하지 못하도록 막는 것을 쓰레드 동기화라고 합니다.
동기화를 하려면 다른 쓰레드의 침범을 막아야 하는 코드들을 '임계영역'으로 설정하면 됩니다. 임계영역에는 Lock을 가진 단 하나의 쓰레드만 출입이 가능합니다.
즉, 임계영역은 한 번에 한 쓰레드만 사용이 가능합니다.
실행할 메서드 또는 실행할 코드 묶음 앞에.
wait() / notify()
침범을 막은 코드를 수행하다가 작업을 더이상 진행할 상황이 아니면 wait을 호출하여 쓰레드가 Lock을 반납하고 기다리게 할 수 있습니다.
추후에 작업을 진행할 수 있는 상황이 되면 notify를 호출해서 작업을 중단했던 쓰레드가 다시 작업을 진행할 수 있게 합니다.
notify는 대상을 지정해주어야 합니다.
Lock, condition
synchronized블럭으로 동기화하면 자동으로 Lock이 걸리고 풀리지만, 같은 메서드 내에서만 Lock을 걸 수 있다는 제약이 있습니다.
이런 제약을 해결하기 위해 Lock 클래스를 사용합니다.
ReentrantLock
재진입 가능한 Lock, 가장 일반적인 배타 Lock
특정 조건에서 Lock을 풀고, 나중에 다시 Lock을 얻어 임계영역으로 진입이 가능합니다.
그 외에도 있습니다...
condition 인터페이스
waiting pool 내의 스레드를 분리하여 특정 조건이 만족될 때만 깨우도록 할 수 있으며, ReentrantLock과 함께 사용됩니다.
wait()과 notify() 대신 await()과 signal()을 사용합니다.
객체지향, 캡슐화, 멀티 쓰레드.
앞으로 배울 모르는 것들까지.
계속해서 더 좋은 방법을 찾아가라고 온갖 도구를 손에 쥐어주는 느낌이다.
어렵더라도 왜 필요한지 아주 조금은 알겠다.
내일 마무리하고 꼭 객체지향부터 다시 복습해야지.