-
thread - 1 (스레드 생성~join)JAVA 2022. 9. 24. 20:16
Thread란?
프로세스에서 기능을 수행하는 주체
프로세스는 1개 이상의 스레드로 구성되어 있다.
프로세스와 스레드의 차이
프로세스는 메모리 공유가 되지 않지만 스레드는 메모리 공유가 된다.
main thread
메인 메소드는 메인 스레드를 생성한다.
메인 스레드는 일할 work thread를 생성할 수 있다.
이를 multi thread라고 한다.
thread 생성 방법
Runnable interface 구현
package chap10.ex01.runnable; public class Job implements Runnable { @Override public void run() { // 스레드가 해야할 일을 이 안에 작성한다. for(int i=0;i<5;i++) { System.out.println("work thread가 해야하는 일"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
package chap10.ex01.runnable; public class MainThread { public static void main(String[] args) { // main thread //work thread 사용 Job job = new Job(); // 1. work thread가 해야할 일을 준비 // 2. work thread 생성 Thread work = new Thread(job); work.start(); // 3. 실행 for (int i = 0; i < 5; i++) { System.out.println("main thread가 하는 일"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
Thread class 상속
package chap10.ex02.thread; public class Job extends Thread { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("work thread가 하는 일"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
package chap10.ex02.thread; public class MainThread { public static void main(String[] args) { //1. 해야할 일 객체화 //2. 스레드 객체화(해야할 일) // Job은 Thread를 상속받았기 때문에 Job은 Thread의 모든 기능을 사용할 수 있다. // Job 안에는 해야할 일이 들어있기 때문에 Thread 안에 해야할 일을 넣을 필요가 없다. // Job의 부모는 Thread이므로 다형성을 이용하여 Thread 형태의 변수에 들어갈 수 있다. Thread work = new Job(); //3. 스레드 실행 work.start(); //메인 스레드가 하는 일 for (int i = 0; i < 5; i++) { System.out.println("main thread가 하는 일"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
익명 객체 -> 인터페이스로 생성하는지 클래스로 생성하는지에 따라 약간의 방법 차이가 있다.
package chap10.ex01.runnable; public class AnonyMain { public static void main(String[] args) { // 2. 스레드 생성(해야할 일 넣는다.) Thread work = new Thread(new Runnable() { @Override public void run() { // 1. 해야할 일 for (int i = 0; i < 5; i++) { System.out.println("work thread가 하는 일"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }); // 3. 스레드 실행 work.start(); // main thread가 하는 일 for (int i = 0; i < 5; i++) { System.out.println("main thread가 하는 일"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
package chap10.ex02.thread; public class AnonyMain { public static void main(String[] args) { /* 익명 객체로 Thread 생성 */ Thread work = new Thread() { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("work thread가 하는 일"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }; work.start(); // 메인 스레드가 하는 일 for (int i = 0; i < 5; i++) { System.out.println("main thread가 하는 일"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
스레드.sleep(시간) : 스레드를 시간(ms)동안 정지 시킨다.
스레드.getName() : 스레드의 이름을 가져온다.
스레드.setName(이름) : 스레드의 이름을 변경한다.
기본적으로 스레드의 이름은 Thread-0부터 차례로 생성된다.
package chap10.ex03.name; public class WorkThread extends Thread { @Override public void run() { for(int i=0;i<5;i++) { System.out.println(getName() + " 스레드가 출력한 내용"); } } }
package chap10.ex03.name; public class Main { public static void main(String[] args) { Thread th = new WorkThread(); th.start(); // Thread - 0 th = new WorkThread(); th.start(); // Thread - 1 th = new WorkThread(); th.setName("막내 스레드"); th.start(); // 막내 스레드 } }
스레드의 우선 순위
스레드는 라운드 로빈 방식을 사용하기 때문에 순서 제어가 힘들다.
우선 순위 방식은 시작과 동시에 우선 순위를 부여하기 때문에 완벽한 제어가 되지 않는다.
setPriority()로 우선 순위를 줄 수 있다.
-> 1~10까지 줄 수 있으며 상수로 줄 수도 있다.
package chap10.ex04.priority; public class WorkThread extends Thread { public WorkThread(String name) { setName(name); } @Override public void run() { System.out.println(getName() + " 작업 시작"); for (int i = 0; i < 1000000; i++) { /*이 시간 동안 하는 작업이라고 가정*/ } System.out.println(getName() + " 작업 끝"); } }
package chap10.ex04.priority; public class Main { public static void main(String[] args) { // 우선 순위는 1~10까지 줄 수 있다. (높을수록 빠르다.) for (int i = 1; i <= 5; i++) { Thread th = new WorkThread(i + "번째 스레드 "); th.setPriority(i+5); /* 우선 순위는 상수로도 줄 수 있다. Thread.MAX_PRIORITY; //10 Thread.MIN_PRIORITY; //1 Thread.NORM_PRIORITY; //5 */ th.start(); // thread 1 -> 5 순서로 시작 // 우선 순위에 의해서 5 -> 1 순서로 끝나야 한다. // 우선 순위 방식은 완벽한 제어가 어렵다. (그래서 이후 다른 방식들이 등장한다.) } } }
syncronized
스레드는 메모리 공유를 하기 때문에 데이터 간섭이 일어난다.
따라서 다중 사용을 허용하지 않을 때가 필요하다.
예시로 HashTable, Vector, StringBuffer가 있다.
syncronized는 다른 스레드의 접근을 막는 키워드이다.
메소드의 접근제한자와 반환타입 사이에 쓰는 방식과 syncronized 블록을 사용하는 방식이 있다.
메소드에서 syncronized를 쓰는 방식은 아예 메소드 자체에 진입을 막는다.
syncronized 블록을 쓰는 경우 메소드의 진입은 허가하지만 syncronized 블록에서 진입을 막는다.
package chap10.ex05.sync; public class User1 extends Thread { private Computer com; public User1(Computer com) { setName("USER 1"); this.com = com; } @Override public void run() { com.setScore(500); // user1은 게임을 해서 500점으로 만든다. } }
package chap10.ex05.sync; public class User2 extends Thread { // Computer 객체는 User2 객체가 죽을 때까지 살아있다. private Computer com; public User2(Computer com) { setName("USER 2"); this.com = com; } @Override public void run() { com.setScore(100); // user2은 게임을 해서 100점으로 만든다. } }
package chap10.ex05.sync; public class Computer { private int score; /* syncronized 사용 전 * public void setScore(int score) { this.score = score; // 점수를 수정하고 try { Thread.sleep(2000); // 2초 동안 화장실 간다. } catch (InterruptedException e) {} // 점수 확인 System.out.println(Thread.currentThread().getName()+ " : " + this.score); } */ /* syncronized 사용 - 메소드 자체 진입을 막는다. public synchronized void setScore(int score) { this.score = score; // 점수를 수정하고 try { Thread.sleep(2000); // 2초 동안 화장실 간다. } catch (InterruptedException e) {} // 점수 확인 System.out.println(Thread.currentThread().getName()+ " : " + this.score); } */ // syncronized 블럭 사용 - 메소드 진입 가능 public void setScore(int score) { synchronized (this) { // 블록 부분에서 막는다. this.score = score; // 점수를 수정하고 try { Thread.sleep(2000); // 2초 동안 화장실 간다. } catch (InterruptedException e) {} // 점수 확인 System.out.println(Thread.currentThread().getName()+ " : " + this.score); } } }
package chap10.ex05.sync; public class PcRoom { public static void main(String[] args) { // 컴퓨터 준비(공용컴퓨터 딱 한 대만 준비) Computer com = new Computer(); // 두 명의 유저 User1 user1 = new User1(com); User2 user2 = new User2(com); // 두 명의 유저가 컴퓨터 사용 user1.start(); user2.start(); } }
Thread State
스레드에는 생성부터 종료까지 상태가 있다.
getState()를 통해 상태를 알 수 있다.
실행 중에는 상태가 없다.
sleep()을 하면 Timed-Waitng 정지 상태이다.
New 스레드가 생성되었을 때 Runnable 스레드가 실행 대기 상태에 있을 때 Terminated 스레드가 종료 되었을 때 package chap10.ex06.state; public class WorkThread extends Thread { @Override public void run() { long cnt = 0; /* 실행 -> 1.5초 휴식 -> 실행 -> 종료*/ //실행 대기 for (long i = 0; i < 1000000000; i++) { cnt++; // 실행 } try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } cnt = 0; for (long i = 0; i < 1000000000; i++) { cnt++; } } }
package chap10.ex06.state; public class Main { private static Thread.State state; public static void main(String[] args) { WorkThread work = new WorkThread(); state = work.getState(); System.out.println("Thread State : " + state); work.start(); while(true) { // 워크 스레드 상태를 계속 얻어낸다. state = work.getState(); // 워크 스레드 상태가 terminated이면 반복문 탈출 System.out.println("Thread State : " + state); if(state==Thread.State.TERMINATED) { break; } } } }
양보(yield)
스레드가 실행 중일 때 다른 스레드에 양보 yield를 할 수 있다.
yield()를 통해 다른 스레드에 제어권을 넘겼을 때 받는 스레드가 즉시 받지 않는다면 제어권을 다시 자신이 가지고 계속 실행을 한다.
package chap10.ex07.yield; public class ThreadA extends Thread { boolean stop = false; boolean yield = false; @Override public void run() { while(!stop) { System.out.println("thread A 동작"); if(yield) { System.out.println("thread B에게 양보"); Thread.yield(); } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("thread A 중지"); } }
package chap10.ex07.yield; public class ThreadB extends Thread { boolean stop = false; boolean yield = false; @Override public void run() { while(!stop) { System.out.println("thread B 동작"); if(yield) { System.out.println("thread A에게 양보"); Thread.yield(); } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("thread B 중지"); } }
package chap10.ex07.yield; public class MainThread { public static void main(String[] args) throws InterruptedException { /*yield는 우리가 아는 양보와 많이 다르다. * 다른 스레드에게 실행할 수 있는 기회를 제공하는 것이다. * 그 기회를 바로 실행하지 않으면 본인이 마저 실행된다. * A, B 스레드 동시 시작 -> A에게 양보하라고 함 -> B에게 양보하라고 함*/ ThreadA thA = new ThreadA(); ThreadB thB = new ThreadB(); //스레드 실행 thA.start(); thB.start(); thA.yield = true; Thread.sleep(500); thA.yield = false; thB.yield = true; Thread.sleep(500); thA.stop = true; thB.stop = true; } }
join
여러 스레드가 실행 중일 때 다른 스레드가 일을 끝내지 않았는데 요구한다면 원하는 값을 찾아 올 수 없다.
이 때 sleep()을 이용하여 일을 끝내기를 기다리고 값을 받아올 수 있으나, 시간의 낭비가 있거나 시간이 모자랄 수 있기 때문에 효율이 좋지 않다.
이럴 때는 join()을 사용한다.
스레드.join()을 사용하면 다른 모든 스레드가 해당 스레드가 종료할 때까지 기다리게 된다.
이를 blocking 이라고 한다.
package chap10.ex08.seq; public class OperThread extends Thread { private int num; public int getNum() { return num; } @Override public void run() { for (int i = 1; i <= 100; i++) { num += i; } } }
package chap10.ex08.seq; public class MainThread { public static void main(String[] args) throws InterruptedException { System.out.println("1~100까지의 합은 ? "); OperThread oper = new OperThread(); oper.start(); // 워크 스레드 실행(1~100 더하기) // 문제1. 왜 0이 나왔는가? oper 스레드가 계산하는 동안에 메인 스레드가 답을 요구했기 때문에 // 문제2. 해결책은? oper 스레드가 계산기 끝날 때까지 기다려준다. //Thread.sleep(100); // sleep은 세밀한 조정이 어렵다. oper.join(); //oper 스레드가 실행을 종료할 때까지 이 부분에서 모든 스레드가 정지한다. -> blocking System.out.println("답 : " + oper.getNum()); } }
'JAVA' 카테고리의 다른 글
JAVA IO (데이터 입출력) (0) 2022.10.07 thread 2 (스레드 제어~스레드 풀) (1) 2022.09.24 컬렉션 프레임워크(List, Set, Map) (1) 2022.09.24 제너릭 (Generic) (1) 2022.09.24 문자열 (String) (1) 2022.09.24