[ 단원 08 ] 예외처리와 스레드
학습목표 예외와 예외처리를 이해하고 프로그래밍에 활용할 수 있다. 스레드를 이해하고 스레드를 프로그래밍에 활용할 수 있다. • 에러와 예외의 차이와 클래스 계층구조 • 체크 예외와 비체크 예외의 차이와 처리 방법 • 새로운 예외 클래스의 생성과 사용 방법 스레드를 이해하고 스레드를 프로그래밍에 활용할 수 있다. • 다중 작업과 스레드의 이해 • 클래스 Thread를 상속받아 처리하는 스레드 구현 • 인터페이스 Runnable을 구현하여 처리하는 스레드 구현 스레드 상태와 우선순위, 동기화를 이해하고 프로그래밍에 활용할 수 있다. • 스레드 상태와 전이 방법 • 스레드 우선순위의 지정 방법 • 스레드에서 자원의 공유 문제와 동기화 처리 • Object의 wait( )와 notify( )를 사용한 동기화
1. 예외처리 개요
예외와 에러 자바에서 오류: 에러(error)와 예외(exception)로 구별 예외 에러 Section 1 예외처리 개요 p302 예외와 에러 자바에서 오류: 에러(error)와 예외(exception)로 구별 예외 자바 프로그램에서 실행 중에 발생할 수 있는 경미한 오류를 예외 적절한 처리 모듈을 추가하여 발생한 문제를 복구 가능 IndexOutOfBoundsException과 같이 ◯ ◯ ◯ Exception 형태 에러 메모리나 내부의 심각한 문제 복구가 불가능한 오류인 OutOfMemoryError, InternalError 등 에러와 예외를 모두 객체로 만들어 처리, 관련 클래스 계층 구조 클래스 Throwable 하부로 예외인 Exception 클래스와 에러인 Error 클래스 Exception 클래스 하부 다양한 예외를 위한 클래스
다양한 예외의 발생 예외가 발생하면 바로 프로그램이 중단되므로 발생한 이후의 프로그램을 실행되지 않음 Section 1 예외처리 개요 다양한 예외의 발생 예외가 발생하면 바로 프로그램이 중단되므로 발생한 이후의 프로그램을 실행되지 않음 발생한 예외 클래스의 이름과 예외가 발생한 프로그램 소스와 줄 번호가 표시 NullPointerException 실제 변수에 저장된 객체가 null임에도 불구하고 객체의 멤버를 참조하려는 경우 발생 ArrayIndexOutOfBoundsException 배열에서 배열의 첨자 범위를 벗어난 첨자 사용 ArithmeticException, 0으로 수를 나누려 할 때 발생 ArrayStoreException 배열에 잘못된 유형의 객체를 저장하려 할 때 발생 ClassCastException 객체를 변환할 수 없는 유형으로 변환하려고 할 때 발생 NegativeArraySizeException 배열의 크기를 음수로 지정하는 경우 발생
Section 1 예외처리 개요 실습예제 8-1, 8-2
예외처리 구문 try 예외처리(exception handling) 예외처리 모듈 Section 1 예외처리 개요 예외처리 구문 try 예외처리(exception handling) 실행 중에 여러 이유로 예상하지 못했던 문제가 발생한 경우 이를 적절히 처리하는 모듈 예외처리 모듈 try ~ catch ~ finally 문장 예외 발생과 상관없이 finally의 블록은 실행 catch와 finally 둘 중 하나는 옵션
실습예제 8-3 try 내부에서 예외가 발생하면 더 이상 try 블록 내부의 나머지 문장은 실행하지 않음 Section 1 예외처리 개요 실습예제 8-3 try 내부에서 예외가 발생하면 더 이상 try 블록 내부의 나머지 문장은 실행하지 않음
catch 처리 순서 여러 개의 catch 문을 이용하는 경우 프로그램이 실행되는 순간에 예외가 발생하면 Section 1 예외처리 개요 catch 처리 순서 여러 개의 catch 문을 이용하는 경우 프로그램이 실행되는 순간에 예외가 발생하면 여러 개의 catch 문 중에서 위에서부터 순차적으로 catch(ExceptionType var) 문에서 ExceptionType 인자 유형을 검사 발생된 예외의 유형과 일치하거나 하위 클래스이면 먼저 만나는 catch 문 블록만을 실행 주의할 점 여러 개의 catch 구문에 기술하는 ExceptionType이 하위 클래스인 것부터 먼저 catch 블록을 기술
실습예제 8-4 Section 1 예외처리 개요 예외 참조 변수인 e를 바로 출력하면 예외 클래스 이름과 메시지가 출력 발생한 예외의 메시지만 알아보려면 e.getMessage() 메소드를 사용 예외 처리를 하지 않은 경우 출력되는 모든 메시지를 보려면 e.printStackTrace() 메소드를 사용 모든 예외 클래스는 Exception의 하위 클래스이므로 Exception 유형의 참조변수 catch 블록은 모든 종류의 예외를 처리할 수 있으므로 반드시 catch 블록의 마지막에 배치
2. 체크 예외와 예외 생성
예외 계층 구조 RuntimeException 그 하부 예외 Section 2 체크 예외와 예외 생성 p308 예외 계층 구조 RuntimeException 그 하부 예외 ArithmeticException, NullPointerException, ArrayIndexOutOfBoundsException, IndexOutOfBoundsException
비체크 예외와 체크 예외 비체크 예외(unchecked exception) 체크 예외(checked exception) Section 2 체크 예외와 예외 생성 비체크 예외와 체크 예외 비체크 예외(unchecked exception) RuntimeException과 그 하부 예외가 발생할 가능성이 있는 코드들은 try ~ catch 문이 선택적 RuntimeException, ArithmeticException, NullPointerException 체크 예외(checked exception) 예외 중에서 RuntimeException과 그 하부 예외를 제외한 모든 예외 FileNotFoundException 존재하지 않는 파일을 처리 ClassNotFoundException 사용하려는 클래스의 이름을 잘못 기술 DataFormatException 입력한 데이터의 형식이 잘못
Section 2 체크 예외와 예외 생성 체크 예외 클래스 Class 클래스 Object의 하위 클래스로 응용 프로그램에서 실행 중인 클래스나 인터페이스를 대표하는 클래스 Class의 메소드 중에 정적 메소드인 forName(className) 인자인 className의 객체를 반환하는 메소드 인자인 className의 클래스가 없다면 ClassNotFoundException 예외를 발생 forName(className)을 호출하는 부분에서 반드시 예외처리를 수행 아니면 컴파일 에러 발생
실습예제 8-5 메소드의 예외 확인 메소드 선언을 살펴보면 발생할 수 있는 예외를 알 수 있음 Section 2 체크 예외와 예외 생성 실습예제 8-5 메소드의 예외 확인 메소드 선언을 살펴보면 발생할 수 있는 예외를 알 수 있음 발생하는 예외가 체크 예외라면 메소드 호출 부분에서 예외처리가 없으면 컴파일 시간에 문법 오류가 발생
체크 예외의 2가지 처리방법 첫 번째 방법 두 번째 방법 try~catch 구문 사용 방법 Section 2 체크 예외와 예외 생성 체크 예외의 2가지 처리방법 첫 번째 방법 try~catch 구문 사용 방법 두 번째 방법 예외가 발생될 수 있는 구문이 속한 메소드에서 다시 예외를 전달(propagation)하는 방법 상위 메소드로 예외처리를 미루는 방법 public class TryCheckedException { public static void main(String[] args) { //메소드 Class.forName()을 사용하려면 반드시 예외처리를 해야 함 try { System.out.println(Class.forName("java.lang.Object")); } catch (ClassNotFoundException ex) { System.out.println(ex); } } public class PropagateCheckedException { //메소드 선언에서 다시 예외 ClassNotFoundException의 발생을 전달 public static void main(String[] args) throws ClassNotFoundException { //메소드 Class.forName()을 사용하려면 반드시 예외처리를 해야 함 System.out.println(Class.forName("java.lang.Object")); }
예외 클래스 생성과 발생 새로운 예외 클래스 정의 생성된 예외의 발생 Exception 을 상속받아 구현 Section 2 체크 예외와 예외 생성 예외 클래스 생성과 발생 새로운 예외 클래스 정의 Exception 을 상속받아 구현 생성자에서 super(msg)로 구현 msg에 저장된 문자열은 메소드 getMessage()에 의해 반환 생성된 예외의 발생 생성된 예외 클래스는 필요한 경우 예외를 발생(throws) 가능 예외를 발생시키는 부분에서는 new MyException("내가 만든 예외") 메소드 선언에서 throws MyException을 기술 발생되는 예외가 여러 개라면 여러 예외 유형을 쉼표로 구분하여 기술
실습예제 : 새로운 예외의 처리 8-9 super(msg)를 구현하지 않았다면 getMessage()는 null이 반환 Section 2 체크 예외와 예외 생성 실습예제 : 새로운 예외의 처리 8-9 super(msg)를 구현하지 않았다면 getMessage()는 null이 반환
3. 스레드 개요
스레드(thread) 프로그램 내에서 실행되는 프로그램 제어 흐름 Section 3 스레드 개요 p314 스레드(thread) 프로그램 내에서 실행되는 프로그램 제어 흐름 프로그램의 내부의 실행 흐름인 스레드를 여러 개 만들 수 있다면 여러 일을 동시에 처리하는 듯한 느낌
다중 작업과 다중 스레드 다중 스레드(multi-thread) 프로그램 스레드 다중 작업(multi-tasking) Section 3 스레드 개요 다중 작업과 다중 스레드 다중 스레드(multi-thread) 프로그램 여러 개의 스레드를 이용하는 프로그램 스레드 가벼운 프로세스(light-process) 장점 하나의 프로그램 내부에서 실행되는 스레드는 프로세스보다 오버헤드가 적으면서 처리할 작업을 동시에 실행 다중 작업(multi-tasking) 여러 프로그램을 동시에 실행시켜 서로 이동하면서 작업이 가능 프로세스(process) 란? 실행되고 있는 프로그램 프로세스마다 고유한 저장공간을 사용하며 독립적으로 실행 단점 여러 프로세스를 실행하려면 프로세스 간의 정보 교환에 많은 시간이 소요
스레드를 처리하는 첫 번째 방법 클래스 Thread를 상속받아 구현하는 방법 Section 3 스레드 개요 스레드를 처리하는 첫 번째 방법 클래스 Thread를 상속받아 구현하는 방법 클래스 Thread를 상속받아 새로운 스레드를 정의 Thread의 메소드 run()에서 스레드 작업을 재정의 단점 다른 클래스를 상속받아야 하는 경우 이용할 수 없음 실행 방법 객체를 생성한 후 메소드 start()를 호출하여 스레드를 시작 메소드 start()의 호출에 의해 스레드에 재정의된 run()이 수행
Section 3 스레드 개요 스레드의 주요 메소드
Section 3 스레드 개요 실습예제 8-10 1에서 9까지 출력하는 스레드 SimpleThread.java
스레드 이름 지정과 반환 이름을 가진 스레드를 생성 지정된 스레드의 이름을 반환 스레드를 잠시 멈추게 하려면 Section 3 스레드 개요 스레드 이름 지정과 반환 이름을 가진 스레드를 생성 메소드 setName()을 사용 지정된 스레드의 이름을 반환 메소드 getName()을 사용 스레드를 잠시 멈추게 하려면 sleep(시간)을 이용, 지정된 인자는 천분의 1초
실습예제 8-11 Section 3 스레드 개요 class IncThread extends Thread { //생성자 구현 public IncThread(String name) { setName(name); //생성자 이름 지정 } public void run() { for (int i = 1; i < 5; i++) { try { sleep(50); System.out.print(getName() + ": " + i); System.out.println(", 활성화된 스레드 수: " + activeCount()); } catch (Exception e) { e.printStackTrace(); class DecThread extends Thread { for (int i = 5; i > 1; i--) public class ThreadTest { public static void main(String[] args) { IncThread inc = new IncThread("증가 스레드"); inc.start(); DecThread dec = new DecThread(); dec.start(); }
인터페이스 Runnable 인터페이스 Runnable 클래스 Thread 추상 메소드 run()으로 구성된 단순한 인터페이스 Section 3 스레드 개요 인터페이스 Runnable 인터페이스 Runnable 추상 메소드 run()으로 구성된 단순한 인터페이스 스레드로 실행하려는 클래스 인터페이스 Runnable을 상속받아 메소드 run()에서 스레드 기능을 구현 클래스 Thread Thread는 인터페이스 Runnable을 상속받아 메소드 run()을 재정의 package java.lang; public interface Runnable { public abstract void run(); } public class Thread implements Runnable { … }
스레드를 처리하는 두 번째 방법 인터페이스 Runnable의 run( ) 메소드 구현 스레드를 시작 스레드의 이름 반환 Section 3 스레드 개요 스레드를 처리하는 두 번째 방법 인터페이스 Runnable의 run( ) 메소드 구현 스레드의 이름 반환 Thread.currentThread().getName()을 호출 스레드를 시작 구현한 스레드의 객체 인자로 Thread를 생성 스레드 시작 메소드 start()를 호출
Section 3 스레드 개요 실습예제 8-12
4. 스레드 상태와 우선순위
스레드 상태 6가지 스레드의 상태 클래스 Thread 내부에 enum State으로 선언 Section 4 스레드 상태와 우선순위 p322 스레드 상태 6가지 스레드의 상태 클래스 Thread 내부에 enum State으로 선언 스레드는 객체가 생성되면 NEW라는 상태 start()가 호출되면 RUNNABLE 상태로 이동
스레드 상태 전이 NEW : 스레드는 객체가 생성되면 이동되는 상태 RUNNABLE : 메소드 start()가 호출되면 이동 Section 4 스레드 상태와 우선순위 스레드 상태 전이 NEW : 스레드는 객체가 생성되면 이동되는 상태 RUNNABLE : 메소드 start()가 호출되면 이동 TERMINATED : 스레드가 완전히 종료된 상태 더 이상 NEW 또는 RUNNABLE 등의 다른 상태로 전이가 불가능한 상태 BLOCKED, WAITING, TIMED_WEIGHTING
스레드 우선순위 setPriority() getPriority() 스레드의 우선순위 스레드의 우선순위 지정 현재 값을 반환 Section 4 스레드 상태와 우선순위 스레드 우선순위 setPriority() 스레드의 우선순위 지정 getPriority() 현재 값을 반환 스레드의 우선순위 1에서 10까지 지정 가능
실습예제 8-13 스레드 우선순위는 특별히 지정하지 않으면 상수 NORM_PRIORITY의 값인 5 Section 4 스레드 상태와 우선순위 실습예제 8-13 스레드 우선순위는 특별히 지정하지 않으면 상수 NORM_PRIORITY의 값인 5 public class ThreadState implements Runnable { public void run() { Thread th = new Thread(new ThreadState()); for (int i = 1; i < 10; i++) { System.out.println("기본 우선순위: " + th.getPriority()); try { Thread.sleep(40); //우선순위 지정 System.out.print(Thread.currentThread().getState() + ", "); th.setPriority(Thread.NORM_PRIORITY+2); System.out.println("우선순위 변경: " + th.getPriority()); System.out.println(Thread.currentThread().getName() + ": " + i); System.out.println("1 단계: " + th.getState()); th.start(); System.out.println("2 단계: " + th.getState()); } catch (InterruptedException e) { Thread.sleep(130); System.err.pprintln("InterruptedException이 발생되어 스레드를 종료합니다. "); th.interrupt(); System.out.println("3 단계: " + th.getState()); Thread.sleep(1000); return; System.out.println("4 단계: " + th.getState()); } catch (Exception e) { e.printStackTrace(); } public static void main(String[] args) throws InterruptedException { System.out.println("스레드의 모든 상태: 6 가지"); for (Thread.State c : Thread.State.values()) System.out.print(c + " "); System.out.println('\n');
5. 스레드 동기화
다중 스레드의 문제 다중 스레드 상에서 공유 자원의 처리 문제 예상하지 못한 문제 발생 가능성 존재 Section 5 스레드 동기화 p326 다중 스레드의 문제 다중 스레드 상에서 공유 자원의 처리 문제 예상하지 못한 문제 발생 가능성 존재 스레드 A가 공유 자료를 처리하는 도중에 다른 스레드 B가 그 자료를 처리한다면 다중 스레드에서 수행해야 할 작업이 하나의 단위로 처리되지 않아서 발생할 수 있는 문제
다중 스레드 구현 은행 계좌 모의 구현 인터페이스 Runnable을 상속받는 스레드 Section 5 스레드 동기화 다중 스레드 구현 은행 계좌 모의 구현 신청한 금액을 인출하는 메소드 withdraw() 신청한 금액을 입금하는 메소드 deposit() 인터페이스 Runnable을 상속받는 스레드 클래스 SyncTest는 은행계좌 객체 act 하나를 사용하여 20000원 이하의 금액에 대하여 입금(deposit)과 출금(withdraw)을 반복 처리 withdraw()는 어떠한 경우에도 잔고가 음수가 발생하지 않도록 의도 만일 계좌의 잔고가 0원 미만이면 계좌에 문제가 발생한 것이므로 스레드를 종료 public class SyncTest implements Runnable { BankAccount act = new BankAccount(); public void run() { while (true) { int amount = new Random().nextInt(10000); act.deposit(amount); act.withdraw(amount*2); if (act.balance < 0) { System.out.printf("[%s] ", Thread.currentThread().getName()); System.out.println("잔고: " + act.balance + " => 오류 종료"); return; } …
다중 스레드의 임계 영역 임계영역(critical section) 임계영역의 실례 다중 스레드에서 하나의 스레드가 배타적(exclusive)으로 공유 자원을 독점하도록 해야 하는 부분 임계영역의 실례 하나의 스레드가 은행계좌의 인출 메소드 withdraw() 기능을 수행 중간에 다른 스레드가 들어와 일을 할 수 없도록 잠금(lock) 장치 필요 인출 기능을 모두 수행했다면 다른 스레드가 일을 하도록 잠금 장치를 해지(unlock) 해야 할 것 즉 다음 소스에서 1, 2, 3 세 개의 문장은 독점적인 실행 필요
동기화 스레드 동기화(thread synchronization) 동기화 방법 크게 2가지 Section 5 스레드 동기화 동기화 스레드 동기화(thread synchronization) 다중 스레드에서 임계영역의 독점적 처리 해결 방법 동기화 방법 크게 2가지 메소드의 동기화 메소드의 동기화는 메소드의 지정자인 키워드 synchronized를 기술 블록의 동기화 블록의 동기화는 synchronized (Object) {…} 여기서 Object는 잠금 장치를 수행하는 객체
은행계좌의 동기화 문제 임계 영역에서 자원의 공유로 인한 문제 두 스레드 Thread-0와 Thread-1 Section 5 스레드 동기화 은행계좌의 동기화 문제 임계 영역에서 자원의 공유로 인한 문제 두 스레드 Thread-0와 Thread-1 동기화가 구현되지 않은 메소드 withdraw()에서 발생하는 문제 두 스레드가 각각 인출을 수행하므로 잔고가 음수가 되는 문제가 발생
실습예제 8-15, 동기화 처리 Section 5 스레드 동기화 package synchronize; amount = amount % 10 * 1000; System.out.printf("[%s] 금액=%d %n", Thread.currentThread().getName(), amount); import java.util.Random; act.deposit(amount); class BankAccount { act.withdraw(amount * 2); int balance = 0; if (act.balance < 0) { System.out.printf("[%s] ", Thread.currentThread().getName()); //메소드 전체의 동기화 처리 public synchronized void withdraw(int money) { System.out.println("잔고: " + act.balance + " => 오류 종료"); if (money > 0 && balance >= money) { balance -= money; return; System.out.printf("%d 인출하여 현재잔고 %d입니다. %n", money, balance); } else if (balance < money) System.out.println("잔고가 부족하여 인출할 수 없습니다."); public static void main(String[] args) { } Runnable r = new SyncTest(); new Thread(r).start(); //블록 동기화로 메소드 전체의 동기화 처리 public void deposit(int money) { synchronized (this) { if (money > 0) { balance += money; System.out.printf("%d 입금하여 현재잔고 %d입니다. %n", money, balance); public class SyncTest implements Runnable { BankAccount act = new BankAccount(); public void run() { //while (true) { for (int i = 0; i < 3; i++) { int amount = new Random().nextInt(10000);
메소드 wait( )와 notify( ), notifyAll( )의 이해 Section 5 스레드 동기화 메소드 wait( )와 notify( ), notifyAll( )의 이해 Object의 메소드 wait()와 notify(), notifyAll()을 사용 스레드 동기화에서 동기화 효율을 높이는 방안 메소드 wait() 동기화 블록에서 객체의 특정한 작업을 위해 처리 중인 스레드를 WAITING 또는 TIMED_WAITING 상태로 이동 notify() 또는 notifyAll() 다시 스레드 상태를 RUNNABLE로 이동시켜 스레드 작업을 다시 수행 시킴 특징 Object 클래스의 메소드이므로 모든 객체에서 사용 가능 키워드 synchronized를 사용하는 동기화 내부에서만 사용 가능
메소드 wait( )와 notify( ), notifyAll( )의 사용 Section 5 스레드 동기화 메소드 wait( )와 notify( ), notifyAll( )의 사용 메소드 wait() notify() 또는 notifyAll()
실습예제 8-18 Section 5 스레드 동기화 package wait; public synchronized void deposit(int money) { import java.util.Random; System.out.println("입금 금액이 잘못됐습니다."); class BankAccount { int balance = 0; balance += money; int diff = 0; public synchronized void withdraw(int money) { System.out.printf("%16s", "notify() 호출: "); if (money < 0) { System.out.printf("계좌입금금액=%6d, balance=%6d %n", money, balance); System.out.println("인출 금액이 잘못됐습니다."); return; // notify(); } notifyAll(); int count = 0; while (balance < money) { System.out.printf("[%s] ", Thread.currentThread().getName()); public class SyncTest implements Runnable { // 지속적으로 잔금이 부족하여 메소드 종료 BankAccount act = new BankAccount(); if (++count > 3) { System.out.println("잔액이 부족하여 출금처리 못하고 종료합니다."); public void run() { for (int i = 1; i < 3; i++) { int amount = (int) (new Random().nextDouble() * 5 + 1) * 10000; act.deposit(amount); System.out.printf("%16s", "wait(1000) 호출: "); amount = (int) (new Random().nextDouble() * 5 + 1) * 10000; System.out.printf("인출요구금액=%6d, balance=%6d %n", money, balance); act.withdraw(amount); try { // wait(); wait(1000); } catch (InterruptedException e) { public static void main(String[] args) { System.err.println(e); Runnable r = new SyncTest(); new Thread(r).start(); balance -= money; System.out.printf(" %12s 정상인출금액=%6d, balance=%6d %n", "정상 출금처리:", money, balance);