728x90
프로세스와 쓰레드
- 프로세스 : 실행중인 프로그램. 자원(resource)과 쓰레드로 구성
- 쓰레드 : 프로세스 내에서 실제 작업을 수행. 모든 프로세스는 최소한 하나의 쓰레드를 가지고 있음
- 프로세스 ≒ 공장, 쓰레드 ≒ 일꾼
- 싱글 쓰레드 프로세스 : 자원 + 1개의 쓰레드
멀티 쓰레드 프로세스 : 자원 + n개의 쓰레드 - 하나의 프로세스를 생성하는 것보다 하나의 새로운 쓰레드를 생성하는 것이 더 적은 비용이 듬
CGI(싱글 쓰레드) → 자바 서블릿(멀티 쓰레드 지원)
멀티쓰레드의 장,단점
- 장점
시스템 자원을 보다 효율적으로 사용할 수 있음
사용자에 대한 응답성(responseness)이 향상됨
작업이 분리되어 코드가 간결해짐 - 단점 → "공유"
동기화(synchronization)에 주의해야함
교착상태(dead-lock)가 발생하지 않도록 주의해야함. 기아 현상 발생
각 쓰레드가 효율적으로 고르게 실행될 수 있게 해야함
쓰레드의 구현과 실행
구현에는 2가지 방법 Thread 클래스를 상속, Runnable 인터페이스를 구현
- Thread클래스를 상속
class MyThread extends Thread{ //자바는 단일 상속만 가능
public void run(){ //Thead 클래스의 run()을 오버라이딩
/* 작업 내용 */
}
}
//쓰레드 생성과 실행
MyThread t = new MyThread();
t.start();
- Runnable 인터페이스를 구현 → 더 나음(단일 상속만 가능하기 때문에)
class MyThread2 implements Runnable{
public void run(){ //Runnable 인터페이스의 추상메소드 run()을 구현
/* 작업 내용 */
}
}
//쓰레드 생성과 실행
Runnable r = new MyThread2();
Thread t = new Thread(r); //Thread(Runnable r)
//위의 두 줄 Thread t = new Thread(new MyThread2());
t.start();
package chapter13;
//Thread 클래스를 상속해서 쓰레드를 구현
class Thread1_1 extends Thread{
public void run() {
for(int i = 0; i < 500;i++) {
System.out.print(0); //조상 Thread의 getName() 호출
}
}
}
//Runnable 인터페이스를 구현해서 쓰레드를 구현
class Thread1_2 implements Runnable{
public void run() {
for(int i = 0; i<500;i++) {
System.out.print(1); //Thread.currentThread() 현재 실행중인 Thread를 반환
}
}
}
public class 쓰레드2 {
public static void main(String[] args) {
Thread1_1 t1 = new Thread1_1();
t1.start();
Runnable r = new Thread1_2();
Thread t2 = new Thread(r);
t2.start();
}
}
실행할 때마다 다른 결과가 번갈아가면서 나타남
쓰레드의 실행 - start()
- 쓰레드를 생성한 후에 start()를 호출해야 쓰레드가 작업을 시작함
- 쓰레드를 먼저 실행했다고 먼저 실행되진 않음. 먼저 실행될 확률이 높음.
쓰레드를 실행했을 때 실행 가능한 상태가 되는 것, 바로 실행되지 않음
실행 순서는 OS의 스케줄러가 결정
Thread t1 = new Thread(); //쓰레드 t1을 생성
Thread t2 = new Thread(); //쓰레드 t2를 생성
t1.start(); //쓰레드 t1을 실행
t2.start(); //쓰레드 t2를 실행
//t1을 먼저 실행했기 때문에 t1이 먼저 실행될 '확률이 높음'. 무조건 t1이 실행되진 않음 → 쓰레드를 start() 했을 시 실행 가능한 상태가 되는 것이지 바로 실행되지 않음 → OS의 스케줄러가 실행 순서 결정
start()와 run()
class Test{
public static void main(String args[]){
MyThread t1 = new MyThread(); //1.생성
t1.start(); //2.실행
//t2.run(); //X
}
}
class MyThread extends Thread{
public void run(){
...
}
}
- run을 구현하였지만 실행은 start()를 사용
start()를 하면 새로운 호출 스택 생성
→ 새로운 호출 스택에 run()이 올라감
→ start() 종료
→ main()과 run() 서로 독립적으로 작업 수행
main 쓰레드
- main 메소드의 코드를 수행하는 쓰레드
- 쓰레드는 '사용자 쓰레드'와 '데몬 쓰레드' 두 종류가 있음
사용자 쓰레드 : main쓰레드
데몬 쓰레드 : 보조 쓰레드 - 실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료됨
package chapter13;
class Thread3_1 extends Thread{
public void run() {
for(int i = 0; i<300;i++) {
System.out.print(new String("-"));
}
}
}
class Thread3_2 extends Thread{
public void run() {
for(int i = 0; i<300;i++) {
System.out.print(new String("|"));
}
}
}
public class 쓰레드3_1 {
public static void main(String[] args) {
Thread3_1 th1 = new Thread3_1();
Thread3_2 th2 = new Thread3_2();
th1.start();
th2.start();
long startTime = System.currentTimeMillis();
try {
th1.join(); //join() : main 쓰레드가 th1 작업이 끝날때까지 기다림
th2.join();
} catch (InterruptedException e) {
}
System.out.println("소요시간 : " + (System.currentTimeMillis()-startTime));
}
}
싱글쓰레드와 멀티쓰레드
- 싱글쓰레드
작업 A가 끝난 다음에 작업 B가 끝남
package chapter13;
public class 쓰레드3_2 {
public static void main(String[] args) {
//싱글쓰레드
long startTime = System.currentTimeMillis();
for(int i =0; i<300;i++) {
System.out.print("-");
}
System.out.println("A작업 소요시간 : " + Math.abs(startTime-System.currentTimeMillis()));
for(int i =0; i<300;i++) {
System.out.print("|");
}
System.out.println("B 작업 소요시간 : " + Math.abs(startTime-System.currentTimeMillis()));
}
}
- 멀티쓰레드
package chapter13;
class MyThread3a extends Thread{
public void run() {
for(int i =0; i<300;i++) {
System.out.print("-");
}
}
}
class MyThread3b extends Thread{
public void run() {
for(int i =0; i<300;i++) {
System.out.print("|");
}
}
}
public class 쓰레드3_2 {
public static void main(String[] args) {
//멀티쓰레드
MyThread3a th1 = new MyThread3a();
MyThread3b th2 = new MyThread3b();
th1.start();
th2.start();
}
}
쓰레드의 I/O 블락킹
- 싱글쓰레드 사용자로부터 입력을 기다리는 구간 아무 작업도 하지 않음
- 멀티 쓰레드 사용자로부터 입력을 기다리는 구간 다른 작업을 수행함
package chapter13;
import javax.swing.JOptionPane;
public class 쓰레드3_3 {
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) {
}
}
//멀티
Thread3_3a th1 = new Thread3_3a();
th1.start();
String input1 = JOptionPane.showInputDialog("아무값이나 입력하세요.");
System.out.println("입력한 값은 " + input1);
}
class Thread3_3a extends Thread{
public void run() {
for(int i = 0;i>0;i--) {
System.out.println(i);
try {
sleep(1000);
} catch (Exception e) {
}
}
}
}
}
※import문에서 에러가 발생할 때 src 안에 module-info.java 파일이 생성되어있는지 확인 후 삭제
쓰레드의 우선순위(priority of thread)
- 작업의 중요도에 따라 쓰레드의 우선순위를 다르게 하여 특정 쓰레드가 더 많은 작업시간을 갖게 할 수 있음
void setPriority(int newPriority) //쓰레드의 우선순위를 지정한 값으로 변경
int getPriority() // 쓰레드의 우선순위를 반환
public static final int MAX_PRIORITY = 10; //최대 우선순위
public static final int MIN_PRIORITY = 1; //최소 우선순위
public static final int NORM_PRIORITY = 5; //보통 우선순위(기본적)
- 우선 순위를 지정해준다고 반드시 우선 순위대로 작동되는 것은 아님. OS 스케줄러에 따라서 작업이 진행됨..
즉 우선순위를 지정하는 것은 희망사항일뿐 결과는 우선 순위대로 작동되지 않음을 인지하자
불확실성 ↑
package chapter13;
class ThreadEx4_1a extends Thread{
public void run() {
for(int i = 0; i<300;i++) {
System.out.print("-");
for(int x = 0; x<10000000;x++); //시간지연용 for문
}
}
}
class ThreadEx4_1b extends Thread{
public void run() {
for(int i = 0; i<300;i++) {
System.out.print("|");
for(int x = 0; x<10000000;x++);
}
}
}
public class 쓰레드4_1 {
public static void main(String[] args) {
ThreadEx4_1a th1 = new ThreadEx4_1a();
ThreadEx4_1b th2 = new ThreadEx4_1b();
th2.setPriority(7);
System.out.println("th1의 우선순위(-) " + th1.getPriority());
System.out.println("th2의 우선순위(|) " + th2.getPriority());
th1.start();
th2.start();
}
}
쓰레드 그룹
- 서로 관련된 쓰레드를 그룹으로 묶어서 다루기 위한 것
- 모든 쓰레드는 반드시 하나의 쓰레드 그룹에 포함되어 있어야함
- 쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 '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 uncaughtException(Thread t, Throwable e) //처리되지 않은 예외에 의해 쓰레드 그룹의 쓰레드가 실행이 종료되었을 때, JVM에 의해 이 메소드가 자동적으로 호출
- 쓰레드 그룹의 메소드
ThreadGroup(String name) | 지정된 이름의 새로운 쓰레드 그룹을 생성 |
ThreadGroup(ThreadGroup parent, String name) | 지정된 쓰레드 그룹에 포함되어 새로운 쓰레드 그룹을 생성 |
int acftiveCount() | 쓰레드 그룹에 포함된 활성상태에 있는 쓰레드의 수를 반환 |
int activeGroupCount() | 쓰레드 그룹에 포함된 활성상태에 있는 쓰레드 그룹의 수를 반환 |
void checkAccess() | 현재 실행중인 쓰레드가 쓰레드 그룹을 변경할 권한이 있는지 체크 |
void destroy() | 쓰레드 그룹과 하위 쓰레드 그룹까지 모두 삭제. 단, 비어있어야 삭제 가능 |
int enumerate(Thread[] list) int enumerate(Thread[] list, boolean recurse) int enumerate(ThreadGroup[] list) int enumerate(ThreadGroup[] list, boolean recurse) |
쓰레드 그룹에 속한 쓰레드 또는 하위 쓰레드 그룹의 목록을 지정된 배열에 담고 그 개수를 반환. 두번째 매개변수인 recurse의 값을 true로 하면 쓰레드 그룹에 속한 하위쓰레드 그룹에 쓰레드 또는 쓰레드 그룹까지 배열에 담음 |
int getMaxPriority() | 쓰레드 그룹의 최대우선순위를 반환 |
String getName() | 쓰레드 그룹의 이름 반환 |
... | ... |
데몬 쓰레드(daemon thread)
- 일반 쓰레드(non-daemon thread)의 작업을 돋는 보조적인 역할을 수행
- 일반 쓰레드가 모두 종료되면 자동적으로 종료됨
- 가비지 컬렉터, 자동저장, 화면 자동갱신 등에 사용됨
- 무한루프와 조건문을 이용해서 실행 후 대기하다가 특정조건이 만족되면 작업을 수행하고 다시 대기하도록 작성함
public void run() {
while(true) {
try {
Thread.sleep(3*1000); //3초마다
} catch(InterruptedException e) {}
if(autoSave) {
autoSave();
}
}
}
boolean isDaemon() //쓰레드가 데몬 쓰레드인지 확인. 데몬 쓰레드이면 true를 반환
void setDaemon(boolean on) //쓰레드를 데몬 쓰레드로 또는 사용자 쓰레드로 변경. 매개변수 on을 true로 지정하면 데몬쓰레드가 됨
//※setDaemon(boolean on)은 반드시 start()를 호출하기 전에 실행되어야 함. 그렇지 않으면 IllegalThreadStateException이 발생
package chapter13;
public class 쓰레드5_1 implements Runnable{
static boolean autoSave = false;
public static void main(String[] args) {
Thread t = new Thread(new 쓰레드5_1()); //Runnnable 인터페이스 구현
t.setDaemon(true); //이 부분이 없을 시 종료가 되지 않음
t.start();
for(int i =1; i<=10; i++) {
try {
Thread.sleep(1000);
} catch (Exception e) {
}
System.out.println(i);
if(i==5) {
autoSave = true;
}
}
System.out.println("프로그램을 종료합니다.");
}
@Override
public void run() {
while(true) {
try {
Thread.sleep(3000); //3초마다
} catch (InterruptedException e) {
}
if(autoSave) {
autoSave();
}
}
}
public void autoSave() {
System.out.println("작업 파일이 자동 저장되었습니다.");
}
}
쓰레드의 상태
상태 | 설명 |
NEW | 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태 |
RUNNABLE | 실행 중 또는 실행 가능한 상태 |
BLOCKED | 등기화블럭에 의해서 일시정지된 상태(lock이 풀릴 때까지 기다리는 상태) |
WAITING, TIMED_WAITTING |
쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrunnable) 일시정지 상태. TIMED_WAITTING은 일시정지시간이 지정된 경우를 의미 |
TERMINATED | 쓰레드의 작업이 종료된 상태 |
생성 → 실행대기 → 실행 → 소멸
일시정지
쓰레드의 실행제어
- 쓰레드의 실행을 제어할 수 있는 메소드가 제공. 이들을 활용해서 보다 효율적인 프로그램을 작성할 수 있음
메소드 | 설명 |
static void sleep(long millis) static void sleep(long millis, int nanos) |
지정된 시간(천분의 일초 단위)동안 쓰레드를 일시정지(잠들게)시킴. 지정된 시간이 지나고 나면, 자동적으로 다시 실행대기 상태가 됨. |
void join() void join(long millis) void join(long millis, int nanos) |
지정된 시간동안 쓰레드가 실행되도록 함. 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속함 |
void interrupt() | sleep()이나 join()에 의해 일시정지상태인 쓰레드를 깨워서 실행대기상태로 만듦. 해당 쓰레드에서는 Interrupted Exception이 발생함으로써 일시정지 상태를 벗어나게 됨 |
void stop() | 쓰레드를 즉시 종료시킴 |
void suspend() | 쓰레드를 일시정지시킴. resume()을 호출하면 다시 실행대기상태가 됨 |
void resume() | suspend()에 의해 일시정지상태에 있는 쓰레드를 실행대기상태로 만듦 |
static void yield() | 실행 중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보(yield)하고 자신은 실행대기상태가 됨 |
※static 쓰레드 자기자신에게만 호출 가능
728x90