다시 자바의 동기화 초리 방법에 대해 간단히 다시 짚고 넘어가도록 하겠습니다.
동기화 처리 방법
- synchronized
- 메서드 자체에 동기화 처리
- public synchronized void add() { ~ }
- 여러개의 Thread들이 공유객체의 메소드를 사용할 떄 메소드에 synchronized가 붙어있을 경우 먼저 호출한 메소드가 객체의 사용권(Monitoring Lock)을 얻습니다.
- 메소드 앞에 synchronized를 붙혀서 실행해 보면, 메소드 하나가 모두 실행된 후에 다음 메소드가 실행됩니다.
- 해당 모니터링 락은 메소드 실행이 종료되거나, wait()와 같은 메소드를 만나기 전까지 유지됩니다.
- 다른 쓰레드들은 모니터링 락을 놓을때까지 대기합니다.
- 동기화 블럭
- synchornized(this) { ~ }
- -synchronized를 메소드에 붙혀서 사용할 경우, 메소드의 코드가 길어지면, 마지막에 대기하는 쓰레드가 너무 오래 기다리는 것을 막기위해 블럭을 사용
- 해당 this가 동기화 처리가 됨
- this만 동기화 블럭에 접근 가능
- 블럭에서 빠져나오기 전까지는 동기화 블럭 내 애들만 공유객체에 접근가능
- 이때는 blocked상태이며 블럭을 빠져나오면서 runnable로 바뀌게 됨
- 메서드 자체에 동기화 처리
wait(), notify()
- wait()
- 동기화 영역에서 락을 풀고 Wait-Set영역(공유객체별 존재)으로 이동한다
- notify()
- Wait-Set영역에 있는 쓰레드를 깨워서 실행할 수 있도록 합니다.
예시
- 내가 스레드 1번, 공유객체 문열고 들어가, 작업이 다 끝나면 락 풀어놓자 ( 사람들 들어올 수 있게 )
- 2번이 거기 들어갔어, 잠궈놓고 일보고 다시 나와서 다시 풀어놓고 나가고
- wait-set영역
- 대기실 같은 곳, 내가 지금 동기화 영역에서 할 일 없어가지고 일시정지할라고 대기실 ㄱ ㄱ
- 대기실은 공유객체에 대한 대기실, 각 공유객체마다 대기실이 따로따로 있음
- 대기실에서 기다리는 동안은(=공유객체에서 나올때) 락풀고 나와야함 다른사람들 들어갈 수 있게
- notify안받으면 영원히 대기실에...
- 딴사람이 작업하다가 할 일 다하면 나 notify로 깨우고나도 일어나서 일하다가 다시 wait하면서 wait-set에서 기다리고 또 notify하고 일하고
ThreadB.java
package com.company.Thread;
public class ThreadB extends Thread{
// 해당 스레드가 실행되면 자기 자신의 모니터링 락을 획득
// 5번 반복하면서 0.5초씩 쉬면서 total에 값을 누적
// 그 후에 notify()메소드를 호출하여 wait하고 있는 스레드를 깨움
int total;
@Override
public void run() {
synchronized (this) {
for(int i = 0; i < 5; i++) {
System.out.println(i + "을 더합니다.");
total += i;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
notify();
}
}
}
ThreadA.java
package com.company.Thread;
public class ThreadA {
public static void main(String[] args) {
// 앞에서 만든 쓰레드 B를 만든 후 start
// 해당 쓰레드가 실행되면, 해당 쓰레드는 run메소드 안에서 모니터링 락을 획득
ThreadB b = new ThreadB();
b.start();
// b에 대하여 동기화 블럭을 설정
// 만약 main쓰레드가 아래의 블록을 위의 Thread보다 먼저 실행되었다면 wait을 하게 되면서 모니터링 락을 놓고 대기
synchronized (b) {
try {
System.out.println("b가 완료될때까지 기다립니다.");
b.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Total is: " + b.total);
}
}
}
//b가 완료될때까지 기다립니다.
//0을 더합니다.
//1을 더합니다.
//2을 더합니다.
//3을 더합니다.
//4을 더합니다.
//Total is: 10
그리고 다음으로는 다음과 같이 키를 누르면 마젠타 색으로 1/100씩 채워지는 GUI프로그램을 작성해 보도록 하겠습니다.
MyLabel.java
package com.company.Thread;
import javax.swing.*;
import java.awt.*;
public class MyLabel extends JLabel {
private int barSize = 0; // 바의 크기
private int maxBarSize;
public 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();
}
}
일단 다음과 같이 화면에 있는 것 처럼 하나의 바를 만들었습니다.
TabAndThreadEx.java
package com.company.Thread;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
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);
ConsumerThread th = new ConsumerThread(bar);
th.start();
}
public static void main(String[] args) {
new TabAndThreadEx(
"아무키나 빨리 눌러 바 채우기");
}
}
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;
}
}
}
}
다음과 같이 기본적인 틀과 기본적인 기능을 완성했습니다.
우선 Thread한개를 만들었는데, 여기서 while(true)만큼 myLabel의 consume메소드를 호출해 줍니다. 그리고 만약 barSize가 0이라면 그냥 wait을 해서 wait-set풀에 들어가서 기다리게 합니다. ( 공유객체를 MyLabel로 설정 ) 그리고 만약 키를 누르면 bar객체의 fill메서드를 호출하게 됩니다. 이는 기본적으로 barSize를 1만큼 늘리고 notify()를 해서 wait-set에서 대기중인 메서드를 다시 실행시키게 합니다. 그리고 fill은 바가 꽉 채워졌다면. wait을 하게 됩니다. 그리고 다시 consume에서 notify를 해서 끝나는 원리입니다.
'School > Java Programming' 카테고리의 다른 글
Java Programming - Socket (0) | 2022.06.02 |
---|---|
[ Java Programming ] IOStream && FileStream (0) | 2022.05.26 |
Java Programming - Thread basic (0) | 2022.05.19 |
Java Programming - Collection Framework (0) | 2022.04.07 |
Java Programming - Basic API Class (0) | 2022.03.24 |