멀티태스킹 : 하나의 프로그램이 여러 개의 작업을 동시에 처리해 성능을 높이기 위한 기법
영상 플레이어를 간단하게 태스크 기준으로 나눠보면 아래처럼 나눌 수 있다. 멀티스레드를 이용하면 이 작업들간 의존성 없이 여러 작업을 동시에 할 수 있다
1) 소리 재생 태스크
2) 영상 재생 태스크
3) 사용자 입력 처리 태스크
run() 메소드가 종료하면 스레드는 종료한다
한번 종료한 스레드는 다시 시작할 수 없고 새로운 스레드 객체를 생성해야한다
한 스레드에서 다른 스레드를 강제 종료할 수 있다
run()
메소드를 오버라이딩해 원하는 동작을 하는 스레드를 위한 클래스를 만들 수 있다class ExampleThread extends Thread {
@Override
public void run() {
/* 코드 */
}
}
// 오버라이딩한건 run() 메소드이지만 th.run()을 호출하면 해당 메소드의 코드만 실행한다
// run() 메소드 내의 코드를 스레드로서 실행하려면 start() 메소드를 실행한다
Thread th = new ExampleThread();
th.start()
public interface Runnable {
void run();
}
class ExampleRunnable implements Runnable {
@Override
public void run() {
/* 코드 */
}
}
Thread th = new Thread(new ExampleRunnable());
th.start();
public Thread(Runnable target) {
this((ThreadGroup)null, target, "Thread-" + nextThreadNum(), 0L);
}
현대의 자바는 Native Thread Model을 따른다. JVM에서 스레드를 생성하고 관리하기는 하지만 호스트 운영체제의 스레드 API를 사용해 운영체제 레벨 스레드로 맵핑된다. Native Thread Model을 따를 경우 실제 스레드의 스케쥴링은 운영체제가 한다
JVM의 스레드 구현에 대한 간단한 글을 여기서 읽을 수 있다
https://www.developer.com/design/an-introduction-to-jvm-threading-implementation/
스레드는 다음과 같은 정보를 가진다
필드 | 타입 | 내용 |
---|---|---|
스레드 이름 | String | 스레드의 이름으로 사용자가 지정 |
스레드 ID | int | 스레드 고유의 식별번호 |
스레드 PC | int | 현재 실행 중인 스레드의 코드 주소 |
스레드 상태 | int | NEW, RUNNABLE, WAITING, TIMED_WAITING, BLOCK, TERMINATED 중 하나 |
스레드 우선순위 | int | 스레드 스케줄링 시 사용되는 우선순위 값으로 1~10 사이의 값이며 10이 최상위 우선순위 기본값은 5 |
스레드 그룹 | int | 여러 개의 자바 스레드가 하나의 그룹을 형성할 수 있으며 이 경우 스레드가 속한 그룹 |
스레드 레지스터 스택 | 메모리 블록 | 스레드가 실행되는 동안 레지스터들의 값 |
NEW : 생성됐지만 아직 실행할 준비 X
RUNNABLE : 현재 실행되고 있거나 실행 준비되어 스케줄링 큐에서 대기 중
WAITING : wait()을 호출한 상태. 다른 스레드가 notify()
하기를 기다림
TIMED_WAITING : sleep()
에서 지정한만큼 suspend된 상태
BLOCK : 스레드가 I/O 작업을 요청하면 JVM이 자동으로 BLOCK 상태로 만듦
TERMINATED : 종료한 상태
synchronized 키워드로 임계 구역을 지정해 스레드를 동기화한다
Thread 클래스는 모니터를 임계 구역 문제 해결법으로 사용한다
public class SynchronizedEx {
public static void main(String [] args) {
SharedBoard board = new SharedBoard();
Thread th1 = new StudentThread("kitae", board);
Thread th2 = new StudentThread("hyosoo", board);
th1.start();
th2.start();
}
}
class SharedBoard {
private int sum = 0; // 집계판의 합
synchronized public void add() {
int n = sum;
Thread.yield(); // 현재 실행 중인 스레드 양보
n += 10; // 10 증가
sum = n; // 증가한 값을 집계합에 기록
System.out.println(Thread.currentThread().getName() + " : " + sum);
}
public int getSum() { return sum; }
}
class StudentThread extends Thread {
private SharedBoard board; // 집계판의 주소
public StudentThread(String name, SharedBoard board) {
super(name);
this.board = board;
}
@Override
public void run() {
for(int i=0; i<10; i++)
board.add();
}
}
producer-consumer 문제 : 데이터를 공급하는 스레드와 소비하는 스레드가 있을때 buffer 같은 중간 단계 객체가 있는 경우 producer와 consumer 모두 공유 데이터에 접근할 때 문제가 발생할 수 있다
이럴때는 동기화 메소드를 사용할 수 있다
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
class MyLabel extends JLabel {
int barSize = 0; // 바의 크기
int maxBarSize;
MyLabel(int maxBarSize) {
this.maxBarSize = maxBarSize;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.MAGENTA);
int width = (int)(((double)(this.getWidth()))
/maxBarSize*barSize);
if(width==0) return;
g.fillRect(0, 0, width, this.getHeight());
}
synchronized void fill() {
if(barSize == maxBarSize) {
try {
wait();
} catch (InterruptedException e) { return; }
}
barSize++;
repaint(); // 바 다시 그리기
notify();
}
synchronized void consume() {
if(barSize == 0) {
try {
wait();
} catch (InterruptedException e)
{ return; }
}
barSize--;
repaint(); // 바 다시 그리기
notify();
}
}
class ConsumerThread extends Thread {
MyLabel bar;
ConsumerThread(MyLabel bar) {
this.bar = bar;
}
public void run() {
while(true) {
try {
sleep(200);
bar.consume();
} catch (InterruptedException e)
{ return; }
}
}
}
public class TabAndThreadEx extends JFrame {
MyLabel bar = new MyLabel(100);
TabAndThreadEx(String title) {
super(title);
this.setDefaultCloseOperation
(JFrame.EXIT_ON_CLOSE);
Container c = getContentPane();
c.setLayout(null);
bar.setBackground(Color.ORANGE);
bar.setOpaque(true);
bar.setLocation(20, 50);
bar.setSize(300, 20);
c.add(bar);
c.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e)
{
bar.fill();
}
});
setSize(350,200);
setVisible(true);
c.requestFocus();
ConsumerThread th = new
ConsumerThread(bar);
th.start(); // 스레드 시작
}
public static void main(String[] args) {
new TabAndThreadEx(
"아무키나 빨리 눌러 바 채우기");
}
}