JAVA공부 [11. JAVA SWING(5)]
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();
}
}
댓글남기기