일꾼이 n명인 것
장점
시스템 자원을 효율적으로 사용할 수 있다
사용자에 대한 응답성이 향상된다
작업이 분리되어 코드가 간결해진다
단점
동기화에 주의해야 한다
교착상태가 발생하지 않도록 주의해야 한다
각 스레드가 효율적으로 고르게 실행될 수 있게 한다
실행되지 못하는 상태인 기아상태가 발생하지 않도록 주의해야 한다
여러 스레드가 동시에 수행되는 프로그래밍, 여러 작업이 동시에 실행되는 효과
스레드는 각각 자신만의 작업 공간을 갖는다
각 스레드 사이에서 공유하는 자원이 있을 수 있다
여러 스레드가 자원을 공유하여 작업이 수행되는 경우, 서로 자원을 차지하려는 race condition이 발생할 수 있다
자원을 차지하려는 경쟁이 발생하는 부분을 critical section(임계영역)이라고 한다
그에 대한 동기화를 구현하지 않으면 오류가 발생할 수 있다
순차적 수행, 한 스레드가 사용할 때 다른 스레드가 사용할 수 없도록 락을 거는 것
스레드의 I/O 블락킹
- 싱글 스레드 프로세스에서는 스레드 실행 중에 사용자의 입력을 받는 구간이 있다면 스레드가 아무 일도 하지 않고 입력을 기다린다
- 멀티 스레드에서는 입력을 기다린다면 다른 스레드의 작업이 실행된다
class MyThread extends Thread{
public void run() {
int i;
for(i = 0; i<200; i++) {
System.out.print(i + "\t");
}
}
}
public class ThreadTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread());
MyThread th1 = new MyThread();
th1.start();
MyThread th2 = new MyThread();
th2.start();
}
}
public static void main(String[] args) {
//스레드 1 : 메인 스레드는 스레드 두개 생성하고 스타트하면 끝
System.out.println(Thread.currentThread() + " start");
MyThread runnable = new MyThread();
Thread th1 = new Thread(runnable); // runnable객체를 입력받을 수 있다
Thread th2 = new Thread(runnable);
th1.start();
th2.start();
System.out.println(Thread.currentThread() + " end");
}
- 간단하게 돌리는 방법
Runnable run = new Runnable() { //바로 익명 객체를 불러올 수 있다 - 메서드 구현해야함 @Override public void run() { System.out.println("run"); } }; run.run();
Runnable 인터페이스를 구현하여 쓰는 것이 코드가 길어지긴 하지만 다른 클래스를 상속받아야 할 경우가 있을 수 있기 때문에 주로 사용된다
프로그램은 실행중인 사용자 스레드가 하나도 없을 때 종료된다
여러 스레드를 start하면 실행된 순서대로 스레드가 실행되지 않을 수 있다
실행 순서는 OS의 스케쥴러가 결정한다
스케쥴러에게 사용자가 원하는 실행 순서를 제안하는 방법으로 우선순위를 둔다
PriorityThread pt1 = new PriorityThread();
PriorityThread pt2 = new PriorityThread();
PriorityThread pt3 = new PriorityThread();
pt1.setPriority(Thread.MIN_PRIORITY);
pt2.setPriority(Thread.NORM_PRIORITY);
pt3.setPriority(Thread.MAX_PRIORITY);
pt1.start();
pt2.start();
pt3.start();
원하는 스레드에 우선순위를 부여할 수 있다
suspend, resume, stop deprecated 됨
일시정지 상태와 정지 상태를 boolean으로 생성하고 반복문에 적용하는 방법으로 구현
public class JoinTest extends Thread{
int start;
int end;
int total;
public JoinTest(int start, int end) {
this.start = start;
this.end = end;
}
public void run() {
int i;
for (i = start; i <= end; i++) {
total += i;
}
}
public static void main(String[] args) {
System.out.println(Thread.currentThread() + "start");
JoinTest jt1 = new JoinTest(1, 50);
JoinTest jt2 = new JoinTest(51, 100);
jt1.start();
jt2.start();
//원래는 메인이 jt1,2가 종료되기 전에 종료되어서 jt1,2가 완전히 실행되지 않고 종료된다
//메인에서 join을 사용하면 메인은 수행을 안한다 jt1, 2가 종료될 때까지 기다린다
try {
jt1.join();
jt2.join();
} catch (InterruptedException e) { //jt1,2의 수행이 영원히 종료되지 않을 경우 메인으로 돌아오기 위해
throw new RuntimeException(e);
}
int lastTotal = jt1.total + jt2.total;
System.out.println("jt1.total = " + jt1.total);
System.out.println("jt2.total = " + jt2.total);
System.out.println("lastTotal = " + lastTotal);
System.out.println(Thread.currentThread() + "end");
}
}
public class TerminateThread extends Thread{
private boolean flag = false;
int i;
public TerminateThread(String name){
super(name);
}
public void run(){
while(!flag){ //while(true)로 두지 말고 플래그를 이용해서 수정할 수 있다
try {
sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println( getName() + " end" );
}
public void setFlag(boolean flag){
this.flag = flag;
}
public static void main(String[] args) throws IOException {
TerminateThread threadA = new TerminateThread("A");
TerminateThread threadB = new TerminateThread("B");
TerminateThread threadC = new TerminateThread("C");
threadA.start();
threadB.start();
threadC.start();
int in;
while(true){
in = System.in.read();
if ( in == 'A'){
threadA.setFlag(true); //a 스레드 종료
}else if(in == 'B'){
threadB.setFlag(true); //b 스레드 종료
}else if( in == 'C'){
threadC.setFlag(true); //c 스레드 종료
}else if( in == 'M'){
threadA.setFlag(true); //모든 스레드 종료
threadB.setFlag(true);
threadC.setFlag(true);
break;
}else{
System.out.println("type again");
}
}
System.out.println("main end");
}
}
멀티 스레드 프로세스에서는 다른 스레드의 작업에 영향을 미칠 수 있다
교착 현상
진행중인 작업이 다른 쓰레드에게 간섭받지 않게 하려면 동기화가 필요하다
간섭받지 않아야 하는 문장을 임계 영역으로 설정한다
임계 영역은 락(자물쇠)을 얻은 단 하나의 스레드만 출입 가능
임계 영역은 최소화 하는 것이 좋음
2번 방법으로 하는 것이 좋음
동기화를 많이 사용하면 효율이 떨어진다.
효율을 높이는 방법으로 wait()과 notify()가 사용된다
> 음식이 없을 때는 wait()으로 손님이 락을 풀고 기다리도록 함
> 손님이 음식을 먹으면 요리사에게 notify()로 통보
> 음식이 추가되면 손님에게 알려 다시 락을 획득하도록 함
결과