본문 바로가기
JAVA공부/2-쓰레드

쓰레드 제어문(4)

by 으노으뇨 2021. 10. 14.
728x90
반응형
SMALL

join()

지정된 시간동안 특정 쓰레드가 작업하는 것을 기다린다.

무슨말이야 A라는 쓰레드와 B라는 쓰레드가 있는데

A라는 작업을 하다가 B가 작업을 끝낸뒤 A쓰레드가 더 진행되게 하도록 해준다.

void join() //작업이 모두 끝날 때까지
void join(long millis) // 천분의 일초동안
void join(long, millis, int nanos) // 천분의 일초 + 나노초 동안

그리고 sleep() 와 동일하게 에외처리를 한다.

try{
t1.join();
}catch(InterruptedException e){}

만약 interrupt가 발생하면  멈추던걸 멈추고 실행한다.

시간을 지정하지 않으면 , 해당 쓰레드가 작업을 모두 마칠때까지 기다린다. 작업 중에 다른 쓰레드의 작업이 먼저 

수행되어야 할 필요가 없을때 사용해 주면 되곘다.

sleep와 유사하지만 다른점은 static 매서드가 아니여서 현재 쓰레드가아닌 특정 쓰레드에 대해 동작할수있다.

예제를 통해 좀더 알아보자

package ch13.ThreadT;
public class ThreadEx19 {
	static long startTime = 0;
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ThreadEx19_1 t1 = new ThreadEx19_1();
		ThreadEx19_2 t2 = new ThreadEx19_2();
		t1.start();
		t2.start();
		startTime = System.currentTimeMillis();
		try {
			t1.join();
			t2.join();
		} catch (InterruptedException e) {
		}
		System.out.println("소요시간 : " + (System.currentTimeMillis() - ThreadEx19.startTime));
	}
}
class ThreadEx19_1 extends Thread {
	public void run() {
		for (int i = 0; i < 300; i++) {
			System.out.print(new String("-"));
		}
	}
}
class ThreadEx19_2 extends Thread {
	public void run() {
		for (int i = 0; i < 300; i++) {
			System.out.print(new String("|"));
		}
	}
}

중간에 조인메서드를 불러서 총 걸린시간을 재었다.

이렇게 소요시간이 맨마지막에 나타났다.

이는 메인쓰레드가 t1의 작업이 끝날때까지 기다렸고,(t1.join() 을통해서)

또 t2의 작업이 끝날때까지도 기렸다.(t2.join())

그래서 소요시간은 맨마지막에 찍혔다.

만약 해당 try-catch문을 주석처리하고 실행시킨다면,

해당 결과처럼 메인쓰레드가 끝나고 , ,t1,t2쓰레드는 계속 진행되었다는 것을 알 수 있다.

즉 조인메서드를 사용하지 않았으면 메인쓰레드는 바로 종료되었고, 조인메서드로 쓰레드 t1,t2가 작업을 마칠 떄까지 메인쓰레드가 기다리도록 된것이다.

그래서 메인쓰레드가 두쓰레드의 작업에 소요된 시간을 출력할 수 있었다.

아직 감이 오지않아 다른 예제를 통해 다시 확인해보겠다.

만약 가비지 컬렉터를 사용한다, 또는 프로그램을 사용하다가 용량이 부족해졌다고 생각해보자

내 피씨의 램 = 16기가
현재 용량 16기가
불필요한 프로세스 제거를 통해 공간을 확보시켜야한다.
꽉찼다..
그럼 잠시 하던일 1초라도 멈추게하고(join(1000))
메모리를 비운다.
그다음 내가 하던일을 계속하게 한다.

이렇게 중간에 멈추게 해준다.

package ch13.Thread;

public class ThreadEx20 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		ThreadEx_20 gc = new ThreadEx_20();
		gc.setDaemon(true);
		gc.start();

		int requireMemory = 0;
		for (int i = 0; i< 20; i++) {
			requireMemory = (int) (Math.random() * 10) * 20;
			if (gc.freeMemory() < requireMemory || gc.freeMemory() < gc.totalMemory() * 0.4) {
				gc.interrupt();
                try {
					gc.join(1000);
				} catch (InterruptedException e) {
				}
			}
			gc.usedMemory += requireMemory;
			System.out.println("usedMemory : " + gc.usedMemory);
		}
	}
}

class ThreadEx_20 extends Thread {
	final static int MAX_MEMORY = 1000;
	int usedMemory = 0;

	public void run() {
		while (true) {
			try {
				Thread.sleep(10 * 1000);
			} catch (InterruptedException e) {
				System.out.println("Awaken by Free Memory : " + freeMemory());
			}
			gc();
			System.out.println("Garbage Collected, Free Memory : " + freeMemory());
		}
	}

	public void gc() {
		usedMemory -= 300;
		if (usedMemory < 0) {
			usedMemory = 0;
		}
	}

	public int totalMemory() {
		return MAX_MEMORY;
	}

	public int freeMemory() {
		return MAX_MEMORY - usedMemory;
	}
}

지금의 예제가 그렇다.

JVM의 가비지컬렉터의 기능을 일부 구현해 보았다.

맥스 메모리 1000을 넘겼을때, join()을 불러들어와 

가비지 컬렉터를 비우는 시간을 번뒤 재작동하게 하였다.

계속계속 반복해서 나타나도록했다. 


yield() - 양보

남은 시간을 다음 쓰레드에게 양보하고, 자신(현재 쓰레드)는 실행대기한다.

스케줄러에 의해 1초의 실행시간을 할당받은 쓰레드가 0.5초의 시간동안 작업을 한 상태에서 yield()가 호출되면

나머지 0.5초는 포기하고, 다시 실행대기상태가 된다.

yield와 interrupt를 적절히 사용하면 프로그램의 응답성을 높이고 보다 효율적인 실행이 가능하게된다.

이것도 static 매서드로써 자기 자신의 메서드에서밖에 사용할 수 없다.

일시정지 상태일때, 작업수행을 해야하는데 작업수행을 못한다. 그럼 할일없이 계속 돌게된다. 

나에게 시간이 주어졌지만 일시정지상태여서 헛돈다 이걸 = busy-waiting이라고한다.

일은안하는데 용량을 먹을수있다...

차리리 이러느리 뒷사람에게 양보하는 것이다.

yield 는 자기자신에게 받고 남은 시간을 남에게 주는 것! 

- 이게더 효율적이다.

앞에서 메서드를 구현해본 예제가있었다.

 그예제에서 

	void stop() {
		this.stopped = true;
	}

	public void suspend() {
		this.stopped = true;
	}
    // 에서 인터럽트를 호출해 주어야 자고있는 시간을 조금더 줄일 수 있다.
    // 1초라던가 ~ 조금이라도 응답성이 좋아진다. 만약우리가
    //스탑버튼이나 시작버튼눌렀는데 1초후에 시작하면 짜증나듯이..
    
	void stop() {
		this.stopped = true;
		th.interrupt();
		System.out.println(th.getName() + " - interrupt() by suspend()");
	}

	public void suspend() {
		this.stopped = true;
		th.interrupt();
		System.out.println(th.getName() + " - interrupt() by suspend()");
	}

yield 는 OS스케줄러에게 통보해는 것이다. 그래서 반드시 엘드를 넣는다고 딱딱 맞춰서 생기는건아니다 그렇게

강력하게 기대할것은아니고 심지어 옐드 쓰는것과 안쓰는 차이점은 없을 정도라고하지만

옐드와 인터럽트를 쓰면 반응적인 측면을 높일 수 있다.

package ch13.Thread;

public class ThreadEx18 {
	public static void main(String[] args) {
		Thread18 t1 = new Thread18("*");
		Thread18 t2 = new Thread18("**");
		Thread18 t3 = new Thread18("***");
		t1.start();
		t2.start();
		t3.start();
		try {
			Thread.sleep(2000);
			t1.suspend();
			Thread.sleep(2000);
			t2.suspend();
			Thread.sleep(2000);
			t1.resume();
			Thread.sleep(2000);
			t1.stop();
			t2.stop();
			Thread.sleep(1000);
			t3.stop();
		} catch (InterruptedException e) {
		}
	}
}

class Thread18 implements Runnable {
	volatile boolean suspended = false;
	volatile boolean stopped = false;
	Thread th;

	Thread18(String name) {
		th = new Thread(this, name);
	}

	@Override
	public void run() {
		String name = th.getName();
		while (!stopped) {
			if (!suspended) {
				System.out.println(name);
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					System.out.println(name + " - interrupted");
				}
			} else {
				Thread.yield();
			}
		}
		System.out.println(name + " - stopped");
	}

	void start() {
		th.start();
	}

	void stop() {
		stopped = true;
		th.interrupt();
		System.out.println(th.getName() + " - interrupt() by stop()");
	}

	public void suspend() {
		stopped = true;
		th.interrupt();
		System.out.println(th.getName() + " - interrupt() by suspend()");
	}

	public void resume() {
		suspended = false;
	}

}

이전 예제 yied()와 인터럽트메서드를 추가해서 예제의 효율과 응답성을 향상시켰다.

먼저 if 문에 yield()를 호출하는 else블럭이 추가되었다.

if (!suspended) {
				System.out.println(name);
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					System.out.println(name + " - interrupted");
				}
			} else {
				Thread.yield();
			}

yield를 호출해서 남은 실행시간을 while문에서 낭비않고 다른 쓰레드에게 양보하게 되므로 효율적이다.

또 stop()이 호출되었을때, Thread.sleep(1000)에 의해 쓰레드가 일시정지 상태에 머물러 있는 상황이라면, 

interrupt로 바로 꺠워서 벗어나게 해주었다.

728x90
반응형
LIST

'JAVA공부 > 2-쓰레드' 카테고리의 다른 글

쓰레드의 동기화(2)  (0) 2021.10.15
쓰레드의 동기화  (0) 2021.10.15
쓰레드 제어문(3)  (0) 2021.10.14
쓰레드 제어문(2)  (0) 2021.10.13
쓰레드 제어문(1)  (0) 2021.10.13

댓글