버글버글

Java 수업 기록 (27) Thread 본문

java/java 수업 기록

Java 수업 기록 (27) Thread

Bugle 2022. 8. 19. 00:00
반응형

▶ 스레드 (Thread) 

1. 세부 실행 단위

2. 자바 실행의 기본 단위(여태까지 사용했던 스레드는, main 스레드 이다)

3. main 스레드 이외의 스레드 추가 가능

 

* 스레드 생성
1. Thread 클래스 상속
2. Runnable 인터페이스 구현

* Thread 클래스 상속
1. extends Thread
2. Thread 클래스의 public void run() 메소드를 오버라이드해서 수행할 작업

    (오버라이드를 하는 이유는, 약속이기 때문이다.)

 

* 스레드 실행

1. start() 메소드를 호출

2. start() 메소드를 호출하면 run() 메소드에 오버라이드 한 내용이 실행

 ** run() 메소드를 만들었지만, run()메소드가 아니라 start()메소드를 호출해야 함**

 

예시) 클래스

public class Process extends Thread {
	
	private String name;

	public Process(String name) {
		super();
		this.name = name;
	}
	
	@Override
	public void run() {
		try {
			Thread.sleep(3000);
			System.out.println(name + " 작업 실행");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

millis = 1/1000초

1초 = 1000/1000초

public class Main {

	public static void main(String[] args) {
		
		System.out.println("main 시작");
		
		Process process = new Process("연산");		
		process.start();	// Process 클래스의 오버라이드된 run() 메소드가 호출
		
		System.out.println("main 종료");

	}
    
	// main 시작
	// main 종료
	// 연산 작업 실행

Main 에서 실행을 하면, main 시작 > main 종료 > 연산 작업 실행이 된다.

Main()메소드 에서는 Main()만 진행하고,

Process는 Process에서 진행하기 때문에 순서의 차이가 발생한다.


여기서 Process를 하나 더 생성하고 호출하면, 다른 프로세스가 생성되는 것이다.

실행 순서는 바뀔 수 있다. (아래 예시 참고)

public class Main {

	public static void main(String[] args) {
		
		System.out.println("main 시작");
		
		Process process = new Process("연산");		
		process.start();	// Process 클래스의 오버라이드된 run() 메소드가 호출
		
		
		Process process2 = new Process("제어");		
		process2.start();
		
		System.out.println("main 종료");

	}

}

// main 시작
// main 종료
// 제어 작업 실행 (연산이 먼저 나오지만, 제어가 먼저 실행되었다.)
// 연산 작업 실행

 

* 스레드의 우선순위

		System.out.println("가장 높은 우선순위: " + Thread.MAX_PRIORITY);	// 10
		System.out.println("가장 낮은 우선순위: " + Thread.MIN_PRIORITY);	// 1
		System.out.println("보통 우선순위: " + Thread.NORM_PRIORITY);		// 5

예시)

public class Main {

	public static void main(String[] args) {
		
		// 스레드 우선순위
		System.out.println("가장 높은 우선순위: " + Thread.MAX_PRIORITY);
		System.out.println("가장 낮은 우선순위: " + Thread.MIN_PRIORITY);
		System.out.println("보통 우선순위: " + Thread.NORM_PRIORITY);
		
		// 스레드 2개(s1, s2)
		Soldier s1 = new Soldier("김상사", new Gun(6));
		Soldier s2 = new Soldier("장병장", new Gun(10));
		
		// 각 스레드의 우선순위
		System.out.println("s1 우선순위 : " + s1.getPriority());
		System.out.println("s2 우선순위 : " + s2.getPriority());
		
		// 스레드 실행
		s1.start();
		s2.start();
	}
}

- 위 코드를 실행해보면 스레드 2개는 우선순위가 5인걸로 나타난다. 그래서 아무거나 무작위로 실행 시킨다.

- 우선순위가 높은 스레드를 (무조건은 아님) 최대한 먼저 실행

		// 우선순위가 높은 스레드를 (최대한) 먼저 실행
		// 우선순위 조정
		s1.setPriority(Thread.MIN_PRIORITY); // 가장 낮은 우선순위
		s2.setPriority(Thread.MAX_PRIORITY); // 가장 높은 우선순위

 

▶ Runnable 

스레드 생성 방법
1. Runnable 인터페이스 구현
2. public void run() 오버라이드

* Runnable 인터페이스를 구현한 클래스는 Thread로 바꿔야 start() 메소드를 호출할 수 있다.

* join() : 스레드가 종료(die)될때까지 기다림

예시) 

public class Robot {

}
public class WashRobot extends Robot implements Runnable {

	private String name;
	
	public WashRobot(String name) {
		super();
		this.name = name;
	}

	@Override
	public void run() {
		System.out.println(name + " 빨래중");		
	}
}
public class Main {

	public static void main(String[] args) {
		
		// Runnable 인터페이스를 구현한 클래스는 Thread로 바꿔야 start() 메소드를 호출할 수 있다.
		
		Runnable robot1 = new WashRobot("로봇1");	// Thread thread3 = new Threae(new WashRobot("로봇1")); 과 같음.
		WashRobot robot2 = new WashRobot("로봇2");
		
		Thread thread1 = new Thread(robot1);
		Thread thread2 = new Thread(robot2);
		
		thread1.start();
		thread2.start();
	}
}

예시2)

public class Calculator implements Runnable {
	
	private int total;
	private int begin;
	private int end;
	
	public Calculator(int begin, int end) {
		this.begin = begin;
		this.end = end;
	}
	
	public int getTotal() {
		return total;
	}
	public void setTotal(int total) {
		this.total = total;
	}

	public void add() {
		for(int n = begin; n <= end; n++) {
			total += n;
		}
	}
	
	@Override
	public void run() {
		add();
	}

}
public class Main {

	public static void main(String[] args) {
		
		// Calculator를 2개 준비
		// 작업을 반으로 나눠서 진행
		
		// Calculator가 동시에 연산을 수행하려면 Calculator를 스레드로 처리해야 함
		
		Calculator calc1 = new Calculator(1, 5000);
		Thread thread1 = new Thread(calc1);
		thread1.start();

		Calculator calc2 = new Calculator(5001, 10000);
		Thread thread2 = new Thread(calc2);
		thread2.start();
		
		System.out.println(calc1.getTotal() + calc2.getTotal());	// 0
	}

}

결과 값이 0이 나오는 이유는 Calculator는 계산 중인거고, main은 main대로 실행해서 Total을 호출한거다.
그래서 계산이 안된 Total 값이 출력되는 것이다.

아래와 같은 코드로 수정하면, 계산할 떄까지 기다렸다가 출력 해 준다. (join())


public class Main {

	public static void main(String[] args) {
		
		// Calculator를 2개 준비
		// 작업을 반으로 나눠서 진행
		
		// Calculator가 동시에 연산을 수행하려면 Calculator를 스레드로 처리해야 함
		
		Calculator calc1 = new Calculator(1, 5000);
		Thread thread1 = new Thread(calc1);
		thread1.start();	// 1번째 계산기 동작 시작

		Calculator calc2 = new Calculator(5001, 10000);
		Thread thread2 = new Thread(calc2);
		thread2.start();	// 2번째 계산기 동작 시작
		
		// 모든 계산기의 동작이 끝날때까지 기다린다.
		try {
			thread1.join();
			thread2.join();			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(calc1.getTotal() + calc2.getTotal());
	}
}

▶ Synchronized 

1. 스레드 충돌 방지를 위해서 한 번에 한 스레드만 접근할 수 있도록 허용

2. 공유 자원의 일관성을 보장

3. 한 번에 한 스레드만 접근할 수 있는 영역을 임계 영역(Critical Section)이라고 함

아래 예시는 스레드 충돌 예시이다.

public class Main {

	public static void main(String[] args) {
		
		// 클리너 1개
		Cleaner cleaner = new Cleaner();
		
		// 로봇 2개(동일한 클리너를 가짐)
		ToiletRobot robot1 = new ToiletRobot(cleaner);
		RoomRobot robot2 = new RoomRobot(cleaner);
		
		// 청소 시작
		robot1.start();
		robot2.start();
	}
}

public class Cleaner {
	
	public void toiletCleaning() {
		System.out.println("화장실 청소");
	}
	
	public void roomCleaning() {
		System.out.println("방 청소");
	}
}


public class RoomRobot extends Thread {
	
	private Cleaner cleaner;
	
	public RoomRobot(Cleaner cleaner) {
		super();
		this.cleaner = cleaner;
	}
    
	@Override
	public void run() {
		for(int i = 0;i < 5; i++) {
			cleaner.roomCleaning();
		}
	}
}

public class ToiletRobot extends Thread {

	private Cleaner cleaner;

	public ToiletRobot(Cleaner cleaner) {
		super();
		this.cleaner = cleaner;
	}

	@Override
	public void run() {
			for(int i = 0;i < 5; i++) {
				cleaner.toiletCleaning();
			}
	}
}

 

▶ Wait() 

1. 오브젝트 클레스의 메소드다.

2. 스레드가 대기 상태가 됨
3. 다른 스레드가 깨울때까지 대기함

4. 예외 처리가 필요함 (InterruptedException)

▶ Notify() 

1. 오브젝트 클레스의 메소드다.

2. 다른 스레드를 깨움
3. notifyAll() 메소드로 모든 스레드를 깨움

 

Wait()과 Notify()의 예시)

public class Cleaner {
	
	public synchronized void toiletCleaning() {
		try {
			System.out.println("화장실 청소");
			notify();	// "나 화장실 청소 끝났다"고 알림
			wait();		// 잠깐 쉼
		} catch (InterruptedException e) { // wait() 메소드는 예외 처리 필요
			e.printStackTrace();
		}
	}
	
	public synchronized void roomCleaning() {
		try {
			System.out.println("방 청소");
			notify();	// "나 방 청소 끝났다"고 알림
			wait();		// 잠깐 쉼
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}

 

 

 

▶ 기타사항

A a = new A(); = B b = new B(new A());
B b = new B(a);

왼쪽을 합친 코드와 오른쪽은 같은 코드다.

 

 

 

 

 

 

반응형