지난 시간에 이어 스레드에 대해 알아본다.

스레드 : 한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위

 

pc, 휴대폰에 설치된 상태 → 프로그램

실행하는 상태 → 프로세스 (하드웨어의 자원을 사용한다.)

멀티프로세스 → 여러 개의 프로세스

일정 시간동안 한 스레드가 작업을 실행하고 나면 다른 스레드가 와서 작업을 실행(스위칭) → 빠른 작업으로 사용자는 동시적으로 스레드가 돌아간다고 생각함.

 

실제 스레드를 구현해 본다.

public class ThreadTest1 {

	public static void main(String[] args) {
		// 스레드를 만들어보자

		// 스레드를 만드는 두가지 방법
		// Thread 클래스
		// Runable 인터페이스

		// Theard 특징
		// 1. 순서대로 실행되지 않음
		// 2. 끝나는 순서도 정해져있지 않음.

		// (하나의 큰 프로그램 안에 작은 프로그램이 독립적으로 돌아가는 상태)

		// Thread 클래스의 생성방식
		ThreadEx1_1 t1 = new ThreadEx1_1();

		// Runable 인터페이스의 생성방식
		Runnable r = new ThreadEx1_2();
		Thread t2 = new Thread(r);

		t1.start(); // start()는 새로운 스텍을 만들고 그 안에 run()메소드를 넣어서 관리
		t2.start(); // 다 실행되고 나면 각 스텍 안에 main 하나 돌고, run() 두 개가 각각 돌아감.
		// main 스레드는 무조건 대기 상태를 거쳐야함. run() 스레드가 작업을 마칠 때까지.
		// 스레드는 동작할 때 순서가 없다! 자원을 먼저 점유하면 실행이 되어짐.
		// 중간에 스위칭(물리적 교환)이 일어남.

//		t1.run(); // run으로 호출하면 스레드 클래스가 되는 것이 아님.
//		t2.run();
	}
}

 

public class ThreadEx1_1 extends Thread{
	// Thread 클래스
	// run()메소드를 오버라이딩 해야 한다. 
	// 이 클래스 자체는 독립적으로 돌아가는 하나의 프로그램

	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			// getName()은 스레드에서 상속 받음
			System.out.println(getName());
		}
	}	
}
public class ThreadEx1_2 implements Runnable{
	// Runable 인터페이스
	// run()메소드를 오버라이딩 해야 한다. 

	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			// 인터페이스이기 때문에 getName()을 바로 가지고 올 수가 없다. 
			// currentThread()를 이용해서 스레드에서 getName()을 가지고 온다.
			System.out.println(Thread.currentThread().getName());
		}		
	}
}

 

스레드를 호출할 때 run()이 아닌 start()를 써야하는 이유 :

start() 메소드를 호출하면 JVM이 새로운 스레드를 생성하고 그 스레드에서 run() 메소드를 실행하게 됩니다. 이렇게 하여 병렬 처리가 가능해집니다. 반면에 run() 메소드를 직접 호출하면 현재 스레드에서 run() 메소드가 실행되며, 이는 새로운 스레드를 생성하지 않고 순차적으로 실행됩니다. 따라서 start()를 호출해야 실제로 멀티스레딩을 이용할 수 있습니다.

(노션 AI 참조)

 

 

멀티스레드를 만들어보자.

import javax.swing.JOptionPane;

public class ThreadTest2 {

	public static void main(String[] args) {

		// JOptionPane 값을 입력하지 않으면 대기
		// 값을 입력하면 실행

		Thread2_1 th1 = new Thread2_1();
		th1.start();

		// 멀티 스레드 방식으로 만들기
		String input = JOptionPane.showInputDialog("아무 값이나 입력하세요");
		System.out.println("입력하신 값은 " + input + "입니다.");

	}
}
public class Thread2_1 extends Thread {

	@Override
	public void run() {
		for (int i = 10; i > 0; i--) {
			System.out.println(i);
			try {
				Thread.sleep(1000);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

	}
}

숫자를 세는 것과는 상관없이 두개의 프로그램이 돌아간다.

 

아직 작업이 끝나지 않고 동작하고 있을 때 main 스레드는 대기하고 있다.

모든 작업이 끝나면 main 스레드도 종료된다.

 

스레드는 시작과 끝을 요청할 순 있지만 언제 시작할지, 끝낼지는 관여할 수 없다.

스레드 운영은 자바 버츄얼 머신이 담당.

생성   →                    실행대기 → 실행 (시간이 되면 다시 실행대기 상태로) → 소멸
                         
                         (RUNNABLE)
(New) →          start() → yield() → stop()                 → (TERMINATED)
                 스레드를 양보하고 싶을 때 yield()

(자바의 정석 13장 참조)

 

 

스레드의 제어를 해보자.

→ 스레드 시작과 끝 요청하기

언제 스레드를 시작하고 종료할지 알수가 없다.

하지만 스레드를 대기 상태를 만들어 제어할 수 있는 방법이 있다. → sleep() 사용

 

public class ThreadTest3 {

	public static void main(String[] args) {
		// 스레드 제어
		ThreadEx3_1 th1 = new ThreadEx3_1();
		ThreadEx3_2 th2 = new ThreadEx3_2();
		
		th1.start();
		th2.start();
		
		// sleep 사용 시 예외처리를 해야 한다.
		try {
			Thread.sleep(2000);  // 현재 sleep 메소드를 호출한 Thread를 일시 정지시킨다.
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("<<main종료>>");

	}

}

 

public class ThreadEx3_1 extends Thread{

	@Override
	public void run() {
		for (int i = 0; i < 300; i++) {
			System.out.print("-");
		}
		
		System.out.println("<<th1 종료>>");
	}

}
public class ThreadEx3_2 extends Thread {

	@Override
	public void run() {
		for (int i = 0; i < 300; i++) {
			System.out.print("|");
		}

		System.out.println("<<th2 종료>>");
	}

}

 

public class ThreadEx3_1 extends Thread{

	@Override
	public void run() {
		for (int i = 0; i < 300; i++) {
			System.out.print("-");
		}
		
		System.out.println("<<th1 종료>>");
	}

}
public class ThreadEx3_2 extends Thread {

	@Override
	public void run() {
		for (int i = 0; i < 300; i++) {
			System.out.print("|");
		}

		System.out.println("<<th2 종료>>");
	}

}

 

 

main종료가 2초 뒤에 뜬다.

2초 동안 sleep 함수로 main 스레드를 대기 시켰기 때문이다.

sleep은 sleep을 호출한 메서드 자체를 sleep 시켜준다.

 

 

interrupt() 강제로 실행대기 상태를 만들어주는 메소드

1. interrupt()
- interrupted 속성 (boolean 타입) : interrupt() 호출되어지면 변수의 값이 변경된다.
- 함수의 기본값 false -> interrupt()가 사용되어지면 true로 바뀐다.

// isinterrupted()
// interrupted 속성값을 반환한다.

2. interrupt()
- InterruptedException 타입의 예외를 발생 시킨다.
-sleep(), wait(), join() 일시정지된 스레드를 실행 대기 상태로 만든다.

 

 

import javax.swing.JOptionPane;

public class ThreadTest4 {

	public static void main(String[] args) {
		// 1. interrupt() 
			// interrupted 속성 (boolean 타입) : interrupt() 호출되어지면 변수의 값이 변경된다.
			// 함수의 기본값 false -> interrupt()가 사용되어지면 true로 바뀐다.	
		// isinterrupted()
			// interrupted 속성값을 반환한다.
		
		// 2. interrupt()
			// InterruptedException 타입의 예외를 발생 시킨다.
			// sleep(), wait(), join() 일시정지된 스레드를 실행 대기 상태로 만든다. 
		
		ThreadEx4_1 th1 = new ThreadEx4_1();
		th1.start();
		
		String input = JOptionPane.showInputDialog("아무 값이나 입력하세요");
		System.out.println("입력하신 값은 " + input + "입니다.");
		
		th1.interrupt();  // interrupted 속성 true로 변경
		System.out.println("isInterrupted() : " + th1.isInterrupted());
	}

}
public class ThreadEx4_1 extends Thread {

	@Override
	public void run() {
		int i = 10;

		// isInterrupted()는 interrupted한 속성값을 반환한다. -> false
		while (i != 0 && !isInterrupted()) {
			System.out.println(i--);
			for (long x = 0; x < 950000000000000000L; x++) {

			}
		}
		
		System.out.println("카운트가 종료되었습니다.");
	}

}

중간에 값을 입력하면 즉시 종료된다.

 

 

public class ThreadEx4_1 extends Thread {

	@Override
	public void run() {
		int i = 10;

		// isInterrupted()는 interrupted한 속성값을 반환한다. -> false
		while (i != 0 && !isInterrupted()) {
			System.out.println(i--);
//			for (long x = 0; x < 950000000000000000L; x++) {

//			}
			
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				
			}
		}
		
		System.out.println("카운트가 종료되었습니다.");
	}

}

중간에 값을 입력해도 즉시 종료되지 않고 카운트가 돌아간다.

 

자세하게 알아보기 :

import javax.swing.JOptionPane;

public class ThreadTest4 {

	public static void main(String[] args) {
		// 1. interrupt() 
			// interrupted 속성 (boolean 타입) : interrupt() 호출되어지면 변수의 값이 변경된다.
			// 함수의 기본값 false -> interrupt()가 사용되어지면 true로 바뀐다.	
		// isinterrupted()
			// interrupted 속성값을 반환한다.
		
		// 2. interrupt()
			// InterruptedException 타입의 예외를 발생 시킨다.
			// sleep(), wait(), join() 일시정지된 스레드를 실행 대기 상태로 만든다. 
		
		ThreadEx4_1 th1 = new ThreadEx4_1();
		th1.start();
		
		String input = JOptionPane.showInputDialog("아무 값이나 입력하세요");
		System.out.println("입력하신 값은 " + input + "입니다.");
		
		System.out.println("isInterrupted() : " + th1.isInterrupted()); // false
		
		th1.interrupt();  // interrupted 속성 true로 변경
		System.out.println("isInterrupted() : " + th1.isInterrupted()); // true, 카운트가 멈춤
		System.out.println("isInterrupted() : " + th1.isInterrupted()); // InterruptedException(예외) 발생 됨, false로 바뀜
		System.out.println("isInterrupted() : " + th1.isInterrupted()); // InterruptedException(예외) 발생 됨, false로 바뀜
	}

}
public class ThreadEx4_1 extends Thread {

	@Override
	public void run() {
		int i = 10;

		// isInterrupted()는 interrupted한 속성값을 반환한다. -> false
		while (i != 0 && !isInterrupted()) {
			System.out.println(i--);
//			for (long x = 0; x < 950000000000000000L; x++) {
//
//			}
			
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// interrupt(); 이 생략되어 있다 // interrupted 속성값을 false로 자동 초기화
				System.out.println("InterruptedException 발생 됨");
			}
		}
		
		System.out.println("카운트가 종료되었습니다.");
	}

}

 

 

suspend()와 resume() 으로 스레드 이해하기

  • resume()은 suspend()를 실행 대기 상태로 만든다.
public class ThreadTest5 {

	public static void main(String[] args) {
		// suspend()와 resume()으로 스레드 이해하기
		// 둘은 짝꿍

		RunImpl5_1 r = new RunImpl5_1();

		Thread th1 = new Thread(r, "*");
		Thread th2 = new Thread(r, "**");
		Thread th3 = new Thread(r, "***");

		// 아래 3개와 메인스레드 하나, 총 4개의 스레드가 돌아감.
		th1.start();
		th2.start();
		th3.start();

		try {
			Thread.sleep(2000); // main thread 2초 동안 일시정지
			th1.suspend(); // th1 thread를 일시정지 상태로 만들어라, 2초 후 실행
			Thread.sleep(2000);
			th2.suspend();
			Thread.sleep(3000);
			th1.resume();
			Thread.sleep(3000);
			th1.stop();
			th2.stop();
			Thread.sleep(2000);
			th3.stop();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
public class RunImpl5_1 implements Runnable {

	@Override
	public void run() {
		while (true) {
			System.out.println(Thread.currentThread().getName());
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {

			}
		}
	}
}

 

 

먼저 점유한 스레드가 작업을 마무리할 수 있도록 하는 것 → 동기화

synchronized 를 사용해 계좌 입출금 프로그래밍을 만들어 동기화작업 알아보기

public class ThreadTest6 {

	public static void main(String[] args) {
		RunnableEx6_1 r = new  RunnableEx6_1();
		
		new Thread(r).start(); 
		new Thread(r).start();
		// -100, -200 등 - 가 나온다.
		// 조건문을 통과한 상태에서 sleep()이 실행되기 떄문에 동시에 처리된 것 처럼 보여짐.
		// 서로 경쟁적으로 자원 사용
		// 이는 synchronized 사용으로 해결할 수 있다. (동기화 처리)
	}

}
public class Account {
	
	private int balance = 1000;
	
	public int getBalance() {
		return balance;

	}
	
	// synchronized 앞에 있는 작업이 마무리 될 때까지 기다려라
	public synchronized void withdrew(int money) {
		if (balance >= money) {
			try {
				Thread.sleep(1000); 
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			balance -= money;
		}
	}

}

Thread의 동기화는 메소드에 적용한다.

 

public class RunnableEx6_1 implements Runnable{
	// 돈을 빼는 사람

	Account acc = new Account(); // 멤버변수는 공유 되어진다.
	
	@Override
	public void run() {
		while(acc.getBalance() > 0) {
			// 100, 200, 300
			int money = (int)(Math.random() *3 +1) * 100;
			acc.withdrew(money);
			System.out.println("balance : " + acc.getBalance());
		}
	}

}

 

 

synchronized는 이렇게도 사용할 수 있다.

public class Account {

	private int balance = 1000;

	public int getBalance() {
		return balance;

	}

	// synchronized 앞에 있는 작업이 마무리 될 때까지 기다려라
	public void withdrew(int money) {
		synchronized (this) {
			if (balance >= money) {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				balance -= money;
			}

		}
	}

}

 

wait()와 notify()로 스레드 이해하기

→ 이 방법을 권장하고 있다.

→ resume()은 suspend()는 내부에 동기화가 되어있지 않아 교착상태가 발생함.

wait()와 notify()는 순서대로 작업하게 되어있다.

+ Recent posts