- 실행중인 프로그램
- 완전히 독립적인 수행 단위
- 프로세스의 세부 실행 단위
- 자바 실행의 기본 단위(main 스레드)
- main 스레드 이외의 스레드 추가 가능
- 한 프로그램에 여러개의 스레드가 존재할 수 있다. 스레드가 1개라면 단일 스레드, 2개 이상이라면 다중 스레드
- 프로그램 코드를 1줄씩 실행하는 것이 스레드의 역할(=실행제어)
- 멀티 스레드 = multi thread = 다중 스레드
- 여러개의 스레드를 이용하는 프로그램
- 다중 스레드에서 각각의 스레드는 하나의 독립적인 프로세스처럼 작업 수행
메모리 절약
OS마다 다르지만, 무슨 작업을 수행하려고 할 때 JVM은 32~64MB 메모리를 점유한다.
스레드 경우에는 1MB 이내의 메모리만 점유한다. 그래서 스레드를 경량 프로세스
라고 부른다.
프로세스 Context Switching에 비해 오버헤드 절감
멀티 프로세스로 실행되는 작업을 멀티 스레드로 실행하게 되면 프로세스를 생성하여 자원을 할당하는 과정도 줄어들뿐더러 프로세스를 Context Switching에 비해 오버헤드를 줄일 수 있다.
작업들 간 통신 비용 절감
프로세스 간의 통신 비용보다 하나의 프로세스 내에서 여러 스레드 간의 통신 비용이 훨씬 적어서 작업들 간의 통신 부담을 줄일 수 있다.
- Thread 클래스 상속
extends Thread
- Runnable 인터페이스 구현
implements Runnable
Thread 클래스 상속을 받거나
extends Thread
Runnable
인터페이스를 구현하게 되면, public void run() 메소드를 오버라이드해서 수행할 작업 작성한다.
start()
메소드를 호출 ★start()
메소드를 호출하면run()
메소드에 오버라이드 한 내용이 실행- Thread의 동작은
run()
안에 작성해야 한다.
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); // 3초 일시중지 (1000 = 1초)
System.out.println(name + " 작업 실행");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
sleep()
메소드를 사용하면 1/1000초를 기다렸다가 Thread를 실행할 수 있다.
예외처리는 만약 Thread끼리 부딪히거나 겹치게 되면 무한 대기상태가 생길 수 있는데 InterruptedException
을 이용하면 그것에 대한 예외처리를 해준다.
Main 클래스
public class Main {
public static void main(String[] args) {
System.out.println("main 시작");
Process process1 = new Process("연산");
process1.start(); // Process 클래스의 오버라이드된 run() 메소드가 호출
Process process2 = new Process("제어");
process2.start(); // 제어가 먼저 시작할 수도 있다. 시작하세요 하면 바로 시작 안할 수 있다.
// 스케줄러에 의해서 늦게 시작할 수도 있다.
System.out.println("main 종료");
// 메인은 프로세스한테 너 시작해 알려주고 내려와서 종료함
// 스레드는 특정 시작을 알려주면 그 때 시작
}
}
main 시작
main 종료
// // wait()에 지정해준 대로 3초 뒤에 출력된다.
연산 작업 실행
제어 작업 실행
Thread 클래스를 상속받게 되면, run()
메소드를 오버라이드하고, 그 안에 Thread의 행동을 명시해야 한다.
Soldier 클래스
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() {
try {
// 1초에 한 발씩 쏘기
while(gun.getBullet() != 0) {
shoot();
Thread.sleep(1000);
}
// 작업 도중에 가로채기 당해서 대기 상태
} catch (InterruptedException e) { // 인터럽트 : 가로채기
e.printStackTrace();
}
}
}
Gun 클래스
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;
}
}
Main 클래스
public class Main {
public static void main(String[] args) {
// 스레드 우선순위
System.out.println("가장 높은 우선 순위 : " + Thread.MAX_PRIORITY);
System.out.println("가장 낮은 우선 순위 : " + Thread.MIN_PRIORITY);
System.out.println("보통 우선 순위 : " + Thread.NORM_PRIORITY);
// 스레드 2개(s1, s2)
Soldier s1 = new Soldier("김상사", new Gun(6));
Soldier s2 = new Soldier("장병장", new Gun(10));
// 각 스레드의 우선 순위
System.out.println("s1 우선 순위 : " + s1.getPriority());
System.out.println("s2 우선 순위 : " + s2.getPriority());
// 우선 순위가 높은 스레드를 (최대한) 먼저 실행
// 우선 순위 조정
s1.setPriority(Thread.MIN_PRIORITY); // 가장 낮은 우선 순위
s2.setPriority(Thread.MAX_PRIORITY); // 가장 높은 우선 순위
// 스레드 실행
s1.start(); // 김상사가 먼저 쏘지 않을수도 있다.
s2.start();
}
}
setPriority()
메소드를 이용해서 우선순위를 최대한 조정할 수 있다.
WashRobot 클래스
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 + " 빨래중");
}
}
Robot 클래스
public class Robot {
}
Main 클래스
public class Main {
public static void main(String[] args) {
Runnable robot1 = new WashRobot("로봇1");
Thread thread1 = new Thread(robot1);
WashRobot robot2 = new WashRobot("로봇2");
Thread thread2 = new Thread(robot2);
Thread thread3 = new Thread(new WashRobot("로봇3"));
// A a = new A();
// B b = new B();
// B b = new B(new A());
thread1.start();
thread2.start();
thread3.start();
}
}
Runnable 인터페이스를 구현한 클래스는 Thread로 바꿔야 start()
메소드를 호출할 수 있다.
Calculator 클래스
public class Calculator implements Runnable {
// 필드
private long total; // 초기화 안해도 자동으로 0
private long begin;
private long end;
// 생성자
public Calculator(long begin, long end) {
this.begin = begin;
this.end = end;
}
public void add() {
for(long n = begin; n <= end; n++) {
total += n;
}
}
public long getTotal() {
return total;
}
public void setTotal(long total) {
this.total = total;
}
@Override
public void run() {
add();
}
}
Main 클래스
public class Main {
public static void main(String[] args) {
// Calculator를 2개 준비
// 작업을 반으로 나눠서 진행
// Calculator가 동시에 연산을 수행하려면 Calculator를 스레드로 처리해야 함
Calculator calc1 = new Calculator(1, 50000);
Thread thread1 = new Thread(calc1);
thread1.start(); // 1번째 계산기 동작 시작
Calculator calc2 = new Calculator(50001, 100000);
Thread thread2 = new Thread(calc2);
thread2.start(); // 2번째 계산기 동작 시작
total 값이 0이 나오는 이유는 Main()
메소드가 종료되면 바로 결과를 도출하기 때문에 0
이 반환된다.
그래서 join()
메소드를 활용하여 Thread의 각각의 작업이 다 끝날 때까지 기다렸다가 출력해야 한다.
// 모든 계산기의 동작이 끝날때까지 기다린다.
// join() : 스레드가 종료(die)될 때까지 기다린다.
try {
thread1.join();
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread1.isAlive());
System.out.println(thread2.isAlive());
System.out.println(calc1.getTotal() + calc2.getTotal());
}
}
join()
메소드를 사용하면, 정상적으로 50005000
값을 반환한다.
synchronized
- 스레드 충돌 방지하기 위해서 한 번에 한 스레드만 접근할 수 있도록 허용
- 공유 자원의 일관성을 보장
- 한 번에 한 스레드만 접근할 수 있는 영역을 임계 영역(Critical Section)이라고 한다.
RoomRobot 클래스
public class RoomRobot extends Thread {
private Cleaner cleaner;
public RoomRobot(Cleaner cleaner) {
super();
this.cleaner = cleaner;
}
@Override
public void run() {
for(int i = 0; i < 5; i++) {
cleaner.roomCleaning();
}
}
}
ToiletRobot 클래스
public class ToiletRobot extends Thread {
private Cleaner cleaner;
public ToiletRobot(Cleaner cleaner) {
super();
this.cleaner = cleaner;
}
@Override
public void run() {
for(int i = 0; i < 5; i++) {
cleaner.toiletCleaning();
}
}
}
Robot 클래스
public class Robot {
}
Cleaner 클래스
Object 클래스의 notify() 메소드
Object 클래스의 wait() 메소드
public class Cleaner {
public synchronized void toiletCleaning() {
try {
System.out.println("화장실 청소");
notify(); // "나 화장실 청소 끝났다"고 알림
wait(); // 잠깐 쉼
} catch (InterruptedException e) { // wait() 메소드는 예외처리 필요
e.printStackTrace();
}
}
public synchronized void roomCleaning() {
try {
System.out.println("방 청소");
notify(); // "나 방 청소 끝났다"고 알림
wait(); // 잠깐 쉼
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Main 클래스
public class Main {
public static void main(String[] args) {
// 클리너 1개
Cleaner cleaner = new Cleaner();
// 로봇 2개(동일한 클리너를 가짐)
ToiletRobot robot1 = new ToiletRobot(cleaner);
RoomRobot robot2 = new RoomRobot(cleaner);
// 청소 시작
robot1.start();
robot2.start();
}
}
반환값
화장실 청소
방 청소
화장실 청소
방 청소
화장실 청소
방 청소
화장실 청소
방 청소
화장실 청소
방 청소