본문 바로가기
Java & Spring

java) 스레드 Thread 와 동기화

by softserve 2021. 12. 9.
반응형

1. 스레드를 생성하는 두 가지 방법

첫 번째 방법은 Thread를 상속받는 것입니다.

class Manager extends Thread {
	SharedResources sr;
	
	public Manager(SharedResources r) {
		this.sr = r;
	}
	
	public void run() {
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			System.out.println("manager wake up!!!");
		}
		
	}
}

두 번째는 Runnable interface를 구현하면 됩니다.

class Client implements Runnable {
	SharedResources sr;
	String clientName;
	
	public Client(SharedResources r, String name) {
		this.sr = r;
		this.clientName = name;
	
	}
	
	public void run() {
		for(int i = 0; i < 100; i++) {
			sr.fill(10);
			try {
				Thread.sleep(99);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			sr.spend(10);
		}
	}
}

두 번째 방법이 다른 클래스를 상속받을 수 있으므로 조금 더 많이 사용됩니다.

이제 메인 메서드에서 스레드의 객체를 만들어줍시다.

 

static SharedResources sr = new SharedResources();
public static void main(String[] args) {
		// new Thread
		Thread c1 = new Thread(new Client(sr, "1st"));
		Thread c2 = new Thread(new Client(sr, "2nd"));
	
		Thread m = new Manager(sr);
		
		
 }

메인 메서드에서 Runnable interface를 구현한 Client 클래스의 객체를 매개변수로 넘겨주고 새로운 Thread 객체를 생성합니다.

Thread 클래스를 상속받은 Manager 클래스의 객체를 생성합니다.

현재 스레드 객체는 갓 생성된 상태에 있습니다.

 

2. 스레드의 실행과 종료

start() 메서드는 run()을 호출하여 스레드가 실행 가능(Runnable) 상태로 바뀝니다. 

public static void main(String[] args) {
		// new Thread
		Thread c1 = new Thread(new Client(sr, "1st"));
		Thread c2 = new Thread(new Client(sr, "2nd"));
	
		Thread m = new Manager(sr);
        
        // Runnable
		c1.start();
		c2.start();
		m.start();
		
		while(Thread.activeCount() > 1) { // 현재 실행중인 스레드 개수가 1보다 클 때
        // 충돌로 인해 잔여 리소스가 음수가 되면 매니저를 소환한다. 
			if(sr.getResource() < 0) m.interrupt();  
		}
		
		
 }

run() 의 작업이 완료되면 스레드는 종료 상태 (Dead) 로 바뀌게 됩니다.

작업이 완료되기 전이라도 stop() 으로 강제 종료시킬 수 있습니다.

한편 아래의 경우에는 실행 중지 상태(Not runnable) 로 변합니다.

① suspend() 호출시 -> resume()으로 재개

② wait() 호출시 -> notify(), notifyAll() 로 재개

③ sleep() 호출 또는 입출력을 위해 대기 -> sleep time이 경과되거나 입출력 작업이 완료 시 재개

 

3. 우선순위와 스케줄링

System.out.println(t1.getPriority()); 5 NORM_PRIORITY		
t2.setPriority(10);
System.out.println(t2.getPriority()); MAX_PRIORITY
t3.setPriority(1);
System.out.println(t3.getPriority()); MIN_PRIORITY
  • 스레드는 각각 1~10 사이의 우선순위를 가집니다. 최초 생성시 5
  • 우선순위가 클수록 먼저 실행이 되며 같을 때는 cpu time이 균등하게 배분됩니다 (Round-robin)
  • 우선순위가 큰 스레드가 있으면 실행중인 스레드를 중단하고 우선순위가 높은 것을 먼저 실행합니다 (선점 방식, preemptive)


4. 멀티스레드와 동기화 문제

여러 개의 스레드를 사용하여 하나의 자원에 접근하는 경우 문제가 생길 수 있습니다.

지금까지 살펴본 코드에서 c1, c2 클라이언트는 공유 자원을 채워 넣고 fill() 소비하는 spend() 행위를 반복하고 있습니다.

만약 c1이 0인 공유 자원을 레지스터로 가져와 10으로 증가시키고 다시 저장하려고 할 때, c2가 동시에 0인 공유 자원에 접근하여 10으로 증가시킨 뒤 저장을 시도한다면 공유자원은 20이 아닌 10이 됩니다.

이후 c1 c2가 spend() 를 호출하면 공유자원이 -10 음수 값을 가지게 되고, 인터럽트를 발생시켜 곤히 자고 있던 매니저를 깨우게 됩니다.

이를 방지하려면 공유 데이터를 조작하는 영역(임계 영역) 에는 하나의 스레드만 존재하여야 합니다.

java에서는 메서드에 synchroonized 키워드를 추가함으로써 동기화를 할 수 있습니다.

한편 생성자는 동기화할 수 없고, 할 필요도 없습니다. 왜냐하면 객체를 생성하는 하나의 스레드만이(예제의 경우 main) 생성자에 접근할  수 있기 때문입니다.

class SharedResources {
	int resource;
	
	public SharedResources() {
		this.resource = 0;
	}
	
	public synchronized void spend(int a) {
		resource -= a;
	}
	public synchronized void fill(int a) {
		resource += a;
	}
	
	public int getResource() {
		return resource;
	}
}

 

반응형

댓글