본문 바로가기
뒷북 정리 (국비 교육)/java

[Java] step16. Thread

by 규글 2022. 4. 25.

step16. Thread

- 01

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

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

public class Frame01 extends JFrame implements ActionListener{	
	// 생성자
	public Frame01() {
		// 레이아웃 설정 
		setLayout(new BorderLayout());
		// 패널을 프레임의 상단에 배치 
		JPanel panel=new JPanel();
		panel.setBackground(Color.YELLOW);
		add(panel, BorderLayout.NORTH);
		// 버튼을 패널에 추가 하고 
		JButton alertBtn=new JButton("알림 띄우기");
		panel.add(alertBtn);
		// 버튼에 리스너 등록하기
		alertBtn.addActionListener(this);
	}
	
	public static void main(String[] args) {
		// MyFrame 클래스를 이용해서 객체 생성하고 참조값을 지역변수 frame 에 담기 
		Frame01 frame=new Frame01();
		// 프레임의 제목 설정
		frame.setTitle("Frame01");
		// 프레임을 닫으면 자동으로 프로세스가 종료 되도록 한다.
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setBounds(100, 100, 500, 500);
		frame.setVisible(true);
	}

	@Override
	public void actionPerformed(ActionEvent arg0) {
		JOptionPane.showMessageDialog(this, "알림~~~");
		System.out.println("알림창이 닫아 졌습니다.");
		
		try {
			System.out.println("10 초동안 무언가 준비 작업을 해요");
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

 이 예시의 경우는 실행의 흐름이 한 줄기이다. 그래서 실행 중간에 어느 단계에서 붙들리게 된다면 더 이상의 진행이 불가능하다. 이런 실행의 흐름을 thread라고 한다. 작업 단위라고도 한다.

 예시를 자세히 보자. Run으로 실행하게 되면 실행 단계가 main method를 지나서 어디선가 멈춰서 기다리고 있는 것이라고 했다. 그러다가 버튼을 누르게 되면 그 누른 action에 대한 단계로 넘어가는 것이다. 그런데 예시의 버튼을 누르면 알람창이 생기는데, 해당 알람창을 닫으면 10초동안 아무런 작업을 할 수 없게 된다. 이렇듯 실행의 흐름이 한 줄기인 상황에 10초동안 붙들리게 된 것이다.

 프로그래밍을 하다보면 상황에 따라 이런 thread를 여러 개 만들어야 할 수도 있다.

 

- 02

/*
 *  새로운 스레드 만드는 방법
 *  
 *  1. Thread 클래스를 상속 받은 클래스를 정의한다.
 *  2. run() 메소드를 오버라이드 한다.
 *  3. run() 메소드 안에서 새로운 스레드에서 해야할 작업을 코딩한다.
 *  4. 만든 클래스로 객체를 생성하고 해당 객체의 start() 메소드를 호출하면 새로운 스레드가
 *     시작된다.
 */
public class CountThread extends Thread{
	@Override
	public void run() {
		// run()  메소드 안쪽이 새로운 작업단위가 된다. 
		int count=0;
		while(true) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			count++; //count 를 1 증가 시킨다.
			System.out.println("현재 카운트:"+count);
			if(count==10) {// 카운트가 10이 되면 
				break;// 반복문 탈출 
			}
		}
	}
}

 새로운 thread를 만드는 첫 번째 방법이다. 가장 먼저 Thread class를 extends하고, run( ) method를 override 해서 새로운 thread에서 수행할 작업을 코딩하는 것이다. 이렇게 만든 class를 객체로 만들어서 해당 객체의 start( ) method를 이용해서 실행하면 새로운 thread가 시작되는 것이다.

 다시 한 번 break가 나왔는데, 이 친구는 가장 가까운 반복문 하나를 탈출할 수 있게 해준다. 잊었다면 기억해두자.

 

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

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

import test.mypac.CountThread;


public class Frame02 extends JFrame implements ActionListener{	
	// 생성자
	public Frame02() {
		// 레이아웃 설정 
		setLayout(new BorderLayout());
		// 패널을 프레임의 상단에 배치 
		JPanel panel=new JPanel();
		panel.setBackground(Color.YELLOW);
		add(panel, BorderLayout.NORTH);
		// 버튼을 패널에 추가 하고 
		JButton countBtn=new JButton("1~10까지 세기");
		panel.add(countBtn);
		// 버튼에 리스너 등록하기
		countBtn.addActionListener(this);
	}
	
	(main method 중략)

	@Override
	public void actionPerformed(ActionEvent arg0) {
		// thread object 생성해서
		CountThread t=new CountThread();
		// 새로운 thread 시작시키기
		t.start();
		/*
		 *  start() method는 run() method return과 상관없이
		 *  바로 return하기 때문에 아래의 내용이 바로 console 창에 출력된다.
		 */
		System.out.println("새로운 스레드를 시작 했습니다.");
	}
}

 사실 이 thread 객체를 받으면 start( ) 도 있고 run( ) 도 있는데, run( ) 의 경우는 그냥 method를 호출한 것 뿐이지 새롭게 만들어진 작업 단위가 아니다. 새로운 작업 단위를 만들려고 한다면 start( ) 를 사용해야 한다. 그러면 main thread는 먼저 제 갈 길을 가고, 이후에 다시금 동작하게 되면 새로운 thread가 뒤이어 독립적으로 갈 길을 가게 된다.

 Process는 하나, thread는 여러 개인 예시이다. 앞으로 프로그래밍을 할 때 thread를 쓸 일은 거의 없겠지만, 일단 배우고 이런 것이 있다고 알고 넘어간다.

 

- 03

/*
 *  새로운 스레드 만드는 방법2
 *  
 *  1. Runnable 인터페이스를 구현한 클래스를 정의한다.
 *  2. run() 메소드를 강제 오버라이드 한다.
 *  3. Thread 클래스로 객체를 생성하면서 해당클래스로 만든 객체를 생성자의 인자로 전달한다.
 *  4. Thread 클래스로 만든 객체의 start() 메소드를 호출해서 스레드를 시작 시킨다.
 */
public class CountRunnable implements Runnable{
	@Override
	public void run() {
		// run()  메소드 안쪽이 새로운 작업단위가 된다. 
		int count=0;
		while(true) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			count++; //count 를 1 증가 시킨다.
			System.out.println("현재 카운트:"+count);
			if(count==10) {// 카운트가 10이 되면 
				break;// 반복문 탈출 
			}
		}		
	}
}

 새로운 thread를 만드는 두 번째 방법이다. Runnable interface를 implements하고, run( ) method를 강제로 override 해서 원하는 작업을 코딩한다.

 

	@Override
	public void actionPerformed(ActionEvent arg0) {
		// Runnable 인터페이스를 구현한 클래스로 객체 생성
		Runnable r=new CountRunnable();
		// Thread 객체를 생성하면서 생성자의 인자로 전달한다.
		Thread t=new Thread(r);
		// start() 메소드를 호출해서 새로운 스레드를 시작한다.
		t.start();
		System.out.println("새로운 스레드를 시작 했습니다.");
	}

 그리고 Thread 객체를 생성하면서 새롭게 만든 객체를 인자로 전달해준 다음, start( ) method를 호출해서 thread를 새롭게 시작시킬 수 있다. 이전 예시와의 차이라고 한다면 interface를 사용한 경우 Thread 객체를 만들어주는 한 단계가 추가되었다는 점이다.

 

- 04

	@Override
	public void actionPerformed(ActionEvent arg0) {
		// Runnable 인터페이스를 구현한 클래스로 객체 생성
		Runnable r=new Runnable() {
			@Override
			public void run() {
				// run()  메소드 안쪽이 새로운 작업단위가 된다. 
				int count=0;
				while(true) {
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					count++; //count 를 1 증가 시킨다.
					System.out.println("현재 카운트:"+count);
					if(count==10) {// 카운트가 10이 되면 
						break;// 반복문 탈출 
					}
				}		
			}
		};
		// Thread 객체를 생성하면서 생성자의 인자로 전달한다.
		Thread t=new Thread(r);
		// start() 메소드를 호출해서 새로운 스레드를 시작한다.
		t.start();
		System.out.println("새로운 스레드를 시작 했습니다.");
	}

 이번 예시는 같은 내용을 anonymous class로 만든 것이다.

 

- 05

	@Override
	public void actionPerformed(ActionEvent arg0) {
		
//		new Thread(new Runnable() {
//			@Override
//			public void run() {
//				
//			}
//		}).start();
		
		new Thread(()->{			
			//run()  메소드 안쪽이 새로운 작업단위가 된다. 
			int count=0;
			while(true) {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				count++; //count 를 1 증가 시킨다.
				System.out.println("현재 카운트:"+count);
				if(count==10) {//카운트가 10이 되면 
					break;//반복문 탈출 
				}
			}					
		}) .start();
		
		System.out.println("새로운 스레드를 시작 했습니다.");
	}

 이번 예시는 lambda function으로 만든 것이다.

 

- 06

public class Frame06 extends JFrame implements ActionListener{
	// 필드 
	JLabel label_result;
	
	// 내부 클래스
	class CountThread extends Thread{
		@Override
		public void run() {
			// run()  메소드 안쪽이 새로운 작업단위가 된다. 
			int count=0;
			while(true) {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				count++; //count 를 1 증가 시킨다.
				System.out.println("현재 카운트:"+count);
				if(count==10) {// 카운트가 10이 되면 
					break;// 반복문 탈출 
				}
			}
			/*
			 *  내부 클래스의 메소드 안에서 바깥쪽에 클래스의 필드와
			 *  메소드를 사용할수 있다. 
			 */
			label_result.setText("작업종료~");
		}// run()
	}// class CountThread
	
	// 생성자
	public Frame06() {
		// 레이아웃 설정 
		setLayout(new BorderLayout());
		// 패널을 프레임의 상단에 배치 
		JPanel panel=new JPanel();
		panel.setBackground(Color.YELLOW);
		add(panel, BorderLayout.NORTH);
		// 버튼을 패널에 추가 하고 
		JButton countBtn=new JButton("1~10까지 세기");
		panel.add(countBtn);
		// 버튼에 리스너 등록하기
		countBtn.addActionListener(this);
		
		// JLabel
		label_result=new JLabel();
		panel.add(label_result);
	}

	(main method 중략)

	@Override
	public void actionPerformed(ActionEvent arg0) {
		// 내부 클래스를 이용해서 스레드 객체를 생성해서 시작 시킨다.
		new CountThread().start();
	}
}

 이번 예시는 inner class를 만들어서 바깥쪽의 field와 method를 사용해 만든 것이다.

 

 사실 이렇게 설명했지만, thread에 대해 오해하지 말아야 할 것이 있다. 위 이미지에서처럼 main thread와 new thread가 사실 동시에 진행되는 것이 아니다. 만약 어느 하나가 진행되고 있는 것이라면, 다른 하나는 멈춰있는 것이다. 무슨 말이냐 하면, 이미지의 경우는 main thread와 new thread가 서로 돌아가면서 진행되는 것이라는 말이다. 서로 돌아가면서 진행되는 교체 주기가 너무 빠르기 때문에 동시에 thread가 진행되는 것처럼 보이는 것이다. 심지어 이 교체 주기가 일정한 것도 아니라고 했다.

 

 그런데 이런 상황을 알고 상상해보자. 동시에 같은 자원을 사용해야 하는 일이 있다면 어떨까? 바로 위 이미지를 보면서 생각하자. 여러 thread가 있는 상황이다. 1번 thread에서 작업을 진행하다가 미완성인 상태로 멈추고 3번 thread가 진행되는 중에 1번 thread에서 사용해야 할 field의 값을 변경했다고 생각해보면, 1번 thread 입장에서는 이것이 애당초 계획과는 다른 결과물을 낼 수 있는 것이다. 다시 1번 thread의 작업이 진행될 때, 다른 thread에서의 작업이 영향을 줄 수 있는 것이다. 그렇기 때문에 문을 잠가야하고, 추후 synchronized를 사용할 것이라고 했다. Web server(웹 서버)는 이미 만들어져 있고, 우리는 부분 프로그래밍을 할 것이지만 알고 있으면 좋다고 했다.

'뒷북 정리 (국비 교육) > java' 카테고리의 다른 글

[Java] step18. Socket  (0) 2022.04.28
[Java] step17. InputOutput  (0) 2022.04.25
[Java] step15. Swing  (0) 2022.04.25
[Java] step14. Exception  (0) 2022.04.24
[Java] step13. Util Class  (0) 2022.04.22

댓글