쓰레드(Thread)란?
실제 프로그램이 수행되는 작업의 최소단위이다.
하나의 프로세스는 하나 이상의 Thread를 갖게 된다.
실행중인 프로그램을 프로세스(Process)라고 한다.
OS로부터 메모리를 할당받고 프로그램이 메모리에 올라간 상태를 Process라고 한다.
쓰레드는 웹 서버 자체가 멀티쓰레드를 서포트 하기 때문에 자주 사용할만한 일은 별로 없다고 한다.
그림처럼 여러개의 Thread가 있고 여러개가 돌아가는 것을 멀티쓰레드(Multi Thread)라고 한다.
Thread는 CPU를 점유해서 돌아가는데 CPU를 점유할 수 있는 것으로는 스케쥴러가 있다.
스케쥴러가 Thread에 CPU를 할당해서 Thread가 수행되도록 한다.
Thread구현
Runnable인터페이스 구현
자바는 다중 상속이 허용되지 않으므로 이미 다른 클래스를 상속한 경우 Thread를 만들려면
Runnable Interface를 implements 하도록 한다.
예제코드
class MyThread extends Thread {
public void run() {
/*
아무것도 구현하라고 하지 않지만
Thread가 스타트되면 Thread는 run메서드가 수행되기 때문에
run을 구현해야 한다.
*/
int i;
System.out.println("extends Thread");
for(i = 0; i <= 200; i++) {
System.out.print(i + "\t");
try{
sleep(100);
}catch (InterruptedException e) {
e.printStackTrace();
}
/*
sleep은 InterruptedException이 발생해서
깨어날 수 있는 가능성이 있기 때문에 처리해줘야 한다.
100이라는 것은 0.1초씩 잠들었다 깨어나면서 수행하도록 한다는 것.
sleep은 Thead메서드의 static클래스다.
여기서 sleep을 사용할 수 있다는 것은 이 클래스가
Thread를 상속받았다는 것을 의미한다.
*/
}
}
}
class MyThread2 implements Runnable {
@Override
public void run() {
int i;
System.out.println("impl Runnable");
for(i = 0; i <= 200; i++) {
System.out.print(i + "\t");
try{
Thread.sleep(10);
}catch (InterruptedException e) {
e.printStackTrace();
}
/*
Thread를 상속받지 않고 Runnable을 implements해도
run을 구현해야 한다.
단, 여기서는 sleep을 바로 사용할 수 없고
Thread.sleep() 형태로 사용해야 한다.
아니면 import해서 sleep으로 사용할 수 있다.
import static java.lang.Thread.sleep;
*/
}
}
}
public class ThreadTest {
public static void main(String[] args) {
System.out.println("start");
//Thread 상속클래스 사용
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
th1.start();
th2.start();
//Runnable impl 클래스 사용
MyThread2 runner1 = new MyThread2();
Thread th3 = new Thread(runner1);
th3.start();
MyThread2 runner2 = new MyThread2();
Thread th4 = new Thread(runner2);
th4.start();
Thread t = Thread.currentThread();
System.out.println(t);
System.out.println("end");
}
}
/*
Runnable을 implements해서 사용한다면
Thread 객체를 만들고 Thread 인스턴스에
Runnable 객체를 넣어서 돌려야 한다.
상속 클래스 사용하는것과 결과는 같다.
*/
위 예제에서 상속클래스를 사용하는것이나 Runnable 클래스를 사용하는것 둘중 하나는 주석처리하고 실행하는것이
결과확인이 편하다.
어떤 것으로 실행을 하던 start와 end가 출력 된 후 0부터 200까지 횡으로 출력되는데 0 0 1 1 2 2 3 3 4 4
이런 형태로 출력되는 것을 확인할 수 있다.
th1와 th2를 실행했으니 그런것이고 중간에 숫자 순서가 섞이는 경우도 있지만 둘씩 붙어서 200까지 출력하게 된다.
Runnable를 사용한 코드도 마찬가지 형태로 출력된다.
main안에서 돌아가는 쓰레드는 총 세개다.
메인쓰레드 그리고 메인안에서 두개의 쓰레드가 생성된다.
그래서 제일 먼저 end를 찍으며 종료되는것이 메인쓰레드다.
메인쓰레드가 하는 일은 start를 찍고 쓰레드를 두개 만들고 end를 찍은 뒤 끝난다.
그리고나서 th1, th2가 수행된다. 그래서 start와 end가 먼저 출력 된 뒤에 run에 구현한 0부터 200까지의 반복문이
출력되는 것이다.
중간에 Thread.currentThread()가 있는데 우선순위를 확인할 수 있다.
출력은 Thread[main, 5, main]으로 출력되는데 우선순위가 5번째라는 것이다.
[쓰레드 이름, 우선순위, 어느그룹에 속해있는지] 이러한 내용이 출력된다.
Thread우선순위는
Thread.MIN_PRIORITY(=1) ~ Thread.MAX_PRIORITY(=10) 이렇게 있다.
기본적으로 디폴트 우선순위는 Thread.NORM_PRIORITY(=5)로 5를 갖게 된다.
setPriority(int newPriority)로 우선순위를 지정할 수 있고
int getPriority()로 가져올 수 있다.
우선순위가 높은 Thread는 CPU를 배분받을 확률이 높다.
Thread t = Thread.currentThread();
int a = 2;
t.setPriority(a);
t.setPriority(3);
t.getpriority();
Thread.currentThread().setPriority(a);
Thread.currentThread().setPriority(3);
Thread.currentThread().getPriority();
이렇게 사용할 수 있다.
join()메서드
다른 Thread의 결과를 보고 진행해야 하는 일이 있는 경우 join()메서드를 활용한다.
join()메서드를 호출한 Thread가 non-runnable상태가 된다.
예제코드
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) {
JoinTest jt1 = new JoinTest(1, 50);
JoinTest jt2 = new JoinTest(51, 100);
jt1.start();
jt2.start();
int total = jt1.total + jt2.total;
System.out.println("jt1.total = " + jt1.total);
System.out.println("jt2.total = " + jt2.total);
System.out.println("total = " + total);
}
}
결과값은
jt1.total = 0
jt2.total = 3775
total = 0
이렇게 출력된다.
강의에서는 jt1.total = 1275 total = 1275로 출력되었지만 계속 둘다 0으로 출력되었다.
jt1과 total은 계속 같은 값으로 출력이 되는데 jt2가 수행이 되기 전에 total이 먼저 출력되기 때문이다.
또 어떠한 경우는 jt1이 1275로 출력되지만 total은 0으로 출력되는 경우도 있는데
그만큼 수행 순서가 변동이 계속 생긴다는 의미다.
하지만 원하는 결과값을 출력하기 위해서는 jt1을 수행하고 jt2를 수행한다음 결과값을 더한 total을 처리하도록
해야하는데 이럴때는 아래와 같이 join()메서드를 이용해 처리한다.
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) {
JoinTest jt1 = new JoinTest(1, 50);
JoinTest jt2 = new JoinTest(51, 100);
jt1.start();
jt2.start();
try {
jt1.join();
jt2.join();
}catch (InterruptedException e) {
e.printStackTrace();
}
int total = jt1.total + jt2.total;
System.out.println("jt1.total = " + jt1.total);
System.out.println("jt2.total = " + jt2.total);
System.out.println("total = " + total);
}
}
jt1.total = 1275
jt2.total = 3775
total = 5050
이렇게 출력된다.
total은 join이 끝나야 수행되기 때문에 연산이 전부 진행된 다음 total의 연산이 수행된다.
위에 있는 ThreadTest에서의 결과값을 보면 start와 end가 출력 된 다음 쓰레드가 수행되었었다.
join은 그렇게 main쓰레드가 먼저 수행된 다음 내부 쓰레드를 수행하도록 하지 않고
내부 쓰레드가 모두 수행될때까지 대기하는 역할을 한다.
interrupt()메서드
다른 Thread에 예외를 발생시키는 interrupt를 보낸다.
Thread가 join, sleep, wait 메서드에 의해 블록킹 되었다면 interrupt에 의해 다시 runnable상태가 될 수 있다.
즉, join, sleep, wait은 try catch로 많이 처리하는데 interrupt를 만나면 InterruptedException이 되어
catch에 있는 코드를 처리하게 된다는 것이다.
예제코드
public class InterruptTest extends Thread {
public void run() {
int i;
for(i = 0; i < 100; i++) {
System.out.println(i);
}
try {
sleep(5000);
}catch (InterruptedException e) {
System.out.println(e);
System.out.println("Wake!!");
}
}
public static void main(String[] args) {
IntteruptTest test = new InterruptTest();
test.start();
test.interrupt();
System.out.println("end");
}
}
end 출력 이후 0부터 99까지 출력 한 뒤 java.lang.InterruptedException : sleep interrupted Wake!!
이렇게 출력된다.
test.interrupt() 로 인해 sleep상태에서 5초간 기다리는 것이 아닌 Exception으로 빠지게 된다.
어떤 Thread가 sleep이나 join, wait 상태에서 interrupt() 메서드를 호출하면 Exception이 발생하게 되면서
처리하게 된다고 했기 때문에 InterruptedException이 발생했다고 출력되고 Wake!!가 출력된 것이다.
여기서는 그냥 Wake!!라는 출력문을 작성했지만 처리하는 코드를 적어두면 되는것이다.
Thread 종료하기
데몬 등 무한 반복하는 Thread가 종료될 수 있도록 run메서드 내의 while문을 활용해서 종료하도록 한다.
이때, Thread.stop()은 사용하지 않는다.
import java.io.IOException;
public class TerminateThread extends Thread {
private boolean flag = false;
int i;
public TerminateThread(String name) {
super(name);
//Thread Constructor 중에 Thread이름을 받을 수 있는 Constructor가 있다.
}
public void run() {
while(!flag) {
try {
sleep(100);
}catch (InterruptedException e) {
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 TherminateTread("A");
TerminateThread threadB = new TherminateTread("B");
threadA.start();
threadB.start();
int in;
while(true) {
in = System.in.read();
if(in == 'A') {
threadA.setFlag(true);
}else if(in == 'B') {
threadB.setFlag(true);
}else if(in == 'M') {
threadA.setFlag(true);
threadB.setFlag(true);
break;
}
}
System.out.println("main end");
}
}
실행 후 A를 입력하면 A end B를 입력하면 B end M을 입력하면 main end가 출력된다.
실행하자마자 M을 먼저 입력한다면 A end B end main end 가 출력된다.
그리고 A를 입력해서 A end 를 본 후 다시 A를 입력하면 아무것도 출력되지 않는다. B도 마찬가지.
이 예제에서 run이 데몬이라고 볼 수 있다. 그럼 main에서 A를 입력했을 때 Flag를 true로 set해주었고
그럼 run에서 !flag로 반복문을 수행하도록 했기 때문에 true일때 멈추고 빠져나오게 된다.
그렇게 빠져나오게 되면서 end를 찍어주게 되는 것이다.
IDE에서 실행하고 보면 M을 입력한 뒤에는 실행이 종료되는것을 확인할 수 있다.
그럼 결국 다 잘 꺼졌다는 것.
레퍼런스
패스트캠퍼스 올인원 패키지 - 자바 객체지향프로그래밍
'JAVA' 카테고리의 다른 글
Servlet&JSP에서 파일 업로드 처리 (0) | 2023.10.20 |
---|---|
멀티쓰레드(Multi Thread) (0) | 2021.02.14 |
입출력스트림(IOStream) (0) | 2021.02.12 |
스트림(Stream) (0) | 2021.02.11 |
람다식(Lambda) (0) | 2021.02.10 |