쓰레드 프로그래밍 2001. 11. 22 Youngnam Kim
목 차 쓰레드 기초 쓰레드 응용 프로세스와 런 타임
쓰레드 기초
8.1.1 쓰레드란 프로세스 : 실행중인 프로그램 실행쓰레드: 명령어들이 순차적으로 실행되는 프로세스 프로세스 : 실행중인 프로그램 실행쓰레드: 명령어들이 순차적으로 실행되는 프로세스 쓰레드 : 실행될 명령어들의 연속 - 단일 쓰레드 프로그램: 순차적으로 실행되는 명령어들로 이루어진 프로그램 - 다중 쓰레드 프로그램: 여러 개의 함수들이 서로 독립적으로 수행되는 경우 쓰레드 사용 방법 - Thread 클래스로부터 상속 - Runnable 인터페이스를 implements * run() 함수: Thread가 할 작업내용 기술, start()함수 호출시 자동 수행
8.1.1 쓰레드란(Cont.)/ Thread 상태도 Blocked suspend() sleep() wait() suspend() sleep() wait() stop() stop() Dead Newborn stop() resume() notify() start() stop() Runnable Running yield()
8.1.1 쓰레드란(Cont.)/ Thread 상태도설명 start()메소드 호출 쓰레드생성-> newborn---------- runnable -> running Runnable 상태: 쓰레드가 CPU의 dispatch queue에 등록된 상태. Running 상태: 쓰레드가 CPU를 차지하고 실행 중인 상태 - CPU를 다른 쓰레드에 넘겨주기 위한 메소드들 - 상태전이 차이점 runnable상태로의 전이 방법 suspend() blocked Blocked상태로 전이되어 cpu를 다른 쓰레드에게 넘겨주는 형태 resume() sleep() 일정시간후 자동적으로 wait() notify() yield() runnable 우선순위가 높다면 다시 cpu잡을 수 있다.
8.1.2 Thread 상속 실행결과 <Thread 클래스로부터 상속받아 쓰레드 생성예제> public class ExtendedThreadTest { static public void main(String args[]) { ExtendedThread thr_a, thr_b; thr_a = new ExtendedThread(); thr_b = new ExtendedThread(); thr_a.start(); thr_b.start(); } class ExtendedThread extends Thread { public void run() { System.out.println("Hello, I ("+getName()+") am working."); 실행결과 F:\>java ExtendedThreadTest Hello, I (Thread-0) am working. Hello, I (Thread-1) am working.
8.1.3 Runnable 인터페이스 상속 실행결과 <Runnable 인터페이스 상속받아 쓰레드 생성예제> public class RunnableThread implements Runnable { private int a; Thread thr_a; public RunnableThread() { a = 10; } public void doing() { thr_a = new Thread(this); thr_a.start(); } public void run() { System.out.println("Hi! I (" + Thread.currentThread().getName() + ") am working."); System.out.println("I can access private var 'a' = " + a); } static public void main(String args[]) { RunnableThread rt = new RunnableThread(); rt.doing(); } } 실행결과 F:\>java RunnableThread Hi! I (Thread-0) am working. I can access private var 'a' = 10 run() 메소드정의 쓰레드 생성시Runnable 인터페이스 implements하는 객체를 매개변수로 전달
Thread 클래스 상속 VS. Runnable 인터페이스 상속 클래스와 무관하게 독립된 작업 or 여러 개의 쓰레드 필요한 경우 Runnable 인터페이스 상속: 클래스의 특성을 그대로 유지하면서 쓰레드를 하나 더 생성할 필요가 있는 경우 Runnable <Thread 클래스 상속> <Runnable을 implements> Thread private int a; private int a(){ …… } private int a; private int a(){ …… } NOT extends implements NOT OK 쓰레드 쓰레드 OK
8.1.4 쓰레드 종료 run() 함수 마침 Dead 상태 stop() 함수 호출 Dead상태 : start() 함수로 다시 실행 불가. stop(), resume(), suspend() : JDK1.2 이후 버전에서는 이러한 메소드들은 deadlock 유발가능성이 있어 deprecated 경고를 유발하므로 사용을 자제하자.
8.1.4 쓰레드 종료(Cont.) public class StopThreadTest { static public void main(String s[]) { StopThread thr_a = new StopThread(); System.out.println("Starting the thread..."); thr_a.start(); try { Thread.sleep(2000); } catch (InterruptedException e) {} System.out.println("Stopping the thread..."); thr_a.stop(); System.out.println("Trying to start the thread again..."); } class StopThread extends Thread { public void run() { while (true) { try { sleep(500); } catch (InterruptedException e) {} System.out.println("Hello, I (" + getName() + ") slept, now I am working."); } } } F:\>java StopThreadTest Starting the thread... Hello, I (Thread-0) slept, now I am working. Stopping the thread... Trying to start the thread again...
8.1.5 ThreadDeath 예외 stop() 메소드 호출 -> ThreadDeath 예외 발생 -> 쓰레드가 갖고 있던 lock, monitor 풀어줌 -> ThreadDeath 예외 다시 throw public class CatchDeathTest { static public void main(String args[]) { KilledThread thr_a = new KilledThread("마당쇠 쓰레드"); System.out.println("Starting the thread..."); thr_a.start(); try { Thread.sleep(2000); } catch (InterruptedException e) {} System.out.println("Stopping the thread..."); thr_a.stop(); }
8.1.5 ThreadDeath 예외(Cont.) F:\>java CatchDeathTest Starting the thread... 마당쇠 쓰레드: is running Stopping the thread... I (마당쇠 쓰레드) is being killed. class KilledThread extends Thread { KilledThread(String name) { super(name); } public void run() { try { while (true) { sleep(500); System.out.println(getName() + ": is running"); } } catch (ThreadDeath ouch) { System.out.println("I (" + getName() + ") is being killed."); throw(ouch); } catch (InterruptedException e) {}
8.1.6 suspend()/resume() sleep : 일정 시간동안 쓰레드 blocked suspend: 얼마동안 blocked되는지 모르는 경우 public class SuspResu { static public void main(String s[]) { SuspendedThread thr_a = new SuspendedThread("홍길동"); System.out.println("Starting the thread..."); thr_a.start(); try { Thread.sleep(2000); } catch (InterruptedException e) {} System.out.println("Suspending the thread..."); thr_a.suspend(); System.out.println("Resuming the thread..."); thr_a.resume(); }
8.1.6 suspend()/resume()(Cont.) F:\>java SuspResu Starting the thread... I (홍길동) am working. Suspending the thread... Resuming the thread... …. class SuspendedThread extends Thread { public SuspendedThread(String name) { super(name); } public void run() { while (true) { try { sleep(500); } catch (InterruptedException e) {} System.out.println("I ("+ getName()+") am working.");
8.1.7 스케줄링 쓰레드의 스케줄링: 가상머신에서 preemptive 방식 <교재 p.451-p.454 source 참조> 27 Thread.currentThread().setPriority(Thread.MAX_PRIORITY); => 우선순위값(1-10)중 우선순위 설정 MAX_PRIORITY: 10, MIN_PRIORITY: 1, NORM_PRIORITY: 5 default : NORM_PRIORITY F:\>java Priority Starting the threads... A: is running B: is running C: is running …
38 System.out.println("Setting Thread b to a higher priority..."); 39 thr_b.setPriority(thr_b.getPriority() + 2); Setting Thread b to a higher priority... B: is running … 45 System.out.println("Setting Thread b to a lower priority..."); 46 thr_b.setPriority(thr_b.getPriority() - 4); Setting Thread b to a lower priority... C: is running A: is running … 52 System.out.println("Setting Thread b to a equal priority..."); 53 thr_b.setPriority(thr_b.getPriority() + 2); Setting Thread b to a equal priority... … B: is running Stopping the threads... A: is running
8.1.8 데몬 쓰레드 데몬 쓰레드: 다른 쓰레드에 서비스 해주면서 다른 쓰레드가 모두 종료하 면 자신도 종료하는 쓰레드 데몬 쓰레드: 다른 쓰레드에 서비스 해주면서 다른 쓰레드가 모두 종료하 면 자신도 종료하는 쓰레드 쓰레드 생성후 setDaemon(true) 이용하여 만듦 public class DaemonThread { static public void main(String s[]) { MyThread6 thr_a = new MyThread6(); MyThread6 thr_b = new MyThread6(); System.out.println("Starting the threads..."); thr_a.start(); thr_b.setDaemon(true); thr_b.start(); try { Thread.sleep(2000); } catch (InterruptedException e) {} System.out.println("Stopping the normal thread..."); thr_a.stop(true); } }
class MyThread6 extends Thread { protected boolean stop; F:\>java DaemonThread Starting the threads... Thread-0: regular thread Thread-1: daemon thread Stopping the normal thread... class MyThread6 extends Thread { protected boolean stop; public void stop(boolean b) { stop = b; } public void run() { while (!stop) { try { sleep(500); } catch (InterruptedException e) {} if (isDaemon()) System.out.println(getName() + ": daemon thread"); else System.out.println(getName() + ": regular thread");
8.1.9 동기화 문제> a가 10일때, a = a + 1을 두개의 쓰레드가 수행시, A쓰레드가 a를 메모리에서 읽어 1증가도중,B쓰레드가 a를 메모리에서 레지스터로 로드시, 결과는 12가 되어야 하지만, 실질적인 결과는 11이됨. 해결> synchrohized를 사용하여 원하는 메소드에 lock을 걸므로써, 어떤 쓰레드가 그 메소드를 사용하는 동안에는 다른 쓰레드의 사용차단
public class Monitors implements Runnable { Critical CritObj; Monitors() { CritObj = new Critical(); Thread thr_a = new Thread(this, "홍길동"); Thread thr_b = new Thread(this, "허접"); thr_a.start(); thr_b.start(); } public void run() { CritObj.a(); CritObj.b(); public static void main(String args[]) { Monitors hong; hong = new Monitors();
public synchronized void a() { class Critical { public synchronized void a() { System.out.println(Thread.currentThread().getName() + " is in a()."); try { Thread.sleep((int)Math.round(Math.random() * 5000)); } catch (InterruptedException e) {} System.out.println(Thread.currentThread().getName() + " is leaving a()."); } public static synchronized void b() { System.out.println(Thread.currentThread().getName() + " is in b()."); try { " is leaving b()."); F:\>java Monitors 홍길동 is in a(). 홍길동 is leaving a(). 허접 is in a(). 홍길동 is in b(). 허접 is leaving a(). 홍길동 is leaving b(). 허접 is in b(). 허접 is leaving b().
8.1.9 동기화(Cont.) 일반락에 비교해 synchronized이용한 락은 재귀적 호출 허용. public class Reentrant { public static void main(String args[]) { TestClass Test = new TestClass(); Test.MyMethod(); } class TestClass { int times = 1; public synchronized void MyMethod() { int i = times++; System.out.println("MyMethod has started " + i + " time(s)"); while (times < 4) MyMethod(); System.out.println("MyMethod has exited " } F:\>java Reentrant MyMethod has started 1 time(s) MyMethod has started 2 time(s) MyMethod has started 3 time(s) MyMethod has exited 3 time(s) MyMethod has exited 2 time(s) MyMethod has exited 1 time(s)
8.1.10 쓰레드 그룹 쓰레드 그룹: 여러 쓰레드의 동시제어를 필요로 할때 사용 class MyThread7 extends Thread { MyThread7(ThreadGroup tg, String name) { super(tg, name); } public void run() { while (true) { try { sleep(500); } catch (InterruptedException e) {} System.out.println(getName() + ": is running");
Stopping the thread group... public class ThrGroup { static public void main(String s[]) { ThreadGroup MyGroup = new ThreadGroup("My Group"); MyThread7 thr_a = new MyThread7(MyGroup, "A"); MyThread7 thr_b = new MyThread7(MyGroup, "B"); MyThread7 thr_c = new MyThread7(MyGroup, "C"); System.out.println("Starting the threads..."); thr_a.start(); thr_b.start(); thr_c.start(); try { Thread.sleep(2000); } catch (InterruptedException e) {} System.out.println("Stopping the thread group..."); MyGroup.stop(); } F:\>java ThrGroup Starting the threads... A: is running B: is running C: is running Stopping the thread group...
쓰레드 응용
8.2.1 자바 쓰레드 자바 쓰레드 : 운영체제의 쓰레드로의 매핑에 의해 실제 쓰레드 작업수행. 운영체제마다 쓰레드 지원방식이 다르기 때문에 자바 쓰레드와 실제 운영체제의 쓰레드 매핑 다를 수 있다. 일종의 사용자 쓰레드 실제 운영체제의 쓰레드 (M-1, 1-1, M-N 관계) 사용자 쓰레드 : 사용자가 라이브러리를 이용해서 만든 쓰레드 커널 쓰레드: 실제 스케줄링이 일어나는 스케줄링 단위 M-1 매핑: 자바응용 프로그램 쓰레드 LWP 사용자 스페이스 네이티브 스케줄 단위(LWP) 커널 스페이스
1-1 매핑 M-N 매핑 자바응용 프로그램 쓰레드 LWP 사용자 스페이스 네이티브 커널 스페이스 자바응용 프로그램 사용자 스페이스 네이티브 커널 스페이스 자바응용 프로그램 M-N 매핑 솔라리스 쓰레드 라이브러리(사용자 스페이스) 네이티브 커널 스페이스
8.2.2 생산자와 소비자 생산자는 알파벳을 생성해서 Monitor라는 곳에 저장하고, 소비자는 저장된 알파벳 을 가져가는 프로그램 <교재 p.464-p.466 source 참조> 생성자 blocked: Monitor에 꽉자서 저장할 곳이 없는 경우. 소비자가 알파벳을 가져가며 생산자에게 알려주어 block풀게함 소비자 blocked: Monitor에 가져갈 알파벳이 없는 경우. 생산자는 알파벳 저장하며 bloked된 소비자에게 알려주어 block풀게함 생산자와 소비자가 동시에 Monitor 접근하여 작업 못하게 Monitor에 lock시킴. Monitor Producer add(char c) eat() Consumer ADGHCE <생산자와 소비자 관계>
F:\>java Monitor ADD --> Y REMOVE <-- Y ADD --> U REMOVE <-- U ADD --> C REMOVE <-- C Buffer is empty. REMOVE <-- T ADD --> T ADD --> J REMOVE <-- J Buffer is empty. REMOVE <-- V ADD --> V ADD --> D REMOVE <-- D ADD --> R REMOVE <-- R REMOVE <-- C ADD --> C REMOVE <-- D ADD --> D ADD --> A ADD --> Y ADD --> Q REMOVE <-- A ADD --> W ADD --> V REMOVE <-- Y REMOVE <-- Q REMOVE <-- W REMOVE <-- V
8.2.3 파일에서 문자열 찾기 <EzSearch의 생산자와 소비자 관계> ……….. ……… File File 8.2.3 파일에서 문자열 찾기 <EzSearch의 생산자와 소비자 관계> File File File Search IOC putMessage() Message message putMessage() ……….. ……… EzDisplay
8.2.3 파일에서 문자열 찾기(Cont.) <실행결과> EzSearch.java: 원하는 문자열을 파일로부터 찾아 프레임으로 보여줌 <교재 p.469-p.475 source 참조> <실행결과>
8.3 프로세스와 런 타임 (Runtime의 method) 8.3 프로세스와 런 타임 ※ 다른 명령어 실행하고자 할때 Process와 Runtime 클래스 이용 (Runtime의 method) ※ 자바 응용프로그램이 실행환경과 연결할 수 있는 class Process exec(String command) : 새로운 프로세스에서 명령어를 실행한다. Process exec(String[] cmdarray): 다른 프로세스에서 아규먼트를 가지는 명령어를 실행한다. Process exec(String[] cmdarray, String[] envp): 다른 프로세스에서 아규먼트를 가지는 명령어를 실행한다. 이때, envp를 이용해서 환경 변수를 설정한다. 환경변수는 name=value의 형태로 설정된다. Process exec(String[] cmdarray, String[] envp, File dir): 다른 프로세스에서 아규먼트를 가지는 명령어를 실행한다. 이때 envp를 이용해서 환경병수를 설정한다.
8.3 프로세스와 런타임 (Runtime의 method) (Cont.) Process exec(String cmd,String[] envp): 다른 프로세스에서 명령어를 실행한다. 이때 envp를 이용해서 환경 변수를 설정한다. Process exec(String command, String[] envp, File dir): 다른 프로세스에서 명령어를 실행한다. 이때 envp를 이용해서 환경변수를 설정한다. dir을 이용해서 현재 작업 디렉토리를 설정한다. void exit(int status): 현재 자바 가상 머신을 종료한다. long freeMemory(): 시스템의 사용 가능한 메모리를 리턴한다. void gc(): 가비지 콜렉터를 실행한다. static Runtime getRuntime(): Runtime을 리턴한다. long totalMemory():자바 가상 머신의 총 메모리 양을 리턴한다.
8.3 프로세스와 런타임 (Process클래스의 method) ※ 프로세스를 표현하기 위한 클래스로, Runtime의 exec() 메소드를 호출하는 경우에 생성. void destroy(): 서브 프로세스를 죽인다. int exitValue(): 서브 프로세스의 종료 상태를 알아본다. InputStream getErrorStream(): 서브 프로세스의 에러 스트림을 리턴한다. InputStream getInputStream(): 서브 프로세스의 입력 스트림을 리턴한다. OutputStream getOutputStream(): 서브 프로세스의 출력 스트림을 리턴한다.
8.2.3 프로세스와 런타임(Cont.) <실행결과> IDE.java: GUI 를 이용해서 자바 프로그램을 작성해서 파일에 저장 <교재 p.469-p.475 source 참조> <실행결과>