1. 🔥 쓰레드와 스윙

1.1. 실습 - 방향키로 박스 이동하기

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

class simpleAniBox extends JPanel implements KeyListener, Runnable{
    int x = 100;
    int y = 100;
    int size = 50;

    simpleAniBox(){
        this.addKeyListener(this);
        this.setFocusable(true);
        this.requestFocus();
    }
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        g.fillRect(x, y, size, size);
    }
    public void run() {}
    public void keyTyped(KeyEvent e) {}
    public void keyReleased(KeyEvent e) {}
    public void keyPressed(KeyEvent e) {
        switch(e.getKeyCode()){
            case KeyEvent.VK_UP:
                y -= 10;
                break;
            case KeyEvent.VK_DOWN:
                y += 10;
                break;
            case KeyEvent.VK_LEFT:
                x -= 10;
                break;
            case KeyEvent.VK_RIGHT:
                x += 10;
                break;
        }
        repaint();
    }
   
}

public class App extends JFrame{
    App(){
        setTitle("DrawBoxAni");
        setSize(500,500);
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        add(new simpleAniBox());

        setVisible(true);
    }
    public static void main(String[] args) throws Exception {
        new App();
    }
}

쓰레드를 다루기 전에 미리 예제파일을 다룬다 밑으로 다루는 실습은 이 코드를 사용하여 실습함.

1.2. 실습 - 박스 움직임 && 벽 튕김

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

class simpleAniBox extends JPanel implements KeyListener, Runnable{
    int x = 100;
    int y = 100;
    int vy = 0;
    int vx = 0;
    int size = 50;

    simpleAniBox(){
        this.addKeyListener(this);
        this.setFocusable(true);
        this.requestFocus();

        Thread t = new Thread(this);
        t.start();
    }
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        g.fillRect(x, y, size, size);
    }
    public void run() {
        try {
            while(true){
                x += vx;
                y += vy;

                if(x < 0) {x = 0; vx = -vx;}
                if(x > this.getWidth() - size) {x = this.getWidth() - size; vx = -vx;}
                if(y < 0) {y = 0; vy = -vy;}
                if(y > this.getHeight() - size) {y = this.getHeight() - size; vy = -vy;}
                Thread.sleep(30);
                repaint();
            }
        } catch (InterruptedException e) {
            return;
        }
    }
    public void keyTyped(KeyEvent e) {}
    public void keyReleased(KeyEvent e) {}
    public void keyPressed(KeyEvent e) {
        switch(e.getKeyCode()){
            case KeyEvent.VK_UP:
                vy -= 10;
                vx = 0;
                break;
            case KeyEvent.VK_DOWN:
                vy += 10;
                vx = 0;
                break;
            case KeyEvent.VK_LEFT:
                vx -= 10;
                vy = 0;
                break;
            case KeyEvent.VK_RIGHT:
                vx += 10;
                vy = 0;
                break;
        }
        repaint();
    }
   
}

public class App extends JFrame{
    App(){
        setTitle("DrawBoxAni");
        setSize(500,500);
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        add(new simpleAniBox());

        setVisible(true);
    }
    public static void main(String[] args){
        new App();
    }
}

sleep을 이용한 update문 구현,,!

INTERRUPT

  • 쓰레드를 만들면 start로 시작하며 중단의 개념이 없다(end가 존재하지 않음)
    • INTERRUPT로 멈출 수 있을 때를 코더가 직접 판단하여 종료해야함(쓰레드가 쉬고 있을 때..!)
    • 사용자가 INTERRUPT 걸면 sleep진입 시 바로 종료됨
    • INTERRUPT로 종료시 쓰레드는 삭제되므로 같은 작업의 실행의 경우 재생성해야함

1.3. 실습 - INTERRUPT

import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;


class SimpleBoxAnimationPanel extends JPanel implements KeyListener, Runnable, ActionListener{
 int x = 100;
 int y = 100;
 int size = 50;
 int vx = 0;   // velocity
 int vy = 0;
 
 Thread myThread;
 
 SimpleBoxAnimationPanel(){
  this.addKeyListener(this);
  this.setFocusable(true);
  this.requestFocus();
  
  myThread = new Thread(this);
  myThread.start();
  
  JButton b = new JButton("Stop");
  b.addActionListener(this);
  add(b);
  
 }
 @Override
 public void paintComponent(Graphics g) {
  super.paintComponent(g);
  g.fillRect(x,  y,  size,  size);
 }
 public void keyTyped(KeyEvent e) {  }
 public void keyReleased(KeyEvent e) {}

 @Override
 public void keyPressed(KeyEvent e) {
  switch(e.getKeyCode()) {
  case KeyEvent.VK_UP:  vy=-10;  break;
  case KeyEvent.VK_DOWN:  vy=10;  break;
  case KeyEvent.VK_LEFT:  vx=-10;  break;
  case KeyEvent.VK_RIGHT:  vx=10;;  break;
  default:        break;  
  }
  repaint();
  
 }
 @Override
 public void run() {
  try {
   while(true) {
    x+=vx;
    y+=vy;
    
    if(x<0) {x=0; vx = -vx;}
    if(x>getWidth()-size) { x=getWidth()-size; vx=-vx;}
    if(y<0) {y=0; vy=-vy;}
    if(y>getHeight()-size) {y=getHeight()-size; vy=-vy;}
    
    
    Thread.sleep(30);
    repaint();
   } 
  }
  catch(InterruptedException e) {
   return;
  }
 }
 @Override
 public void actionPerformed(ActionEvent e) {
  if(myThread.isAlive()) {
   JButton b = (JButton)(e.getSource());
   b.setText("Start");
   myThread.interrupt();
  }
  else {
   JButton b = (JButton)(e.getSource());
   b.setText("Stop");

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

public class App extends JFrame{

 App(){
  setTitle("Simple Box Animation");
  setSize(500,500);
  setDefaultCloseOperation(EXIT_ON_CLOSE);
  add(new SimpleBoxAnimationPanel());
  setVisible(true);
 }
 
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  new App();
 }
}

2. 병렬 처리

cpu의 개수가 2개 이상이라면 병렬처리가 가능해지는데 병렬처리란, 간단하게 1~10000까지 더하는 작업을 분활하여 한번에 더하는 작업같은걸 의미한다.

속도가 빨라짐

  • 이때 발생하는 문제점이 병렬처리의 속도가 각각 달라서 합치는 부분에서의 문제점이 발생하는데 이를 해결하는 명령어(java관점) JOIN이 있다.

2.1. 실습 - 병렬 처리 문제점

class Mycal extends Thread{
    int start;
    int end;
    int sum = 0;
    Mycal(int s, int e){
        start = s;
        end = e;
    }
    public void run(){
        for(int i = start;  i < end; i++)
            sum += i;
    }
}

public class App{
    public static void main(String[] args){
        Mycal t = new Mycal(0, 100);
        t.start();

        System.out.println("Sum ="+t.sum);
    }
}

위의 코드는 sum = 0이 출력된다.

  • 문제점은 main쓰레드는 t쓰레드를 만들고 System을 찍고 종료된다.(따로 sleep이나 무한루프가 없기 때문에)
    • 이때 t가 생긴 시점에서 sum은 0이기 때문에 바로 0이 출력되는 문제점이 발생!
    public static void main(String[] args){
        Mycal t = new Mycal(0, 100);
        t.start();

        try {
            t.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println("Sum ="+t.sum);
    }
  • join은 해당 쓰레드가 종료될때 까지 기달린다. 즉, 다른 쓰레드에 간섭가능
    • sleep과 마찬가지로 try, catch문 사용해야함 -> 종료 시점을 모르기 때문에

2.2. 실습 - 기본 병렬 처리

class Mycal extends Thread{
    int start;
    int end;
    int sum = 0;
    Mycal(int s, int e){
        start = s;
        end = e;
    }
    public void run(){
        for(int i = start;  i < end; i++)
            sum += i;
    }
}

public class App{
    public static void main(String[] args){
        Mycal t1 = new Mycal(0, 2500);
        Mycal t2 = new Mycal(2501, 5000);
        Mycal t3 = new Mycal(5001, 7500);
        Mycal t4 = new Mycal(7501, 10000);
        
        t1.start();
        t2.start();
        t3.start();
        t4.start();

        try {
            t1.join();
            t2.join();
            t3.join();
            t4.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.print("Sum : ");
        System.out.println(t1.sum+t2.sum+t3.sum+t4.sum);
    }
}

0~10000까지의 합 과정을 4분활하여 각각의 쓰레드 종료까지 기달린 뒤 합쳐서 출력하는 모습!

t1.join(1)의 사용법은 1밀리세컨트까지 기달린 뒤 넘어간다. 만약 종료되지 않았더라도 넘어감

3. 동기화

위와 달리 다른 쓰레드끼리의 동기화를 다룬다.

3.1. 실습 - 동기화의 문제점(1)

class Mycal extends Thread{
    BankAccount bank;
    Mycal(String name, BankAccount b){
        super(name);
        bank = b;
    }

    public void run(){
        for(int i = 0; i<10; i++)
            bank.add(10, this);
    }   
}

class BankAccount{
    int money = 0;
    void add(int deposit, Mycal t){
        int cur = money;
        cur = cur + deposit;
        money = cur;
        System.out.println(t.getName()+": Money="+money);
    }
}

public class App{
    public static void main(String[] args){
        BankAccount back = new BankAccount();
        Mycal t1 = new Mycal("A", back);
        Mycal t2 = new Mycal("B", back);
        t1.start();
        t2.start();
        
    }
}

위의 결과값은 정상적으로 200으로 찍히지만 과정에 오류가 발생한다.

전 실습과 비슷하게 컴퓨터는 엄청 빠르기 때문에 동시에 bank에 접근하게 된다.

t1이 print할때 이미 t2가 값을 증가 시켜서 출력값이 찍히는 동시에 증가하기 때문에 꼬이게 된다.

3.2. 실습 - 동기화의 문제점(2)

class Mycal extends Thread{
    BankAccount bank;
    Mycal(String name, BankAccount b){
        super(name);
        bank = b;
    }

    public void run(){
        for(int i = 0; i<100; i++){
            bank.add(10, this);
        }
    }   
}

class BankAccount{
    int money = 0;
    void add(int deposit, Mycal t){
        int cur = money;
        cur = cur + deposit;
        try {
            Thread.sleep((int)(Math.random()*10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        money = cur;
        System.out.println(t.getName()+": Money="+money);
    }
}

public class App{
    public static void main(String[] args){
        BankAccount back = new BankAccount();
        Mycal t1 = new Mycal("A", back);
        Mycal t2 = new Mycal("B", back);
        Mycal t3 = new Mycal("C", back);
        t1.start();
        t2.start();
        t3.start();
        
    }
}
  • 위 처럼 시간을 가지고 접근하게 되면 최종 값까지 문제가 생기게 되며 동기화의 치명적인 문제가 된다.

  • 동시에 접근해서 값을 건드리면 안되는 영역을 임계영역(Critical Section)이라고 칭하고 해당 영역은 동시 접근에 대한 제한을 걸어야 한다.

    • lock과 unlock을 가지고 접근 방식을 설정해줘야한다.

synchronized키워드를 사용하면 해당 메서드를 접근할 때 lock이 걸리고 끝나면 unlock이 된다.

3.3. 실습 - synchronized

synchronized void add(int deposit, Mycal t){
        int cur = money;
        cur = cur + deposit;
        try {
            Thread.sleep((int)(Math.random()*10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        money = cur;
        System.out.println(t.getName()+": Money="+money);
    }

synchronized키워드를 함수 전체에 걸어 함수 자체에 대한 동시 접근을 제한함

4. 정리

  • 멀티 쓰레드 프로그래밍 주의 사항
    • 다수의 쓰레드가 공유 데이터에 동시에 접근하는 경우는 예상치 못한 값을 발생시킴
  • 쓰레드 동기화
    • 위의 문제점의 해결책이며 쓰레드의 접근에 대한 제한을 걸어 순차적 접근을 만들어 줌
    • 임계영역에 대한 베타적 독접 접근을 보장해준다.
    • 무분별한 사용은 멀티 쓰레드 프로그래밍으로 볼 수 없다!

5. wait-notify

producer-consumer 문제점이 존재 공급과 소비의 쓰레드가 존재한다면 서로의 데이터를 동시에 접근하는 문제가 발생

  • wait, notify로 해결가능
    • wait은 공급 쓰레드에게 소비 쓰레드가 종료되었음을 알리고
    • notify는 소비 쓰레드에게 공급 가능함을 알리고 실행 함
  • wait(): 다른 쓰레드가 notify를 호출할 때까지 기달림
  • notify(): wait()을 호출하여 대기중인 쓰레드를 깨우고 runnable상태로 만든다.
    • 오직! 2개이상의 쓰레드가 대기중이라도 한 쓰레드만 깨운다.
  • notifyAll(): wait()를 호출하여 대기중인 모든 쓰레드를 깨우고 모두 Runnable 상태로 만든다.

Synchronized블록 내에서만 사용되어야 함

5.1. 실습 - wait-notify 사용법

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;


// button 그리기만 존재 쓰레드 x
class ProgressButton extends JButton {
 int value = 0;
 Thread t= null;
 ProgressButton(String str){
  super(str);   
 }
 @Override
 public void paintComponent(Graphics g) {
  super.paintComponent(g);
  int w = (int)(getWidth() * value / 100.0f);
  g.setColor(Color.red);
  g.fillRect(0,0, w, getHeight());
 }
}

class ThreadWaitNotifyPanel extends JPanel implements ActionListener{

 ProgressButton [] but = new ProgressButton[4];
 JButton wakeButton;
 ThreadWaitNotifyPanel(){
  setLayout(null);
  for(int i=0; i<but.length; i++) {
   String str = "button" + (i+1);
   ProgressButton but = new ProgressButton(str);
   but.setBounds(50,50*i+50, 400, 30);
   but.addActionListener(this); // panel에서 생성자로 버튼마다 각각 리스너 등록
   add(but);
  }
  wakeButton = new JButton("Wake");
  wakeButton.addActionListener(this);
  wakeButton.setBounds(50,300,400,30);
  add(wakeButton);   
 }
 // synchronized로 감싸야지 wait, notify사용가능
 synchronized void addValue(ProgressButton b) throws InterruptedException {
  if(b.value == 0) wait(); //메서드 호출 시 0이므로 wait 즉, 살아있지만 notify까지 대기
  b.value ++;
  if(b.value >100) notify();
 } 
 
 private class myThread extends Thread {
  ProgressButton but; //생성자로 레퍼런스 변수 연결
  myThread(ProgressButton in){
   but = in;
  }
  @Override
  public void run() { 
   try {
    while(but.value<=100) {// 값추가 연산부분
     addValue(but);
     Thread.sleep(10); 
     but.repaint();
    }
    //notify();
   }
   catch(InterruptedException e) {
    return;
   }
  }
 }
 
 @Override
 synchronized public void actionPerformed(ActionEvent e) {
  if(e.getSource() == wakeButton) {
   notify();
  } // wake버튼은 쓰레드 한개를 깨운다.
  else {
   ProgressButton b = (ProgressButton)e.getSource();
   
   if(b.t != null && b.t.isAlive() == true) return;
   b.t = new myThread(b);
   b.t.start();
  } // 그냥 버튼은 쓰레드를 만들고 시작함 하지만 addvalue부분에서 잠들어버림
 }
}

public class App extends JFrame{
 App(){
  setTitle("Wait and notity");
  setSize(500,500);
  
  add(new ThreadWaitNotifyPanel());
  
  setDefaultCloseOperation(EXIT_ON_CLOSE);
  setVisible(true);
 }
 
 public static void main(String[] args) {
  new App();
 }
}

버튼 4개와 wake버튼이 생긴다.

  • 그냥 wake버튼을 클릭해도 아무런 이벤트가 발생하지않음 -> 버튼을 클릭해서 쓰레드를 만들지 않았기 때문에(run에서 돌아감)
  • 버튼을 클릭 후 wake버튼을 클릭하면 클릭했던 버튼중 하나가 깨어나서 돌아가고 나머지 버튼들도 깨움(클릭한 버튼만)

Dynamic(물리 적용)

실습 - 공 튀기기

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;

class MyBall{
    float x,y;
    float vx, vy;
    float ax, ay;
    float r;
    Color c;
    MyBall(){
        x = 100;
        y = 100;
        r = 20;
        c = Color.RED;
        vy = 0;
        vx = 200;
        ax = 0;
        ay = 100.0f;
    } /// 사용할 물리값
    void draw(Graphics g){
        g.setColor(c);
        g.fillOval((int)(x - r), (int)(y- r), (int)(2*r), (int)(2*r));
    }
    void update(float dt){
        vx = vx + ax *dt;
        vy = vy + ay*dt;
        x = x + vx*dt;
        y = y + vy*dt;
    } // 적분
    void collisionHandling(Dimension d){
        int w = d.width;
        int h = d.height;

        if(y + r > h) {y = h - r; vy = -vy*0.8f;}
        if(x + r > w) {x = w - r; vx = -vx*0.8f;}
        if(x - r < 0) {x = r; vx = -vx*0.8f;}
    } // 충돌 검사
}

class FallingBall extends JPanel implements Runnable{
    MyBall b;
    FallingBall(){
        b = new MyBall();

        Thread t = new Thread(this);
        t.start();
    }
    @Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        b.draw(g);
    }
    @Override
    public void run() {
        try { 
            while(true){
                b.update(0.033f);

                b.collisionHandling(this.getSize());

                repaint();
                Thread.sleep(30);
            }
        } catch (InterruptedException e) {
            return;
        }
    }
}

public class App extends JFrame{
 App(){
        setTitle("Falling Ball");
        setSize(800,800);
        add(new FallingBall());
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }
 public static void main(String[] args) {
  new App();
 }
}

실습 - 공 튀기기++

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;

import javax.swing.*;

class MyBall{
    float x,y;
    float vx, vy;
    float ax, ay;
    float r;
    Color c;
    MyBall(){
        x = 400;
        y = 100; //생성 시 랜덤값 적용
        r = (float)(Math.random()*10) + 1;
        c = new Color((int)(Math.random()*255),(int)(Math.random()*255),(int)(Math.random()*255));
        vy = -1 * (float)(Math.random()*100.0f) + 400;
        vx = (float)(Math.random()*200.0f) - 100;
        ax = 0;
        ay = 100.0f;
    }
    void draw(Graphics g){
        g.setColor(c);
        g.fillOval((int)(x - r), (int)(y- r), (int)(2*r), (int)(2*r));
    }
    void update(float dt){
        vx = vx + ax *dt;
        vy = vy + ay*dt;
        x = x + vx*dt;
        y = y + vy*dt;
    } // 프레임 업데이트 문
    void collisionHandling(Dimension d){
        int w = d.width;
        int h = d.height;

        if(y + r > h) {y = h - r; vy = -vy*0.8f;}
        if(x + r > w) {x = w - r; vx = -vx*0.8f;}
        if(x - r < 0) {x = r; vx = -vx*0.8f;}
    } // 충돌검사 및 속도 감소
}

class FallingBall extends JPanel implements Runnable, KeyListener{
    ArrayList<MyBall> balls;
    FallingBall(){
        balls = new ArrayList<MyBall>();

        Thread t = new Thread(this);
        t.start();

        addKeyListener(this);
        setFocusable(true);
        requestFocus();
    }
    @Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        for(MyBall b : balls)
            b.draw(g);
    }
    @Override
    public void run() {
        try {
            while(true){
                for(MyBall b : balls)
                    b.update(0.033f);
                for(MyBall b : balls)
                    b.collisionHandling(getSize());
                repaint();
                Thread.sleep(30);
            }
        } catch (InterruptedException e) {
            return;
        }
    }
    public void keyTyped(KeyEvent e) {}
    public void keyReleased(KeyEvent e) {}
    @Override
    public void keyPressed(KeyEvent e) {
        if(e.getKeyCode() == KeyEvent.VK_SPACE){
            balls.add(new MyBall());
        }
        
    }
}

public class App extends JFrame{
 App(){
        setTitle("Falling Ball");
        setSize(800,800);
        add(new FallingBall());
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }
 public static void main(String[] args) {
  new App();
 }
}

태그: ,

카테고리:

업데이트:

댓글남기기