다시 자바의 동기화 초리 방법에 대해 간단히 다시 짚고 넘어가도록 하겠습니다.
동기화 처리 방법
- 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 |