JAVA 멀티스레드 프로그래밍

이상민·2021년 8월 6일
0
post-thumbnail

멀티스레드

  • 멀티태스킹 : 하나의 프로그램이 여러 개의 작업을 동시에 처리해 성능을 높이기 위한 기법

  • 영상 플레이어를 간단하게 태스크 기준으로 나눠보면 아래처럼 나눌 수 있다. 멀티스레드를 이용하면 이 작업들간 의존성 없이 여러 작업을 동시에 할 수 있다
    1) 소리 재생 태스크
    2) 영상 재생 태스크
    3) 사용자 입력 처리 태스크

  • 스레드 : 프로세스 내에서 스케줄링되어 실행되는 실행의 단위

1. Thread 클래스와 Runnable 인터페이스

run() 메소드가 종료하면 스레드는 종료한다
한번 종료한 스레드는 다시 시작할 수 없고 새로운 스레드 객체를 생성해야한다
한 스레드에서 다른 스레드를 강제 종료할 수 있다

1-1. Thread 클래스

  • Thread 클래스는 스레드를 만들기 위한 클래스이다. run() 메소드를 오버라이딩해 원하는 동작을 하는 스레드를 위한 클래스를 만들 수 있다
class ExampleThread extends Thread {
    @Override
    public void run() {
        /* 코드 */
    }
}
  • 다음과 같이 스레드 인스턴스를 생성하고 실행한다
// 오버라이딩한건 run() 메소드이지만 th.run()을 호출하면 해당 메소드의 코드만 실행한다
// run() 메소드 내의 코드를 스레드로서 실행하려면 start() 메소드를 실행한다 
Thread th = new ExampleThread();
th.start()

1-2. Runnable 인터페이스

  • Runnable 인터페이스는 실행가능한지만 나타내는 함수형 인터페이스이다
public interface Runnable {
    void run();
}
  • Runnable 인터페이스를 구현한 클래스의 인스턴스를 받는 생성자를 통해 스레드를 생성할 수도 있다
class ExampleRunnable implements Runnable {
    @Override
    public void run() {
        /* 코드 */
    }
}
  • 다음과 같이 스레드 인스턴스를 생성하고 실행한다
Thread th = new Thread(new ExampleRunnable());
th.start();
  • Runnable 구현 인스턴스를 받는 Thread 클래스의 생성자는 다음과 같다
public Thread(Runnable target) {
    this((ThreadGroup)null, target, "Thread-" + nextThreadNum(), 0L);
}

2. 스레드 스케쥴링

  • 현대의 자바는 Native Thread Model을 따른다. JVM에서 스레드를 생성하고 관리하기는 하지만 호스트 운영체제의 스레드 API를 사용해 운영체제 레벨 스레드로 맵핑된다. Native Thread Model을 따를 경우 실제 스레드의 스케쥴링은 운영체제가 한다

  • JVM의 스레드 구현에 대한 간단한 글을 여기서 읽을 수 있다
    https://www.developer.com/design/an-introduction-to-jvm-threading-implementation/

  • 스레드는 다음과 같은 정보를 가진다

필드타입내용
스레드 이름String스레드의 이름으로 사용자가 지정
스레드 IDint스레드 고유의 식별번호
스레드 PCint현재 실행 중인 스레드의 코드 주소
스레드 상태intNEW, RUNNABLE, WAITING, TIMED_WAITING, BLOCK, TERMINATED 중 하나
스레드 우선순위int스레드 스케줄링 시 사용되는 우선순위 값으로 1~10 사이의 값이며 10이 최상위 우선순위 기본값은 5
스레드 그룹int여러 개의 자바 스레드가 하나의 그룹을 형성할 수 있으며 이 경우 스레드가 속한 그룹
스레드 레지스터 스택메모리 블록스레드가 실행되는 동안 레지스터들의 값

2-1. 스레드의 상태와 생명주기

  • NEW : 생성됐지만 아직 실행할 준비 X

  • RUNNABLE : 현재 실행되고 있거나 실행 준비되어 스케줄링 큐에서 대기 중

  • WAITING : wait()을 호출한 상태. 다른 스레드가 notify()하기를 기다림

  • TIMED_WAITING : sleep()에서 지정한만큼 suspend된 상태

  • BLOCK : 스레드가 I/O 작업을 요청하면 JVM이 자동으로 BLOCK 상태로 만듦

  • TERMINATED : 종료한 상태


3. 스레드 동기화

3-1. 임계 구역 지정

  • 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();
    }
}

3-2. wait()-nofity()를 통한 동기화

  • 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(
			"아무키나 빨리 눌러 바 채우기");
	}
}
profile
편하게 읽기 좋은 단위의 포스트를 추구하는 개발자입니다

0개의 댓글

관련 채용 정보