스레드(Thread)란?
- 프로세스의 세부 실행 단위를 말한다.
- 자바 실행의 기본 단위이다.
- main스레드 이외의 추가가 가능하다.
방법은 두가지가 있다.
- Thread 클래스를 상속받는다. (extends Thread)
- Runnable 인터페이스를 구현한다. (implements Runnable)
- Thread 클래스를 상속받게 되거나, Runnable인터페이스를 구현하게 되면, public void run() 메소드를 오버라이드 해야한다. (오버라이드 하는 이유 : 약속이기 때문)
- start() 메소드를 호출해야한다. (중요)
- start() 메소드를 호출하면 run() 메소드에 오버라이드 한 내용이 실행된다.
- Thread의 동작은 run() 안에 명시해야한다. (약속)
자바의 프로세스의 동작을 살펴보자.
메인 메소드에서 System.out.println()
을 하면
System.out.println("main 시작");
System.out.println("main 종료");
출력 :
main 시작
main 종료
우리가 알던대로 이렇게 바로 출력이 된다.
이제 Thread를 만들어 보자.
Thread 클래스를 상속 받을 것이다.
Process자식클래스
public class Process extends Thread {
private String name;
public Process(String name) {
super();
this.name = name;
}
@Override // 오버라이드 애너테이션 추가
public void run() {
// millies = 1/1000
try {
Thread.sleep(3000); // 1000 = 1초, 3초 일시중지
System.out.println(name + " 작업 실행");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
sleep()
메소드를 사용하면 1/1000초를 기다렸다가 Thread를 실행 시킬 수 있다. 1000을 입력하면 1초가 되는 셈이다.
예외처리로는 만약 Thread끼리 부딪히거나 겹치게 되면 무한 대기상태가 생길 수 있는데 InterruptedException
를 이용하여 그것에 대한 예외처리를 지정해준다.
Main클래스
public class Main {
public static void main(String[] args) {
System.out.println("main 시작");
Process process = new Process("연산");
process.start(); // Process 클래스의 오버라이드된 run() 메소드가 호출
Process process2 = new Process("제어");
process2.start();
System.out.println("main 종료");
// Main은 시작, 메소드, 종료 순으로 일만시키고 메소드는 다른애한테 맡겼기때문에 나중에 실행된다.
// 그래서 시작 -> 종료 -> 메소드 순으로 실행이 된다.
}
}
출력 :
main 시작
main 종료
// wait()에 지정해준 대로 3초 뒤에 출력된다.
제어 작업 실행
연산 작업 실행
Thread 클래스를 상속받게 되면, run()메소드를 오버라이드하고, 그 안에 Thread의 행동을 명시해야한다.
package ex02_thread;
public class Soldier extends Thread {
private String name;
private Gun gun;
public Soldier(String name, Gun gun) {
super();
this.name = name;
this.gun = gun;
}
public void shoot() {
System.out.print(name + "이(가) 총을 쐈다! ");
gun.shoot();
}
@Override
public void run() {
// 1초에 한 발씩 쏘기
try {
while (gun.getBullet() != 0) {
shoot();
Thread.sleep(1000);
}
} catch (InterruptedException e) { // 작업을 하고 있는 도중에 가로채기 당하여 대기를 하는 것
e.printStackTrace();
}
}
}
package ex02_thread;
public class Gun {
private int bullet;
public Gun(int bullet) {
super();
this.bullet = bullet;
}
public void shoot() {
if(bullet == 0) {
System.out.println("총알 없음");
return;
}
bullet--;
System.out.println("빵야! " + bullet + "발 남음");
}
public int getBullet() {
return bullet;
}
public void setBullet(int bullet) {
this.bullet = bullet;
}
}
package ex02_thread;
public class Main {
public static void main(String[] args) {
// 스레드 우선순위
System.out.println("가장 높은 우선순위 : " + Thread.MAX_PRIORITY); // 10
System.out.println("가장 낮은 우선순위 : " + Thread.MIN_PRIORITY); // 1
System.out.println("보통 우선순위 : " + Thread.NORM_PRIORITY); // 5
Soldier s1 = new Soldier("김상사", new Gun(6));
Soldier s2 = new Soldier("장병장", new Gun(10));
System.out.println("s1 우선순위 : " + s1.getPriority()); // 5
System.out.println("s2 우선순위 : " + s2.getPriority()); // 5
// 우선순위가 같기 때문에 쓰레드가 알아서 작업한다.
// 우선순위가 높은 스레드를 최대한 먼저 실행한다.
// 우선순위 조정
s1.setPriority(Thread.MIN_PRIORITY);
s2.setPriority(Thread.MAX_PRIORITY);
// 스레드 실행
s1.start();
s2.start();
}
}
setPriority()
메소드를 이용해서 우선순위를 최대한 조정할 수 있다.
무조건 적인게 아니라서 알고 있어야 한다.
package ex03_runnable;
// 스레드 생성 방법
// Runnable 인터페이스 구현
// 2. public void run() 오버라이드
public class WashRobot extends Robot implements Runnable {
private String name;
public WashRobot(String name) {
super();
this.name = name;
}
@Override
public void run() {
System.out.println(name + " 빨래중");
}
}
package ex03_runnable;
public class Robot {
}
package ex03_runnable;
public class Main {
public static void main(String[] args) {
// Runnable 인터페이스를 구현한 클래스는 Thread로 바꿔야 start()메소드로 호출할 수 있다.
Runnable robot1 = new WashRobot("로봇1");
WashRobot robot2 = new WashRobot("로봇2");
Thread thread1 = new Thread(robot1);
Thread thread2 = new Thread(robot2);
Thread thread3 = new Thread(new WashRobot("로봇3"));
thread1.start();
thread2.start();
thread3.start();
}
}
join()
메소드를 활용하면 모든 Thread의 동작이 종료(die)될 때까지 기다린다.
package ex04_join;
public class Calculator implements Runnable {
long total;
long begin;
long end;
public Calculator(long begin, long end) {
super();
this.begin = begin;
this.end = end;
}
public void add() {
for(long n = begin; n <= end; n++) {
total += n;
}
}
@Override
public void run() {
add();
}
public long getTotal() {
return total;
}
public void setTotal(long total) {
this.total = total;
}
public long getBegin() {
return begin;
}
public void setBegin(long begin) {
this.begin = begin;
}
public long getEnd() {
return end;
}
public void setEnd(long end) {
this.end = end;
}
}
package ex04_join;
public class Main {
public static void main(String[] args) throws InterruptedException {
// Calculator를 2개 준비
// 작업을 반으로 나눠서 진행한다.
// Calculator가 동시에 연산을 수행하려면 스레드로 처리해야 한다.
Calculator calc1 = new Calculator(1, 5000);
Thread thread1 = new Thread(calc1);
thread1.start();
Calculator calc2 = new Calculator(5001, 10000);
Thread thread2 = new Thread(calc2);
thread2.start();
이렇게만 하면 메인메소드가 종료되는대로 결과를 출력해내기 때문에 0
이라는 결과를 얻게 된다.
그래서 join()
메소드를 활용하여 Thread의 각각의 작업이 다 끝날때까지 기다렸다가 출력해 내야한다.
public class Main {
public static void main(String[] args) throws InterruptedException {
// Calculator를 2개 준비
// 작업을 반으로 나눠서 진행한다.
// Calculator가 동시에 연산을 수행하려면 스레드로 처리해야 한다.
Calculator calc1 = new Calculator(1, 5000);
Thread thread1 = new Thread(calc1);
thread1.start();
Calculator calc2 = new Calculator(5001, 10000);
Thread thread2 = new Thread(calc2);
thread2.start();
// Thread.sleep(1000);
// 모든 계산기의 동작이 끝날때까지 기다린다.
// 스레드가 종료(die)될 때까지 기다린다.
thread1.join();
thread2.join();
System.out.println(thread1.isAlive()); // true
System.out.println(thread2.isAlive()); // false 살아있는가?
System.out.println(calc1.getTotal() + calc2.getTotal());
}
}
위의 Main클래스에서 이렇게 join()을 해주면, 정상적으로 50005000
이라는 출력값을 얻게된다.