09009

[Java] 쓰레드 본문

Back-End/JAVA
[Java] 쓰레드
09009

프로세스와 쓰레드

•  프로세스 : 실행 중인 프로그램, 자원과 쓰레드로 구성됨. 모든 프로세스에는 최소한 하나 이상의 쓰레드가 존재

•  쓰레드 :  하나의 프로그램이 동시에 여러 개의 일을 수행할 수 있도록 해 주는것, 프로세스 안에 있는 일의 단위

                   프로세스의 자원을 이용해서 실제로 작업을 수행하는 것이 쓰레드


멀티 쓰레드

단일 Thread일 경우 3개의 작업 1번째 작업  → 2번째 작업 → 3번째 작업

 Multi Thread일 경우 1번째 작업 ----

                                           2번째 작업 ----

                                           3번째 작업 ----

장점: 처리속도가 빠르다. 자원을 보다 효율적으로 사용 가능

단점: 순서를 지키는 작업은 곤란하다. 교착상태(dead lock)에 주의해야한다.


쓰레드의 실행 -  start()

•  쓰레드를 생성했다고 하여 자동으로 실행되는 것은 아님. start()를 호출해야 쓰레드가 실행됨. 

  start() 메서드는 run() 메서드를 호출한다.

•  start()가 호출되었다고 하여 바로 실행되는 것이 아니라, 실행대기 상태로 있다가 본인의 차례가 되어야 실행되는 것이다.

   단, 실행대기중인 쓰레드가 없으면 곧바로 실행된다.

•  한 번 실행이 종료된 쓰레드는 다시 실행할 수 없다. - 하나의 쓰레드에 대해 start()가 한 번만 호출될 수 있는 것.

•  쓰레드의 작업을 한 번 더 수행하고 싶다면 새로운 쓰레드를 생성한 다음에 start()를 호출해야함.  


 

쓰레드를 생성하는 방법

 

1) Thread 클래스로부터 직접 상속받는 방법

2) Runnable 인터페이스를 구현하는 방법(현재의 클래스가 이미 다른 클래스로부터 상속 받고 있는 경우)

Java에서는 다중상속을 지원하지 않기 때문에 Thread 클래스를 상속받으면 다른 클래스를 상속받을 수 없으므로 Runnable 인터페이스를 구현하는 방법이 일반적임.

 

• Thread를 상속받으면 run() 메서드 재정의가 필수는 아니지만 Runnable 인터페이스를 구현하면 반드시 재정의 해주어야 한다. 

   - Runnable 인터페이스를 구현하였을 때 run() 메서드를 재정의하지 않으면 에러가 발생한다.

 

!! Thread 클래스를 상속받았을 때와 Runnable 인터페이스를 구현하였을 때의 인스턴스 생성 방법이 다르다. !!

 

1) Thread 클래스로부터 직접 상속받는 방법 

class MyThread extends Thread {
	public void run() { 
// 상위 클래스인 Thread 클래스의  run() 메서드를 오버라이딩하여 스레드가 수행하여야 하는 문장들을 기술
    	...
    }
}

인스턴스 생성 방법  : 상속받은 클래스를 이용하여 객체 생성

MyThread th = new MyThread();
th.start();
// 쓰레드 객체를 생성하여 쓰레드를 시작시킨다.

Thread 클래스를 상속받은 후, 해당 쓰레드에서 지원하고 싶은 코드를 run() 메서드에서 오버라이딩 해준다.

해당 쓰레드를 생성한 후, 쓰레드 객체의 start() 메서드를 호출한다.

 

참고 소스코드

package ch12;
class Thread1 extends Thread{
	public void run() {  // 쓰레드를 실행하는 메서드
		for(int i = 0; i < 100; i++) {
			System.out.print("Thread" + i + "\t");
			if (i % 9 == 0)
				System.out.println();
			try {
				Thread.sleep(500); // 0.5초 쉬어라 	단위: 1/1000초
			} catch (InterruptedException e) {

			}
		}
	}
}

public class ThreadEx1 {
	public static void main(String[] args) {
		Thread1 th1 = new Thread1();
		Thread1 th2 = new Thread1();
//		th1.run(); 쓰레드를 쓰지 않을 경우
		th1.start(); th2.start(); // start : 메모리에 들어가기 위해 준비하는 것
		for(int i = 0; i < 100; i++) {
			System.out.print("main" + i + "\t");
			if (i % 9 == 0)
				System.out.println();
			try {
				Thread.sleep(500); // 0.5초 쉬어라 	단위: 1/1000초
			} catch (InterruptedException e) {

			}
		
	}
		System.out.println("프로그램 종료");
}
}
package ch12;
class Thread2 extends Thread{
public Thread2(String title) {
		super(title); // 재정의
	}

//	대박 50 출력 0.3초 쉬고 탭
//	10로 나누어 나머지가 0이면 줄바꿈
	public void run() {
		for(int i = 0; i < 50; i++) {
//			getName은 쓰레드명 읽기
			System.out.print(getName() + i + "\t");
			if (i % 10 == 0) System.out.println();
//		sleep안에 들어간 숫자는 1/1000초 단위
			try {
				Thread.sleep(300);
			} catch (InterruptedException e) {
			
			}
		}
	}
}

public class ThreadEx2 {
	public static void main(String[] args) {
		Thread2 th1 = new Thread2("안녕");
		Thread2 th2 = new Thread2("하이");
		th1.start(); th2.start();
		for(int i = 0; i < 50; i++) {
			System.out.print("main" + i + "\t");
			if (i % 10 == 0) System.out.println();
			try {
				Thread.sleep(300);
			} catch (InterruptedException e) {
			
			}
		}
		System.out.println("프로그램 종료");
	}

	
}

2) Runnable 인터페이스를 구현하는 방법

class MyThread implements Runnable {
	public void run() {
    //  Runnable 인터페이스에 정의된 run() 메소드를 오버라이딩하여 스레드가 수행할 문장들을 기술한다
   
   }
}

현재 클래스가 이미 다른 클래스로부터 상속을 받고 있다면 Runnable 인터페이스를 구현하여 쓰레드를 생성할 수 있다.

Runnable 인터페이스는 오로지 run() 메소드만 정의되어 있다.

public interface Runnable {
	public abstract void run();
 }

• 인스턴스 생성 방법  : 구현한 클래스를 이용하여 객체를생성하고 객체를 매개변수로 받는 쓰레드 객체를 생성한다.

 

RunnableB rb = new RunnableB(); // 객체 rb 생성
Thread tb = new Thread(rb);
// rb를 매개변수로 하여 스레드 객체 tb를 생성
tb.start(); // 스레드 시작

Runnable 인터페이스를 구현하였을 때, Runnable 인터페이스를 구현한 클래스의 인스턴스를 생성한 후, 이 인스턴스를 Thread 클래스의 생성자의 매개변수로 제공해야 한다.

 

!!! Thread 클래스를 상속받을 때에는 자손 클래스에서 조상인 Thread 클래스의 메서드를 직접 호출할 수 있었다.

하지만 Runnable 인터페이스를 구현할 경우에는 Thread 클래스의 static 메서드인 currentThread()를 호출하여 쓰레드에 대한 참조를 얻어 와야만 호출이 가능하다. !!!

 

static Thread currentThread() // 현재 실행중인 쓰레드의 참조를 반환
String getName() // 쓰레드의 이름 반환

 

참고 소스코드

package ch12;
class Run1 implements Runnable {
//	class Run1 extends Object, Thread  -> 에러 발생, 자바는 다중 상속 지원 x ---> Runnable 구현하면 됨.
	public void run() {
		for(int i = 0; i < 20; i++) {
		// Thread.currentThread() : 현재 작업중인 쓰레드 Thread.currentThread().getName() : 현재 작업중인 쓰레드의 이름 읽어오기
			System.out.println(Thread.currentThread().getName()+ i + " "); 
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
			
			}
		}
		
	}
}

public class RunnableEx1 {
	public static void main(String[] args) {
		Run1 r1 = new Run1();
		Run1 r2 = new Run1();
		Run1 r3 = new Run1();
//	쓰레드를 상속받았을 때는 객체를 생성하고 start 메서드가 사용가능하였다. 
//	하지만 runnable를 구현했을 때는 쓰레드 안에다 객체를 넣고 start메서드를 실행한다
		Thread th1 = new Thread(r1, "1번");
		Thread th2 = new Thread(r2, "2번");
		Thread th3 = new Thread(r3, "3번");
		th1.start(); th2.start(); th3.start();
	}
}

 

package ch12;

//Run2 i=1부터 10  현재 쓰레드명 + i 
//RunnableEx2 Run2 hello, hi,hey

class Run2 extends Object implements Runnable{ // run 메서드를 재정의해야함
	@Override
	public void run() {
		for(int i = 1; i <= 10; i++) {
			System.out.println(Thread.currentThread().getName() + i + " ");
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
			
			}
		}
		
	}
}


public class Runnable2Ex {
	public static void main(String[] args) {
		Run2 r1 = new Run2();
		Run2 r2 = new Run2();
		Run2 r3 = new Run2();
		
		Thread th1 = new Thread(r1, "hello");
		Thread th2 = new Thread(r2, "hi");
		Thread th3 = new Thread(r3, "hey");
		
		th1.start(); th2.start(); th3.start();
	}

}

Runnable 객체를 이용하여 Thread 생성자 입력

Thread th1 = new Thread(r1, "hello");
Thread th2 = new Thread(r2, "hi");
Thread th3 = new Thread(r3, "hey");

자바 api (https://docs.oracle.com/javase/8/docs/api/)에서 쓰레드 생성자에 관한 정보 참고


Join()

•  다른 쓰레드의 작업을 기다린다.

•  쓰레드 본인이 하고 있던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도록 하려면 join()을 사용한다.

•  작업 중에 다른 쓰레드의 작업이 먼저 수행되어야할 때 join()을 사용한다.


데몬 쓰레드

• 다른 일반 쓰레드의 작업을 돕는 보조적인 역할 수행

일반 쓰레드가 모두 종료될 시, 데몬 쓰레드는 강제적으로 자동 종료

 

쓰레드를 생성한 다음 실행하기 전에 setDaemon(true)를 호출하여야 한다.

setDaemon(true) : main 메서드의 작업이 끝나면 모든 쓰레드를 종료하라는 명령

!!! 주의사항: setDaemon(true), 즉 데몬 쓰레드는 반드시 start()보다 앞에서 실행되어야 한다.

 

참고 소스코드

package ch12;
class Daemon1 extends Thread {
	public void run() {
		while(true) {
		System.out.println("데몬");
		try {
			Thread.sleep(300);
		} catch (InterruptedException e) {
		}
		
	}
}
}
public class DaeMon1Ex {
	public static void main(String[] args) {
		Daemon1 dm = new Daemon1();
//	 main 작업이 끝나면 모든 쓰레드를 종료하라는 명령: setDaemon(true) , 주의사항: 데몬쓰레드는 반드시 start()보다 앞에서 실행해야한다. 소스코드가 start()보다 위에 있어야함\
//		setDaemon: 주 작업이 끝나면 관련 작업도 다 같이 끝내라는 명령
		dm.setDaemon(true);
		dm.start();
		for(int i = 0; i < 20; i++) {
		System.out.println(i);
		try {
			Thread.sleep(300);
		} catch (InterruptedException e) {
		}
	}

}
}

 

 

 

'Back-End > JAVA' 카테고리의 다른 글

[Java] 클래스변수, 인스턴스변수, 초기화 블럭  (0) 2023.03.18
[Java] (중요) 참조형 반환타입의 메서드  (0) 2023.03.18
[Java] Generic  (0) 2023.03.12
[Java] Arrays 클래스  (0) 2023.03.12
[Java] collection  (0) 2023.03.11