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로 바로 꺠워서 벗어나게 해주었다.
'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 |
댓글