티스토리 뷰

반응형

✏️ 멀티태스킹

멀티태스킹

멀티태스킹(multitasking)이란 멀티 + 태스킹의 합성어로서 다수의 작업을 동시에 처리하는 것을 말한다. 간단한 응용프로그램은 보통 하나의 작업(태스크)만 하는 경우가 대부분이지만, 큰 규모의 응용프로그램은 많은 경우 여러 작업(태스크)을 동시에 실행하게 된다. 그래서 멀티태스킹은 응용프로그램의 여러 작업(태스크)이 동시에 진행되게 하는 기법으로, 응용프로그램의 목적을 효율적으로 달성하게 한다. 아래 사진은 멀티태스킹 소프트웨어 사례이다.

멀티태스킹 소프트웨어 사례

 

스레드와 멀티태스크

스레드는 thread of control의 준말로서 프로그램 코드를 실행하는 하나의 실 혹은  제어의 개념으로, 하나의 스레드는 하나의 작업만 처리할 수 있다. 좀 더 구체적으로 가보면 스레드는 운영체제나 JVM에서 태스크를 실행하는 단위로, 운영체제나 JVM에 의해 관리되는 단위이다. 아래의 사진은 테트리스를 구성하는 3개의 태스크 코드를 3개의 스레드로 실행시키는 멀티스레딩을 보여준다.

 

멀티태스킹과 멀티스레딩

멀티태스킹을 실현하기 위해서는 2가지 방법이 사용되고 있다. 먼저 멀티 프로세싱(multi-processing)하나의 응용프로그램을 여러 개의 프로세스로 구성하여 각 프로세스가 하나의 작업을 처리하도록 하는 기법으로 각 프로세스는 고유한 메모리 영역을 보유하고 독립적으로 실행된다. 그러므로 하나의 응용프로그램에 속하는 프로세스들은 변수를 공유할 수 없으므로 오버헤드가 크고 시간 소모가 크다.
이에 반해 멀티스레딩(multi-threading) 하나의 응용프로그램을 동시처리가 가능한 여러 작업(코드)으로 분할하고 작업의 개수만큼 스레드를 생성하여 각 스레드로 하여금 하나의 작업을 처리하도록 하는 기법이다. 모든 스레드는 응용프로그램 내의 자원과 메모리를 공유하므로 통신에 따른 오버헤드가 작고 문맥 교환이 빠른 장점이 있다.

 

멀티스레딩 활용분야

아래 그림은 멀티스레딩의 활용분야에 속하는 웹 서버 시스템으로 클라이언트에 접속하는 인원수만큼 웹 서비스 스레드가 각각 웹 클라이언트 요청을 서비스함으로 인해 대기 시간을 줄여준다. 이렇듯 멀티스레딩은 응용프로그램이 다수의 스레드를 가지고 다수의 작업을 동시에 처리함으로써, 한 스레드가 대기하는 동안 다른 스레드를 실행하여 시간 지연을 줄이고 자원의 비효율적 사용을 개선한다.

 

✏️ 자바의 멀티스레딩

멀티스레드와 자바 가상 기계(JVM)

자바에는 프로세스(process)가 존재하지 않고 스레드 개념만 존재하여, JVM은 멀티스레딩만 지원한다. 자바 스레드(java thread)는 JVM에 의해 스케줄되는 실행 단위 코드 블럭이다. 하나의 JVM은 아래 그림과 같이 하나의 자바 응용프로그램만 실행이 가능하며 하나의 자바 응용프로그램은 하나 이상의 스레드를 생성할 수 있다. 주의할 점으로는 컴퓨터에서 n개의 자바 응용프로그램이 실행되고 있으면 n개의 JVM이 실행되고 있다는 점을 알고 있어야 한다.

 

자바 스레드와 JVM

자바 스레드는 다른 운영체제의 스레드와 크게 다르지 않다. 개발자가 스레드를 만들기 위해서는 스레드로 실행될 프로그램 코드를 작성하여야 한다. 그 후 스레드를 실행시키면 스레드 스케줄링은 전적으로 JVM에 의해 이루어진다. 또한 스레드가 몇 개 존재하는지, 스레드로 실행되는 프로그램 코드의 메모리 위치는 어디인지, 스레드의 상태는 무엇인지 등등 많은 정보는 JVM이 관리한다. 즉 개발자는 스레드 코드를 작성하고 스레드 코드를 실행시킬 수 있도록 JVM에게 요청하고, JVM은 이후 스레드를 관리하게 된다. 아래 그림은 자바응용프로그램, 스레드 코드, JVM, 스레드 정보와의 관계를 보여준다.

 

✏️ 스레드 만들기

스레드 클래스의 주요 메소드

Thread의 메소드 내용
Thread()
Thread(Runnable target)
Thread(String name)
Thread(Runnable target, String name)
스레드 객체 생성
Runnable 객체인 target을 이용하여 스레드 객체 생성
이름이 name인 스레드 객체 생성
Runnable 객체를 이용하여, 이름이 name인 스레드 객체 생성
void run() 스레드 코드로서 JVM에 의해 호출, 개발자는 반드시 이 메소드를
오버라이딩하여야 함
. 이 메소드 종료 시 스레드도 종료
void start() JVM에게 스레드 실행을 시작하도록 요청
void interrupt() 스레드 강제 종료
static void yield() 다른 스레드에게 실행을 양보. 이때 JVM은 스레드 스케줄링을 시행하며
다른 스레드를 선택하여 실행시킴
void join() 스레드가 종료할 때까지 기다린다
long getId() 스레드의 ID 값 리턴
String getName() 스레드의 이름 리턴
int getPriority() 스레드의 우선순위 값 리턴, 1에서 10 사이
void setPriority(int n) 스레드의 우선순위 값을 n으로 변경
Thread.State getState() 스레드의 상태 값 리턴
static void sleep(long mills) 스레드는 mills 시간 동안 잔다
static Thread currentThread() 현재 실행 중인 스레드 객체의 래퍼런스 리턴

 

스레드 클래스 상속받아 스레드 만들기

// 스레드 클래스 작성: Thread 클래스 상속
class TimerThread extends Thread{ ...}

// 스레드 코드 작성: run() 메소드 오버라이딩
class TimerThread extends Thread{
    @Override
    public void run(){ // run()을 오버라이딩 하지 않으면 Thread 클래스에 작성된 run()이 실행되며
    	...            // 이 run()은 아무 일도 하지 않고 단순 리턴하기 때문에 스레드가 바로 종료됨
    }
}

// 스레드 객체 생성
TimerThread th = new TimerThread();

// 스레드 시작: start() 메소드 호출
th.start();

 

 

 

스레드 예제

// 1초 단위로 증가하는 스레드를 만들고 레이블을 이용하여 타이머 값 출력
package ex;
import java.awt.*;
import javax.swing.*;
class TimerThread extends Thread{
  private JLabel timerLabel; // 타이머 값 출력
    public TimerThread(JLabel timerLabel){
        this.timerLabel=timerLabel;
    }

    @Override
    public void run() {
        int n=0;
        while(true){
            timerLabel.setText(Integer.toString(n));
            n++;
            try{
                Thread.sleep(1000);
            }
            catch (InterruptedException e){ // 스레드가 잠을 자는 사이에 발생하는 Inter~예외를 처리할 catch블록이 필요함
                return;
            }
        }
    }
}
public class ThreadTimerEx extends JFrame{
    public ThreadTimerEx(){
        setTitle("Thread를 상속받은 타이머 예제");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Container c = getContentPane();
        c.setLayout(new FlowLayout());

        JLabel timerLabel = new JLabel();
        timerLabel.setFont(new Font("Gothic",Font.ITALIC,80));
        c.add(timerLabel);

        TimerThread th = new TimerThread(timerLabel);

        setSize(300,170);
        setVisible(true);

        th.start();
    }

    public static void main(String[] args) {
        new ThreadTimerEx();
    }
}

 

Runnable 인터페이스

Runnable은 클래스가 아닌 인터페이스로 java.lang.Runnable이며 run()추상 메소드 한 개만 가지고 있다.

 

Runnable 인터페이스로 스레드 만들기

// 스레드 클래스 선언: Runnable 인터페이스 구현
class TimerRunnable implements Runnable{...}

// 스레드 코드 작성: run() 메소드 오버라이딩
class TimerRunnable implements Runnable{
    @Override
    public void run(){
    	...
    }
}

// 스레드 객체 생성
Thread th = new Thread(new TimerRunnable());

// 스레드 시작: start() 메소드 호출
th.start();

 

Runnable 인터페이스 예제

// 1초 단위로 증가하는 스레드를 만들고 레이블을 이용하여 타이머 값 출력
package ex;
import javax.swing.*;
import java.awt.*;


class TimerRunnable implements Runnable{
    private JLabel timerLabel;
    public TimerRunnable(JLabel timerLabel){
        this.timerLabel=timerLabel;
    }

    @Override
    public void run() {
        int n=0;
        while(true){
            timerLabel.setText(Integer.toString(n));
            n++;
            try{
                Thread.sleep(1000);
            }
            catch (InterruptedException e){
                return;
            }

        }
    }
}
public class RunnableTimerEx extends JFrame{
    public RunnableTimerEx(){
        setTitle("Runnalbe을 구현한 타이머스레드 예제");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Container c = getContentPane();
        c.setLayout(new FlowLayout());

        JLabel timerLabel = new JLabel();
        timerLabel.setFont(new Font("Gothic",Font.ITALIC,80));
        c.add(timerLabel);

        TimerRunnable runnable = new TimerRunnable(timerLabel);
        Thread th = new Thread(runnable);

        setSize(250,150);
        setVisible(true);

        th.start();
    }

    public static void main(String[] args) {
        new RunnableTimerEx();
    }
}

 

깜빡이는 문자열을 가진 레이블 만들기

package ex;
import javax.swing.*;
import java.awt.*;

class FlickeringLabel extends JLabel implements Runnable {
    private long delay;

    public FlickeringLabel(String text, long delay){
        super(text);
        this.delay=delay;
        setOpaque(true);

        Thread th = new Thread(this);
        th.start();
    }

    @Override
    public void run() {
        int n=0;
        while(true){
        if(n==0)
            setBackground(Color.GREEN);
        else
            setBackground(Color.YELLOW);
        if(n==0)
            n=1;
        else
            n=0;
        try{
            Thread.sleep(delay);
        }
        catch (InterruptedException e){
            return;
        }
     }
   }
}

public class FlickeringLabelEx extends JFrame {
    public FlickeringLabelEx(){
        setTitle("FlickeringLabelEx 예제");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Container c= getContentPane();
        c.setLayout(new FlowLayout());

        FlickeringLabel fLabel = new FlickeringLabel("깜빡", 500);

        JLabel label = new JLabel("안깜빡");

        FlickeringLabel fLabel2 = new FlickeringLabel("여기도 깜빡", 300);

        c.add(fLabel);
        c.add(label);
        c.add(fLabel2);

        setSize(300,150);
        setVisible(true);
    }

    public static void main(String[] args) {
        new FlickeringLabelEx();
    }
}

 

스레드 정보

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

 

데몬 스레드(demon thread)와 사용자 스레드(user thread)

자바의 스레드는 위와 같이 두 가지 종류로 구분된다. 먼저 데몬 스레드는 응용프로그램이 실행되는 동안 관리를 위해 존재하는 스레드로, 가비지 컬렉션 스레드가 대표적이다.
두 번째는 사용자 스레드로서 응용프로그램에서 생성한 스레드인데 위에서 만들었던 예제들이 사용자 스레드이다. 그리고 main() 메소드를 실행하는 main 스레드 역시 사용자 스레드이며, 사용자 스레드는 Thread 클래스의 setDemon(true)을 호출하여 데몬 스레드로 바꿀 수 있다.

 

스레드 생성 시 주의사항

  • run() 메소드가 종료하면 스레드가 종료된다
  • 한 번 종료한 스레드는 다시 시작할 수 없다
  • 한 스레드에서 다른 스레드를 강제 호출할 수 있다

 

✏️ 스레드 생명 주기와 스케줄링

스레드 상태

  • NEW => 스레드가 생성되었지만 아직 실행할 준비가 되지 않은 상태, start() 메소드가 호출되면 RUUNABLE 상태가 됨
  • RUNNABLE =>스레드가 현재 실행되고 있거나, 실행 준비되어 스케줄링을 기다리는 상태
  • TIMED_WATTING => 스레드가 sleep(long n)을 호출하여 n밀리초동안 잠을 자는 상태
  • BLOCK => 스레드가 I/O 작업을 실행하여 I/O 작업의 완료를 기다리면서 멈춘 상태, 스레드가 I/O 작업을 실행하면 JVM이 자동으로 현재 스레드를 BLOCK 상태로 만들고 다른 스레드를 스케줄링
  • WATTING => 스레드가 어떤 객체 a에 대해 a.wait()을 호출하여, 다른 스레드가 a.notify(), a.notifyAll()을 불러줄 때까지 무한정 기다리는 상태
  • TERMINATED => 스레드가 종료한 상태로 더 이상 다른 상태로 변이할 수 없다

 

스레드의 우선순위와 스케줄링

JVM은 우선순위(priority)를 기반으로 스레드를 스케줄링하게 된고, 가장 높은 우선순위의 스레드를 먼저 실행시킨다. 만약 우선순위가 동일한 경우에는 돌아가면서 실행시키고 스레드의 우선순위 체계는 다음과 같다.

  • 최댓값(MAX_PRIORITY) = 10
  • 최솟값(MIN_PRIORITY) = 5
  • 보통값(NORMAL_PRIORITY) = 5

 

main()을 실행하는 main 스레드

JVM은 자바 응용프로그램을 실행하기 직전, 사용자 스레드를 하나 만들고, 이 스레드로 하여금 main() 메소드를 실행하도록 한다. 이 스레드가 바로 메인 스레드(main 스레드)이고 실행 시작 주소는 main() 메소드의 첫 코드가 된다. 아래 코드는 main()을 실행하는 main() 스레드를 확인하는 예제이다.

package ex;

public class ThreadMainEx {
    public static void main(String[] args) {
        long id = Thread.currentThread().getId();
        String name = Thread.currentThread().getName();
        int priority= Thread.currentThread().getPriority();

        Thread.State s = Thread.currentThread().getState();

        System.out.println("현재 스레드 이름: "+name);
        System.out.println("현재 스레드 ID: "+id);
        System.out.println("현재 스레드 우선순위 값: "+priority);
        System.out.println("현재 스레드 상태: "+s);
        
    }
}

 

 

✏️ 스레드 종료

스스로 종료

public void run(){
    ...
    return; // 스레드가 스스로 종료
    ...
}

 

강제 종료

// 스레드 A가 스레드 B를 강제 종료시키고자 하는 경우
B.interrupt(); // 스레드 B를 종료시킨다.

 

타미어 스레드 강제 종료시키기 예제

package ex;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

class TimerRunnable implements Runnable{
    private JLabel timerLabel;
    public TimerRunnable(JLabel timerLabel){
        this.timerLabel = timerLabel;
    }

    @Override
    public void run() {
        int n=0;
        while(true){
            timerLabel.setText(Integer.toString(n));
            n++;
            try{
                Thread.sleep(1000);
            }
            catch (InterruptedException e){
                return;
            }
        }
    }
}
public class ThreadInterruptEx extends JFrame{
    private Thread th;
    public ThreadInterruptEx(){
        setTitle("ThreadInterrupt 예제");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Container c = getContentPane();
        c.setLayout(new FlowLayout());

        JLabel timerLabel = new JLabel();
        timerLabel.setFont(new Font("Gothic", Font.ITALIC,80));

        TimerRunnable runnable = new TimerRunnable(timerLabel);
        th = new Thread(runnable);
        c.add(timerLabel);

        JButton btn = new JButton("Kill Timer");
        btn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                th.interrupt();
                JButton btn = (JButton) e.getSource();
                btn.setEnabled(false);
            }
        });

        c.add(btn);

        setSize(300, 170);
        setVisible(true);

        th.start();
    }

    public static void main(String[] args) {
        new ThreadInterruptEx();
    }
}

 

flag를 이용한 종료

 

flag를 이용하여 스레드 강제 종료 시키기

package ex;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;


class RandomThread extends Thread{
    private Container container;
    private boolean flag =false;
    public RandomThread(Container container) {
        this.container=container;
    }
    public void finish(){
        flag=true;
    }

    @Override
    public void run() {
        while(true){
            int x=((int)(Math.random()*container.getWidth()));
            int y=((int)(Math.random()*container.getHeight()));
            JLabel label = new JLabel("java");
            label.setSize(80,30);
            label.setLocation(x,y);
            container.add(label);
            container.repaint();
            try{
                Thread.sleep(300);
                if(flag==true){
                    container.removeAll();
                    label = new JLabel("finish");
                    label.setLocation(100,100);
                    label.setSize(80,30);

                    label.setForeground(Color.RED);
                    container.add(label);
                    container.repaint();
                    return;
                }
            }
            catch (InterruptedException e){
                return;
            }
        }
    }
}
public class ThreadFinishFlagEx extends JFrame{
    private RandomThread th;
    public ThreadFinishFlagEx() {
        setTitle("ThreadFinishFlag 예제");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Container c = getContentPane();
        c.setLayout(new FlowLayout());

        c.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                th.finish();
            }
        });
        setSize(300,200);
        setVisible(true);

        th=new RandomThread(c);
        th.start();
    }

    public static void main(String[] args) {
        new ThreadFinishFlagEx();
    }
}

 

 

✏️ 스레드 동기화

스레드 동기화의 필요성

다수의 스레드가 공유 자원 혹은 공유 데이터에 동시에 접근할 대 예상치 못한 결과를 낳을 수 있다. 이 같은 경우에 대비하여 스레드 동기화가 필요하게 되는데 멀티스레드의 동시 접근을 순차화하거나 락을 거는 방식으로 해결할 수 있다. 아래 그림은 스레드 동기화를 할 수 있는 방안을 제공해주고 있다. 이런 해결방안을 스레드 동기화(Thread Sychronization)이라고 하는데 이는 공유 데이터에 접근하고자 하는 다수의 스레드가 서로 순서대로 충돌 없이 공유 데이터를 배타적으로 접근하기 위해 상호 협력하는 것을 말한다. 공유 데이터에 대한 접근은 배타적이고 독점적으로 이루어져야 하며, 독점적으로 공유 데이터를 다루는 프로그램 코드를 임계 영역(critical section)이라고 부른다. 스레드 동기화는 다음과 같은 2가지 방법을 사용한다. synchronized로 동기화 블록 지정 / wait() - notify() 메소드로 스레드 실행 순서 제어

두 개의 스레드가 프린터에 동시에 쓰기를 수행할 때 -> 동시 접근을 순차화 하기!
학생들이 공유 집계판에 동시 접근하는 경우 -> 락을 걸어서 다른 스레드가 사용 못하게 만들기

 

synchronized 키워드

synchronized 키워드는 스레드 동기화를 위한 장치로서 임의의 코드 블록을 동기화가 설정된 임계 영역으로 지정한다. 이때 하나의 메소드를 통째로 임계 영역으로 지정하는 방법과, 임의의 코드 블록을 임계 영역으로 지정하는 방법이 있다. 어떤 방법을 사용하든지 synchronized블록은, 진입할 때 락(lock)이 걸리고 빠져나올 때 락이 풀리는(unlock)동작이 자동으로 이루어지도록 컴파일된다. 그러므로 락을 소유하지 못한 레드는 synchronized 블록 앞에서 락을 소유할 때까지 기다린다.

// 메소드 전체를 임계 영역으로 지정
synchronized void add(){ 
    int n = getCurrentSum();
    n+=10;
    setCurrentSum(n);
}
/*add 메소드가 호출되면 자동으로 동기화과 되고 한 스레드가 add() 메소드를 호출하여 실행하고 있는
동중에 다른 스레드가 add(), 메소드를 호출하면, 이 스레드는 첫 번쨰 스레드가 add() 실행을
마치고 완젼히 빠져나오기 전까지 대기하게 된다. */

// 코드 블록을 임계 영역으로 지정
void execute(){
	...
    synchronized(this){
    	int n = getCurrentSum();
        n+=10;
        setCurrentSum(n);
    }
	...
}
/* 한 스레드가 synchronized 블록 내의 코드를 실행하고 있을 때 다른 스레드가 이 블록을 실행하고자
하면, 먼저 실행 중인 스레드가 synchronized 블록의 실행을 마칠때까지 자동으로 대기한다. */

 

synchronized 사용 예제

// 집계판을 공유하기
package ex;
public class SynchronizedEx {
    public static void main(String[] args) {
        ShardBoard board = new ShardBoard(); // 집계판 공유 데이터 생성

        // 스레드 생성 시 집계판의 주소를 알려줘서 두 스레드는 집계판에 동시 접근
        Thread th1 = new StudentThread("pooreum",board);
        Thread th2= new StudentThread("parang",board);

        // 두 스레드 실행
        th1.start();
        th2.start();
    }
}

class ShardBoard{
    private int sum=0;
    synchronized public void add(){
        int n=sum;
        Thread.yield(); // 현재 실행 중인 스레드 양보
        n+=10;
        sum=n;
        System.out.println(Thread.currentThread().getName()+" : "+sum);
    }
    public int getSum(){
        return sum;
    }
}


class StudentThread extends Thread{
    private ShardBoard board; // 집계판의 주소

    public StudentThread(String name, ShardBoard board){
        super(name);
        this.board=board;
    }

    @Override
    public void run() {
        for(int i=0;i<10;i++)
            board.add();
    }
}

 

synchronized 사용하지 않았을 때의 예제

package ex;

public class SynchronizedEx {
    public static void main(String[] args) {
        ShardBoard board = new ShardBoard(); // 집계판 공유 데이터 생성

        // 스레드 생성 시 집계판의 주소를 알려줘서 두 스레드는 집계판에 동시 접근
        Thread th1 = new StudentThread("pooreum",board);
        Thread th2= new StudentThread("parang",board);

        // 두 스레드 실행
        th1.start();
        th2.start();
    }
}

class ShardBoard{
    private int sum=0;
     public void add(){
        int n=sum;
        Thread.yield(); // 현재 실행 중인 스레드 양보
        n+=10;
        sum=n;
        System.out.println(Thread.currentThread().getName()+" : "+sum);
    }
    public int getSum(){
        return sum;
    }
}


class StudentThread extends Thread{
    private ShardBoard board; // 집계판의 주소

    public StudentThread(String name, ShardBoard board){
        super(name);
        this.board=board;
    }

    @Override
    public void run() {
        for(int i=0;i<10;i++)
            board.add();
    }
}
// 위의 코드와는 다르게 add() 메소드에서 충돌이 일어난다.

 

 

✏️ wait(), notify(), notifyAll()을 이용한 스레드 동기화

producer-consumer 문제

스레드들이 synchronized를 이용하여 공유 데이터에 순차적으로 잘 접근하도록 만들어진 경우라도, 동기화가 필요한 상황이 있다. 대표적인 경우로 공유 메모리를 통해 두 스레드가 데이터를 주고받을 때, 공유 메모리에 대해 두 스레드가 동시에 접근하는 경우이다. 아래 그림이 예제이다.

 

Object의 wait(), notify() 메소드

wait() - notify()를 이용하면 앞의 producer-consumer 문제의 스레드 동기화를 해결할 수 있다. java.lang.Object 클래스는 스레드 사이에 동기화를 위한 3개의 메소드 wait(), notify(), notifyAll()를 제공한다.

  • wait() => 다른 스레드가 이 객체의 notify()를 불러줄 때까지 대기한다.
  • notify() => 이 객체에 대기 중인 스레드를 깨워 RUNNABLE 상태로 만든다. 2개 이상의 스레드가 대기 중이라도 오직 한 개의 스레드만 깨워 RUNNABLE 상태로 한다.
  • notifyAll() => 이 객체에 대기 중인 모든 스레드를 깨우고 모두 RUNNABLE 상태로 한다.

위의 메소드들이 호출되는 경우는 아래 사진과 같다.

 

wait(), notify()를 이용한 바 채우기

package ex;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

class MyLabel extends JLabel{
    private int  barSize=0;
    private int maxBarSize;
    public MyLabel(int maxBarSize){
        this.maxBarSize=maxBarSize;
    }

    @Override
    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 public void fill(){
        if(barSize==maxBarSize){
            try{
                wait();
            }
            catch (InterruptedException e){
                return;
            }
        }

        barSize++;
        repaint();
        notify();
    }
    synchronized public void consume(){
        if(barSize==0){
            try{
                wait();
            }
            catch (InterruptedException e){
                return;
            }
        }
        barSize--;
        repaint();
        notify();
    }
}

class ConsumerThread extends Thread{
    private MyLabel bar;
    public ConsumerThread(MyLabel bar){
        this.bar=bar;
    }

    @Override
    public void run() {
        while(true){
            try{
                sleep(200);
                bar.consume();
            }
            catch (InterruptedException e){
                return;
            }
        }

    }
}
public class TabAndThreadEx extends JFrame {
    private MyLabel bar = new MyLabel(100);

    public 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() {
            @Override
            public void keyPressed(KeyEvent e) {
                bar.fill();
            }
        });
        setSize(350,200);
        setVisible(true);

        c.setFocusable(true);
        c.requestFocus();
        ConsumerThread th = new ConsumerThread(bar);
        th.start();
    }

    public static void main(String[] args) {
        new TabAndThreadEx("아무거나 눌러서 바 채우기");
    }
}
반응형

'PL > JAVA' 카테고리의 다른 글

[JAVA] 자바 - 네트워크  (0) 2024.06.17
[JAVA] 자바 - 고급 스윙 컴포넌트  (0) 2024.06.16
[Java] 자바 - 그래픽  (0) 2024.06.14
[JAVA] 자바 - 스윙 컴포넌트  (2) 2024.06.13
[JAVA] 자바 - 이벤트  (0) 2024.06.12
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함