- Checked Exception : 반드시 예외처리를 해야함
- UnChecked Exception : 컴파일이 이상없이 실행이 된다
프로그램이 스스로 해결할 수 없는 에러
(Program이 지속할 수 없는 오류)
프로그램적으로 해결할 수 있는 오류
(자바는 클래스의 집합이기 때문에 정의를 해두었다)
프로그램을 실행하다가 프로그램이 정상적으로 돌아가지 않는 상황에 처하면 JVM이 현재 오류에 대한 정보를 모아서 객체(인스턴스)를 만들어서 준다 → 자동생성됨
만약 이 객체를 해결하지 못하면 프로그램이 자동 종료. 처리되면 종료되지 않고 지속적인 수행이 가능하다.
Exception이 발생하게 되면 프로그램이 즉시 멈추게 된다
public class Main {
public static void main(String[] args) {
System.out.println("프로그램 시작!");
int result = 10/0; //오류가 발생
System.out.println("프로그램 종료");
}
}
여기서 try-catch를 사용하게 되면 프로그램이 멈추지 않고 돌아가는 것을 볼 수 있다.
public class Main {
public static void main(String[] args) {
System.out.println("프로그램 시작!");
try { //예외가 생길 여지가 있는 구문을 넣어준다
int result = 10/0;
//오류가생긴 타입 //e- 객체
}catch (ArithmeticException e){//catch- 어떤 오류를 잡을 것인지 넣어준다
//오류 처리코드
System.out.println("오류가 발생했어요!");
}
System.out.println("프로그램 종료");
}
}
try{ //에러가 발생할 수 있는 코드 throw new Exception(); //강제 에러 출력 }catch (Exception e){ //에러시 수행 e.printStackTrace(); //오류 출력(방법은 여러가지) throw e; //최상위 클래스가 아니라면 무조건 던져주자 }finally{ //무조건 수행 }
에러가 났던 ArithmeticException을 사용하지 않고 Exception을 사용하는 이유는 상위 클래스임으로 모든 예외를 잡을 수 있기 때문이다
컴퓨터의 하드웨어를 사용하게 해주는 프로그램이다
현재 실행중인 프로그램. 각프로그램마다 메모리를 사용한다
프로그램을 실행시키기 위해 운영체제로부터 자원(resource)을 받아야 한다.
운영체제 입장에서 보면 자바의 JVM도 하나의 프로세스이다
하나의 프로세스 안에서도 여러개의 흐름이 동작할 수 있다
스레드 thread는 exception stack을 별도로 가지고 있는 실행 흐름이다
스레드는 독립적인 실행흐름이라서 스레드마다 스택이 별도로 할당이 된다
자바는 JVM만 있다면 어떤 운영체제에서도 동작할 수 있다. 자바 프로그램이 동시에 작업을 하도록 하기 위해서는 스레드를 공부해야만 한다!
CPU안에 실제로 일을 할 수 있는 것을 코어cor라고 한다
코어는 한순간에 하나의 일밖에 못한다 (일꾼인데 일 하나밖에 못함)
멀티 프로세싱 Milti Processing: 프로그램 두개(코어 2개 이상)가 시간적의미로 동시에 실행하는것
하나의 프로세스 안에서 여러개의 코어로 동시에 실행하는것
멀티테스킹 VS 멀티 프로세싱
- 멀티 테스킹 Multi Tasking : 코어 하나로 굴리면서 마치 여러개의 기능을 수행하는 것처럼 보이게 하는것
- 멀티 프로세싱 Multi Processing: 멀티 코어로 시간적인 의미로 동시에 실행하는것.
자바에서 main() 메소드 자체가 스레드를 뜻하지 않는다
방법1 - Thread 클래스 상속
스레드 클래스를 직접상속해서 Userdefine 사용자 정의 스레드 클래스를 만든다
- 자바는 단일상속만 지원하기 때문에 (다중상속안됨)하나만 사용 가능한 제약이 있다
- 클래스끼리의 결합이 강해져서 재사용하기가 힘들다
- 직접적인 호출이 아닌 간접 호출이다
t.start()와 밑의 println은 스케줄러에 의해 실행순서가 달라진다class MyThread extends Thread{ @Override public void run() { System.out.println("Hello!"); } } public class Main { public static void main(String[] args) { MyThread t = new MyThread(); t.run(); //위의 클래스로부터 파생된 새로운 메소드 run() 실행 t.start(); //thread를 생성하고 stack을 할당받고 run()호출한다 } }
방법2 - Runnable 인터페이스 구현
Runnable Interface 스레드를 만들기 위한 용도의 인터페이스를 사용한다
- Runnable 인터페이스를 받아 UserDefine 클래스를 생성하고 난뒤 객체(인스턴스)를 만든다
- 상속관계가 없기 때문에 1번보다 자유롭다
- 1번 처럼 상속 받는것보다 인터페이스로 구현하는것이 더 자주 사용된다
class MyThread extends Thread{ //방법1 @Override public void run() { System.out.println("Hello!"); } } class MyThread2 implements Runnable{ //방법2 @Override public void run() { System.out.println("실행이된다! "); } } public class Main { public static void main(String[] args) { MyThread t = new MyThread(); t.run(); //위의 클래스로부터 파생된 새로운 메소드 run() 실행 t.start(); //thread를 생성하고 stack을 할당받고 run()호출한다 MyThread2 a = new MyThread2(); Thread t1 = new Thread(a); t1.start(); System.out.println("안녕하세요!"); } } 결과>> Hello! Hello! 안녕하세요! 실행이된다!
1,2 방법 둘다 내가 스레드를 만들어야한다
- 일반적인
run()
은 스레드가 생성되지 않는다
- main스레드 스택이 최초생성되어
main()
이 스택에 쌓인다- main 스택의 main메소드 영역에 t변수가 생성된다
MyThread 인스턴스도 힙에 생성된다 (t 변수가 인스턴스 참조)t.run()
호출로 run() 스택이 main() 에 쌓인다 -> Hello출력
- 새로운 thread가 생성되는
start()
t.start()
호출로 인해 새로운 Thread스레드가 생성된다. stack도 새롭게 할당됨.run()
을 호출하여 Hello 출력- main메소드 영역에 a변수 생성 MyThread2 인스턴스가 힙에 생기고 a가 인스턴스를 참조한다
- main메소드 영역에 t1 변수가 생성된다 Thread 인스턴스가 힙에 생성된다
t1에 a가 생성자에 들어간다. a는 Thread 인스턴스를 참조할수 있게되었다.t1.start()
로 인해 main 스택에 start()가 쌓인다. 새로운 Thread스레드 생성으로 stack도 할당되어 공간이 새롭게 생긴다. 여기서 run()메소드가 들어가 실행한다 -> 실행이된다 출력- main()의 마지막 println이 안녕하세요를 출력하고 끝낸다
- thread scheduler에 의해 실행되기 때문에 처음 run()을 제외한 나머지는 특정 thread내부의 method의 실행순서가 보장되지 않는다
예제
class ThreadEX_01_1 extends Thread{ @Override public void run() { for(int i=0; i<5; i++){ System.out.println(this.getName()); } } } class ThreadEx_01_2 implements Runnable{ @Override public void run() { for(int i=0; i<5; i++){ System.out.println(Thread.currentThread().getName()); //runabl로부터 구현한거지 아직 스레드가 아니다! //현재 실행중인 스레드 실행 } } } public class ThreadExam01 { public static void main(String[] args) { ThreadEX_01_1 t1 = new ThreadEX_01_1(); ThreadEx_01_2 r = new ThreadEx_01_2(); Thread t2 = new Thread(r); t1.start(); t2.start(); System.out.println("main thread 종료"); } }
- 1번 실행 결과
- 2번 실행 결과
Thread 종료되는 결과가 계속 바뀌는 것을 볼 수 있음
start()
를 수행하면 Thread가 생성(new)되고 Runnable상태로 들어간다. 스케줄러에 의해 선택을 받을 수 있는 상태이다. 생성된 다른 스레드들도 대기를 하고 있음. 스케줄러는 알고리즘에 의해 선택을 한다(우리는 알 수 없다)- Runnable에 있는 Thread를 스케줄러가 선택하면 CPU(Core)가 붙어 Running상태로 변경한다. (
start()
는 스레드 생성과run()
을 동시에 수행함) 열심히 일을하다 스케줄러가 다른 Thread를 선택하게되면 CPU가 떨어지고 다시 Runnable상태로 변경된다- 이렇게 왔다갔다 상태가 변경되다가 모든 작업이 끝나면 Dead 상태로 변경된다
Dead로 바뀐 스레드는 다시 실행이 불가능하다 새로 스레드를 생성해야함
스레드.setDaemon(true);
- 예제코드
public class ThreadExam2 implements Runnable { static boolean autoSave = false; public static void main(String[] args) { //메인스레드는 이미 실행중! Thread t = new Thread(new ThreadExam2()); t.setDaemon(true); t.start(); //t 스레드 실행 for (int i = 0; i < 10; i++) { try { Thread.sleep(1000); //메인 스레드를 1초 재운다 } catch (Exception e) { } System.out.println(i); if(i==5) { autoSave = true; //데몬 실행 } } } @Override public void run() { while (true){ //t 스레드는 계속 실행 try { Thread.sleep(3000); //t 스레드는 3초동안 재움 }catch ( InterruptedException e){ } if (autoSave){ //3초마다 저장한다 System.out.println("자동저장되었어요!"); } } } } 출력>> 0 1 2 3 4 5 6 7 자동저장되었어요! 8 9
- 메인스레드와 t스레드를 생성했다
- mian스레드는 1초마다 자면서 i의 값을 찍는다
- t 스레드는 3초마다 자면서 실행되는 동안 i=5가 될때 autoSave로 자동저장을 실행한다
앞서 start()
를 배웠다 JAVA에는 다양한 스레드 메소드가 존재한다
sleep()
: 일정시간동안 스레드를 중지시킨다interrupt()
: 스레드를 중지 (sleep을 깨우는(중지) 용도로도 사용)yeid()
: 스레드가 자신에게 주어진 수행시간을 다 쓰지 않고 다른 스레드에게 양보하는 방법
sleep()
과yeid()
는 static 메소드이다
스레드 자기 자신한테만 동작한다
sleep()
: 남을 재울 수 없고 나만 스스로 잘수 있다yeid()
: 남에게 양보할 수 없고 나만 양보할 수 있다
- 일정시간동안 스레드를 중지시킨다
- 지정된 시간이 다 되거나 Thread에 대해
interrupt()
가 호출되면 exception이 뜬다. Interrupted Exception 발생하면서 sleep에서 깨어난다- 항상 try-catch를 사용해야한다
interruped()
로 깨어난다
- 스레드가 한번 데드상태가 되면 끝남
- sleep은 실행중에 일시정지 -outerwise blocked로 멈춘다
- 시간이 timeout 되거나
- 누군가에 의해서 interrupt되거나
- otherwiseblocked후 sleep timeout을 가진뒤에 runnable이됨
- 3초뒤 실행이라고 해도 언제 실행될지 모름
class ThreadExam03_1 extends Thread{
@Override
public void run() {
try {
Thread.sleep(2000); //현재실행하는 스레드를 재워라 => 메인스레드가 자는거
}catch (Exception e){
}for (int i=0; i<300; i++){
System.out.print("-");
}
System.out.print("<<Thread 1 종료>>");
}
}
class ThreadExam03_2 extends Thread{
@Override
public void run() {
for (int i=0; i<300; i++){
System.out.print("|");
}
System.out.print("<<Thread 2 종료>>");
}
}
public class ThreadExam03 {
public static void main(String[] args) {
ThreadExam03_1 t1 = new ThreadExam03_1();
ThreadExam03_2 t2 = new ThreadExam03_2();
t1.start();
t2.start();
}
}
Interrupt() : 스레드를 중지시킴
- stop() → thread 강제중지 → 이제는 사용안함 (의도치않게 다른 결과를 냄)
- start() → thread 시작! → interrupt()
스레드 실행중에 작업이 끝나기전에 해당 스레드를 중지시키고 싶을 때 사용
interrupt()
이용 → 직접 중지시키지 않는다
스레드의 내부 상태값인 Interrupted state 를 변경해주는 역할
- 스레드가 interrupt가 되었는지 확인하는 메서드
interrupted()
: interrupte가 되었니? → thread의 static
특정 스레드 지칭할수 없음.
현재 실행중인 스레드가 interrupt가 걸렸는지 안되었는지 알 수 있다
상태값을 조사하고 그 상태 값을 false로 바꾼다 (초기화 한다)is_interrupted()
: static이 아니다
인스턴스 메서드라 특정 스레드를 지칭할수있다
현재 상태값만 true/false로 리턴을 해준다
class ThreadEx_04 extends Thread{
@Override
public void run() {
int i= 10;
while (i != 0 && !isInterrupted() ){
//i=0이면 튕겨나간다
System.out.println(--i);
try{
Thread.sleep(3000); //제대로 실행되지 않는다 -> 계속 스레드가 돌아감
}catch (Exception e){
interrupt(); //그래서 한번더 재재를 해야한다
}
// for(long k=0; i<250000L; k++); //시간지연
}
System.out.println("카운트가 종료되었어요!");
}
}
public class ThreadExam04 {
public static void main(String[] args) {
/**스레드 인스턴스 생성*/
Thread t = new ThreadEx_04(); //내가 직접 만드는 인스턴스는 ThreadEX_04지만 타입은 상위타입 Thread
t.start(); //동적바인딩으로 타입을 상위타입으로 사용한다
//Swing에 있는 옵션펜 - 입력할수 있는 창이 생성된다 -> 입력값이 프로그래밍한테 전달된다
//값을 입력할때까지 메인 스레드가 멈추게된다
String input = JOptionPane.showInputDialog("값을 입력하세요");
//메세지의 리턴타입으로 들어간다
System.out.println("입력값은 :" + input);
t.interrupt();
//Thread t의 interrupted 상태가 true가 된다
System.out.println("Thread의 상태값은 :" + t.isInterrupted());
}
}
- Thread가 자신에게 주어진 수행시간을 다 쓰지 않고 다른 Thread에게 양보하는 방법
ex) 0.5는 쉬고 0.5는 할꺼야 → 그사이에 스레드를 양보하는 방법- 프로그램의 응답성을 높이기 위해 사용한다
class ThreadEx_06 implements Runnable{
//멀티코어 환경에서 캐시가 아닌 메인 메모리에서 값을 불러오겠다는게 volatile이다
volatile boolean suspended = false; //일시정지 되었니?
volatile boolean stopped = false; //중지되었니?
@Override
public void run() {
//위의 변수 2개를 이용해서
//멈추지 않으면 계속 돌아라
while(!stopped){
//일시멈춤이 아니라면
if (!suspended){ //실행중인 스레드 호출
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
}catch (Exception e) {
}
}else {
Thread.yield();
}
}
}
public void suspend(){
suspended = true;
}
public void stop(){
//stop이 호출되면 true가 떠서 while문이 멈추게된다
stopped = true;
}
public void resume(){
suspended = false; //일시정리된것을 다시 실행시킨다
}
}
public class ThreadExam06 {
public static void main(String[] args) {
ThreadEx_06 r1 = new ThreadEx_06(); //runnable 3개
ThreadEx_06 r2 = new ThreadEx_06();
ThreadEx_06 r3 = new ThreadEx_06();
Thread t1 = new Thread(r1, "*"); //thread 3개
Thread t2 = new Thread(r2, "**");
Thread t3 = new Thread(r3, "***"); //동일한 작업을 하는 스레드를 3개 생성
t1.start();
t2.start();
t3.start(); //runnable이 스케줄러에 의해 랜덤으로 스레드를 선택해서 작동시킨다
try {
Thread.sleep(2000); //메인 스레드를 재운다
r1.suspend(); //t1을 일시 중지
Thread.sleep(2000);
r2.suspend(); //t2을 일시 중지
Thread.sleep(2000);
r1.resume(); //t1이 다시 동작하도록 설정
r1.stop();
r2.stop();
Thread.sleep(2000);
r3.stop();
}catch (Exception e){
}
}
}
다른 스레드를 지정해서 나를 멈추고 참여시킨다
(현재 실행중인 스레드가 멈추고 특정 스레드를 끌어들여 수행하도록 한다)
- Join() : 시간을 정해주지 않고 스레드 스케줄러에 의해 결정하게됨
- Join(long 초): 시간을 정해서 실행되게함
class ThreadEx_07_1 extends Thread{
@Override
public void run() {
for (int i=0; i<300; i++){
System.out.print("-");
}
}
}
class ThreadEx_07_2 extends Thread{
@Override
public void run() {
for (int i=0; i<300; i++){
System.out.print("|");
}
}
}
public class ThreadExam07 {
public static void main(String[] args) {
//두개의 스레드 생성
Thread t1 = new ThreadEx_07_1();
Thread t2 = new ThreadEx_07_2();
//둘다 runnable 상태로 넣는다
//스케줄러가 내부적인 메커니즘으로 선택을 하여 실행시킨다 -> 누가 될지 모름
//스레드 큐에 들어가 튕겨나옴 우리는 알수 없다
t1.start();
t2.start();
//main, t1, t2 3개가 돌고있는 상황
try {
//main스레드가 멈추고 t1을 참여시킨다
t1.join();
//main스레드가 제일 뒤로 밀린다
t2.join();
}catch (Exception e){
}
//위의 명령문이 아니었다면 main이 제일먼저 끝났을 것이다
//join이 들어가면서 t1, t2가 먼저 수행
System.out.println("<<main 종료>>");
}
}
- sleep은 static 메소드이다
특정 스레드를 지정해서 재울 수 없다- join은 인스턴스 메소드이다
특정 스레드를 지정이 가능하다
//편하게 만들기 위해
class ThreadEx08_1 extends Thread{
//static: 인스턴스 안만들어도됨 -> final: 재할당안됨
//메모리의 총량은 1000이다
final static int MAX_MEMORY = 1000; //상수는 전부 대문자화 + SnakeCase 사용
//실제로 사용하는 메모리(변수)
int usedMemory = 0;
@Override
public void run() {
//무한루프
while(true){
//sleep을 사용하려면 try-catch가 강제됨
try {
//thread 클래스가 가지는 static 메소드
Thread.sleep(10000); //10초동안 자라
}catch (Exception e){
System.out.println("interrupt()에 의해서 깨어남");
}
gc();
System.out.println("메모리 청소 완료! 현재 사용가능한 메모리 량은 : "
+ freeMemory());
}
}
//내부에서만 사용하는 메소드임으로 원래 private가 맞다
//garbge collector을 사용 10초마다 청소해준다
public void gc(){
usedMemory -= 300;
if (usedMemory < 0){
//사용중인 메모리양을 마이너스가 되지 않도록 조치
usedMemory = 0;
}
}
//프로그램에서 사용하는 전체 메모리량을 리턴
public int totalMemory(){
return MAX_MEMORY;
}
//남은 용량 (사용가능한 메모리량)을 리턴
public int freeMemory(){
return MAX_MEMORY - usedMemory;
}
}
public class ThreadExam08 {
public static void main(String[] args) {
ThreadEx08_1 t = new ThreadEx08_1();
t.setDaemon(true);
t.start();
int requiredMemory = 0;
for(int i=0; i<20; i++){
requiredMemory = (int)(Math.random() * 10)*20;
//0보다 크고 200보다 작은 정수
//필요한 memory가 사용할 수 있는 양보다 크거나
//현재 전체 메모리양의 60% 이상을 사용하고 있을때 gc를 실행
//if - 지금 내가 필요한 메모리가 가용메모리보다 큰 경우
if ((requiredMemory > t.freeMemory()) ||
(t.freeMemory() < t.totalMemory() * 0.4)){
t.interrupt(); /**gc()실행이 끝날때까지 기다리지 않는다*/
//join을 건다 - gc가 main보다 먼저 수행되도록 하기 위해서
try{
t.join(100);
}catch (Exception e){
}
}
//현재 사용하는 메모리에 위쪽에서 사용하려는 메모리양을 추가
t.usedMemory += requiredMemory;
System.out.println("현재 사용된 메모리 량: " + t.usedMemory);
//전체 메모리량은 1000인데 main이 random 하게 계속 사용함
//if에서 가비지가 청소를 한다 (이미 60%사용하거나 사용하려는 메모리량보다 가용메모리가 적을때)
}
}
}
참고자료
https://coco-log.tistory.com/31
https://coding-factory.tistory.com/280
https://kim-jong-hyun.tistory.com/101
https://veneas.tistory.com/entry/Java-%EC%9E%90%EB%B0%94-Thread-%EC%A3%BC%EC%9A%94-%EA%B8%B0%EB%8A%A5sleep-interrupt-join