프로세스와 쓰레드 (process & thread)
프로세스 : 실행 중인 프로그램. 자원과 쓰레드로 구성 / 모든 프로세스는 최소한 하나의 쓰레드를 가지고 있음 (공장)
쓰레드 : 프로세스 내에서 실제 작업을 수행 (일꾼)
싱글 쓰레드 프로세스/ 멀티 쓰레드 프로레스 가 있음
**하나의 새로운 프로세스를 생성하는 것보다 하나의 새로운 쓰레드를 생성하는 것이 더 적은 비용이 듦
멀티쓰레드의 장단점
- 장점 (대부분의 프로세스는 멀티쓰레드로 작성되어있음)
시스템 자원을 보다 효율적으로 사용할 수 있음
사용자에 대한 응답성이 향상됨(ex 채팅 프로그램)
작업이 분리되어있어 코드가 간결해짐 - 단점 - 여러 쓰레드가 자원을 공유하는데에서 발생하는 문제
동기화에 주의해야함
교착상태가 발생하지 않도록 주의해야함
각 쓰레드가 효율적으로 고르게 실행될 수 있게해야함
쓰레드의 구현과 실행 - **run이란 메서드를 작성
1. Thread 클래스를 상속- 자바는 단일 상속만 가능해서 쓰레드를 상속받으면 다른 클래스를 상속 받을수 없음
class MyThread extends Thread{
public void run(){ //Thread클래스의 run()을 오버라이딩
/*작업내용*/
}
쓰레드의 생성과 실행
Mythread t1 = new MyThread(); //쓰레드의 생성
t1.start(); //쓰레드의 실행
2. Runnable인터페이스를 구현 - 더 좋음
Runnable 인터페이스
public interface Runnable{
public abstruct void run();
}
Runnable 인터페이스를 구현
class MyThread2 implements Runnable{
public void run(){//Runnable 인터페이스의 추상메서드 run()을 구현
/*작업 내용*/
}
}
쓰레드의 생성과 실행
Runnable r = new MyThread2();
Thread t2 = new Thread(r); //Thread(Runnable r) run이란 메소드의 구현체를 외부에서 매개변수로 받음
// THread t2 = new Thread(new Mythread2());
t2.start();
class Ex13_1 {
public static void main(String args[]) {
ThreadEx1_1 t1 = new ThreadEx1_1();
Runnable r = new ThreadEx1_2();
Thread t2 = new Thread(r); // 생성자 Thread(Runnable target)
t1.start(); //쓰레드 t1을 실행시킴
t2.start();//쓰레드 t1을 실행시킴
}
}
class ThreadEx1_1 extends Thread { //방법 1 Thread 클래스를 상속
public void run() { //run에 쓰레드가 수행할 작업을 작성
for(int i=0; i < 5; i++) {
System.out.println(getName()); // 조상인 Thread의 getName()을 호출 / this.getName에서 this 생략됨
}
}
}
class ThreadEx1_2 implements Runnable { //방법 2 Runnable interface 구현
public void run() {//run에 쓰레드가 수행할 작업을 작성
for(int i=0; i < 5; i++) {
// Thread.currentThread() - 현재 실행중인 Thread를 반환한다.
System.out.println(Thread.currentThread().getName()); //currentThread라는 메서드를 이용해서 쓰레드 객체를 참조해야함
}
}
}
// 방법 2 : Runnable interface를 구현한 경우에는 thread를 상속 받지 않았기 때문에 getName에 접근할때는 쓰레드 객체를 참조해야함
쓰레드의 실행 -start()
-쓰레드를 생성한 후에 start()를 호출해야 쓰레드가 작업 시작
start하면 실행가능한 상태가 되는거지 바로 실행되는것이 아님(먼저 start했다고 먼저 시작하는게 아님) -> os의 스케줄러가 실행순서를 결정
(JVM이 os의 독립적이라고 이야기하지만 os의 종속적인 것 중 하나가 쓰레드임 )
start()와 run() -작성한건 run인데 왜 start를 호출하는가?
start()메서드는 새로운 호출스택을 생성하여 run을 올리고 종료됨.
각각의 쓰레드가 자기만의 호출스택을 가지고 실행 -> 서로 독립적으로 작업을 수행할 수 있음
1. main 메서드에서 start호출
2. start메서드가 새로운 호출스택을 생성
3. 새로운 호출스택에 run을 올림
4. start 메서드 종료 -> 각각의 쓰레드가 자신의 호출스택을 가지고 독립적인 작업수행
** run이 호출되면 하나의 호출스택을 하나의 스레드가 호출하는것! 따라서 run이 아닌 start를 호출해야함.
main 쓰레드
- main메서드이 코드를 수행하는 쓰레드
- 쓰레드는 '사용자 쓰레드'와 '데몬 쓰레드'(=보조 쓰레드) 두 종류가 있음
실행중인 사용자 쓰레드가 하나도 없을때 프로그램은 종료됨
메인쓰레드가 종료되어도 실행중인 쓰레드가 있으면 종료되지 않음. run메서드를 실행하고 있는 쓰레드가 작업을 마쳐야 프로그램이 종료됨
싱글쓰레드와 멀티 쓰레드
-싱글 쓰레드
class Ex13_2 {
public static void main(String args[]) {
long startTime = System.currentTimeMillis();
for(int i=0; i < 300; i++)
System.out.printf("%s", new String("-"));
System.out.print("소요시간1:" +(System.currentTimeMillis()- startTime));
for(int i=0; i < 300; i++)
System.out.printf("%s", new String("|"));
System.out.print("소요시간2:"+(System.currentTimeMillis() - startTime));
}
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------소요시간1:169||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||소요시간2:181
-멀티 쓰레드
class Ex13_3 {
static long startTime = 0;
public static void main(String args[]) {
ThreadEx3_1 th1 = new ThreadEx3_1();
th1.start();
startTime = System.currentTimeMillis();
for(int i=0; i < 300; i++)
System.out.printf("%s", new String("-"));
System.out.print("소요시간1:" + (System.currentTimeMillis() - Ex13_3.startTime));
}
}
class ThreadEx3_1 extends Thread {
public void run() {
for(int i=0; i < 300; i++)
System.out.printf("%s", new String("|"));
System.out.print("소요시간2:" + (System.currentTimeMillis() - Ex13_3.startTime));
}
}
--------------------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||-------||||||-|||||||||||||||||||||--------------------------------------------------------------------------------------------------------|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||------------------------------------------------------------------------------------------------------------------------------------------------------------------------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||소요시간1:72소요시간2:73
-쓰레드의 I/O블락킹(blocking)
입출력시 작업이 중단되는것이 I/O blocking이라고 함
싱글쓰레드는 blocking 구간(사용자 입력을 안해서 작업진행이 막혀있는동안) 아무 일 도할 수 없는데 멀티쓰레드는 다른 쓰레드가 작업을 할 수 있음 -> 싱글쓰레드가 멀티쓰레드보다 빨리 작업을 마칠 수 있음
(싱글쓰레드)
import javax.swing.JOptionPane;
class Ex13_4 {
public static void main(String[] args) throws Exception {
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하신 값은 " + input + "입니다.");
for(int i=10; i > 0; i--) {
System.out.println(i);
try {
Thread.sleep(1000); // 1초간 시간을 지연한다.
} catch(Exception e ) {}
}
}
}
(멀티쓰레드)
import javax.swing.JOptionPane;
class Ex13_5 {
public static void main(String[] args) throws Exception {
ThreadEx5_1 th1 = new ThreadEx5_1();
th1.start();
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하신 값은 " + input + "입니다.");
}
}
class ThreadEx5_1 extends Thread {
public void run() {
for(int i=10; i > 0; i--) {
System.out.println(i);
try {
sleep(1000);
} catch(Exception e ) {}
}
} // run()
}
-쓰레드의 우선순위(priority of thread)
-작업의 중요도에 따라 쓰레드의 우선순위를 다르게 하여 특정 쓰레드가 더 많은 작업시간을 갖게 할 수 있음
void setPriority(int newPriority) //쓰레드의 우선순위를 지정한 값으로 변경
int getPriority() //쓰레드의 우선순위를 반환
public static final int MAX_PRIORITY =10 //최대 우선순위
public static final int MIN_PRIORITY =10 //최소 우선순위
public static final int NORM_PRIORITY =5 //보통 우선순위
-쓰레드를 만들고 우선순위를 지정해주지 않으면 5로 지정
- 우선순위를 변경하고 싶으면 setPriority메서드를 이용함
- 쓰레드의 우선순위를 알고 싶으면 getPriority 메서드를 호출하면 우선순위를 반환함
- 우선순위를 쓰레드가 시작된 이후에도 변경가능함
- 쓰레드의 우선순위는 희망사항에 불과하며 os스케줄러에 전적으로 반영되는것이 아님
class Ex13_6 {
public static void main(String args[]) {
// 우선순위는 프로세스 안의 쓰레드들 간의 상대적인 우선순위 (1-3과 5-7 동일)
ThreadEx6_1 th1 = new ThreadEx6_1(); // 기본 우선순위 5로 지정됨
ThreadEx6_2 th2 = new ThreadEx6_2();
th2.setPriority(7); //th2는 우선순위 7로 지정
//우선순위가 높은 th1이 먼저 끝날것으로 기대됨(확률이 높지만 꼭 그런것은 아님)
System.out.println("Priority of th1(-) : " + th1.getPriority());
System.out.println("Priority of th2(|) : " + th2.getPriority());
th1.start();
th2.start();
}
}
class ThreadEx6_1 extends Thread {
public void run() {
for(int i=0; i < 300; i++) {
System.out.print("-");
for(int x=0; x < 10000000; x++); //시간 지연을 위함
}
}
}
class ThreadEx6_2 extends Thread {
public void run() {
for(int i=0; i < 300; i++) {
System.out.print("|");
for(int x=0; x < 10000000; x++);
}
}
}
쓰레드 그룹
-서로 관련된 쓰레드를 그룹으로 묶어서 다루기 위함
- 모든 쓰레드는 반드시 하나의 쓰레드 그룹에 포함되어 있어야함
- 쓰레드 그룹을 지정하지 않고 생성한 쓰레드는 'main쓰레드 그룹'에 속함(main가 main쓰레드 그룹에 속함)
- 자신을 생성한 쓰레드(부모 쓰레드)의 그룹과 우선순위를 상속받음
//쓰레드의 생성자
Thread(ThreadGroup group, String name)
Thread(ThreadGroup group. Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
//관련 메서드
ThreadGroup getThreadGroup() //쓰레드 자신이 속한 쓰레드 그룹을 반환
void uncaughtException(Thread t, Throwable e)
//처리되지 않은 예외에 의해 쓰레드 그룹의 쓰레드가 종료되었을떄 JVM에 의해 이 메서드가 자동으로 호출
//쓰레드 그룹에 있는 쓰레드가 예외처리 못하고 죽었을때 수행되는 동작 오버라이딩 가능
ThreadGroup의 메서드
- ThreadGroup (String name) : 지정된 이름의 새로운 쓰레드 그룹을 생성
- ThreadGroup(ThreadGroup parent, String name) : 지정된 쓰레드 그룹에 포함되는 새로운 쓰레드 그룹을 생성 (쓰레드 그룹안에 또다른 그룹 만들기)
- int activeCount() : 쓰레드 그룹에 포함된 활성상태에 있는 쓰레드의 수를 반환 (활성상태 : 아직 작업을 다 마치지 않은 상태)
- int activeGroupCount() : 쓰레드 그룹에 포함된 활성상태에 있는 쓰레드 그룹의 수를 반환
- void checkAccess() : 현재 실행중인 쓰레드가 쓰레드 그룹을 변경할 권한이 있는지 체크
- void destroy() : 쓰레드그룹과 하위 쓰레드 그룹까지 모두 삭제. 단, 비어있어야 삭제 가능
- int enumerale(Thread[] list)
int enumerale(Thread[] list, boolean recurse)
int enumerale(ThreadGroup[] list)
int enumerale(ThreadGroup[] list,boolean recurse)
: <배열에 정보를 담아서 반환하는 메서드> 쓰레드 그룹에 속한 쓰레드 또는 하위 쓰레드 그룹의 목록을 지정한 배열에 달고 그 개수를 반환. 두번째 매개변수인 recurse의 값을 true로 하면 쓰레드 그룹에 속한 하위쓰레드 그룹에 쓰레드 또는 쓰레드 그룹까지 배열에 담음 - int getMaxPriority : 쓰레드 그룹의 최대 우선순위를 반환
- String getName(): 쓰레드 그룹의 이름을 반환
- ThreadGroup getParent() : 쓰레드 그룹의 상위 쓰레드 그룹을 반환
- void interrupt() : 쓰레드 그룹에 속한 모든 쓰레드를 interrupt
- boolean is Deamon() : 쓰레드 그룹이 데몬 쓰레드 그룹인지 학인
- boolean isDestroyed() : 쓰레드 그룹이 삭제되었는지 확인
- void list() : 쓰레드 그룹에 속한 쓰레드와 하위 쓰레드 그룹에 대한 정보를 출력
- boolean parnetOf(ThreadGroup g) : 지정된 쓰레드 그룹의 상위 쓰레드 그룹인지 확인
- void setDeamon(boolean deamon) : 쓰레드 그룹을 데몬 쓰레드 그룹으로 설정/해제
- void setMaxPriority(int pri) : 쓰레드 그룹의 최대 우선순위를 설정
데몬 쓰레드(deamon thread)
- 일반쓰레드의 작업을 돕는 보조적인 역할을 수행
- 일반쓰레드가 종료되면 자동으로 종료됨
- 가비지 컬렉터, 자동저장, 화면 자동 갱신등에 사용됨
- 무한 루프와 조건문을 이용해서 실행 후 대기하다 특정조건이 만족되면 작업을 수행하고 다시 대기하도록 작성
(일반 쓰레드가 종료되면 자동종료가 되기 때문에 무한루프로 작성해도 괜찮음)
boolean is Daemon() //쓰레드가 데몬 쓰레드인지 확인. 데몬 쓰레드이면 true를 반환
void setDaemon(boolean on) //쓰레드를 데몬 쓰레드로 또는 사용자 쓰레드로 변경. 매개변수 on을 true로 지정하면 데몬 쓰레드가 됨
* setDeamon(boolean on)은 반드시 start()가 호출되기 전에 실행되어야함. (그렇지 않으면 illegalThreadStateException이 발생/ start가 실행된 다음에는 deamon thread로 바꿀수 없음)
class Ex13_7 implements Runnable {//Runnable 인터페이스를 구현하여 thread 생성
static boolean autoSave = false;
public static void main(String[] args) { //Main쓰레드
Thread t = new Thread(new Ex13_7());//Thread(Runnable r) //run이란 메서드를 가지고 있는 클래스의 객체를 매개변수로 줌
t.setDaemon(true); // 이 부분이 없으면 종료되지 않는다.
t.start(); //start 전에 setDeamon이 있어야함
for(int i=1; i <= 10; i++) {
try{
Thread.sleep(1000);
} catch(InterruptedException e) {}
System.out.println(i);
if(i==5) autoSave = true;
}
System.out.println("프로그램을 종료합니다.");
}
public void run() { //데몬쓰레드 -자동종료(일반쓰레드가 하나도 없을때)
while(true) {
try {
Thread.sleep(3 * 1000); // 3초마다
} catch(InterruptedException e) {}
// autoSave의 값이 true이면 autoSave()를 호출한다.
if(autoSave) autoSave();
}
}
public void autoSave() {
System.out.println("작업파일이 자동저장되었습니다.");
}
}
쓰레드의 상태
- NEW : 쓰레드가 생성되고 아직 start()가 호출되지 않은 상태
- RUNNABLE : 실행중 또는 실행가능한 상태 (실행 될 수 있는 상태)
- BLOCKED: 동기화블럭에 의해서 일시정지된 상태(block이 풀릴 때 까지 기다리는 상태) (ex I/O block -입출력 대기)
- WAITING, TIMED_WAITING : 쓰레드의 작업이 종료되지는 않았지만 실행가능하지 않은(unrunnable)일시 정지상태, TIMED_WAITING은 일시정지 시간이 지정된 경우를 의미 (ex sleep(),suspend())
- TERMINATED : 쓰레드의 작업이 종료된 상태 (stop 메서드가 호출되거나 작업이 다 끝났을때)
+)
suspend <-> resume
notify <-> wait
interrupt <-> sleep
쓰레드의 실행제어
-쓰레드의 실행을 제어할 수 있는 메서드가 제공됨
- static void sleep(long millis)
static void sleep(long millis, int nanos)
: 지정된 시간(천분의 일초단위 (5초 -> 5000)) 동안 쓰레드를 일시정지시킴. 지정한 시간이 지나고 나면, 자동적으로 다시 실행대기상태가됨 - void join()
void join(long millis)
void join(long millis, int nanos)
: <다른 쓰레드 기다리기- 기다리는 시간을 줄 수 있음>지정된 시간동안 쓰레드가 실행되도록 함. 지정된 시간이 지나가거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속함 - void interrupt() : sleep()이나 join()에 의해 일시정지상태인 쓰레드를 꺠워서 실행대기상태로 만듦. 해당 쓰레드에서는 interrupted Exception이 발생함을써 일시정지 상태를 벗어나게됨
- void stop() : 쓰레드를 즉시 종료시킴
- void suspend() : 쓰레드를 일시 정지 시킴. resume()을 호출하면 다시 실행대기상태가됨
- void resume() : suspend()에 의해 일시정지상태에 있는 쓰레드를 실행대기상태로 만
- static void yield() : 실행중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보하고 자신은 실행대기 상태가 됨
static 메서드는 쓰레드 자기자신한테만 호출이 가능함 (다른 쓰레드에 적용 불가)
Thread의 실행제어 메서드 (sleep, interrupted)
sleep() (static : 자기자신한테만 동작함)
- 현재 쓰레드를 지정된 시간동안 멈추게함
static void sleep(long millis) //천분의 일초 단위
static void sleep(long millis, int nanos) //천분의 일초 + 나노초 (잘쓰이지는 않음)
- 예외처리를 해야함(interruptedException이 발생하면 깨어남)
try {
Thread.sleep(1,5000000); //쓰레드를 0.0015초 동안 멈추게함
}catch(InterruptedException e){}
매번 예외처리하는게 불편해서 아래와 같이 메서드를 만들고 sleep대신 쓰기도함
void delay(long millis){
try{
Thread.sleep(millis);
}catch(InterruptedException e){}
}
-특정 쓰레드를 지정해서 멈추게 하는것은 불가능함(클래스 이름으로 호출하기)
class Ex13_8 {
public static void main(String args[]) {
ThreadEx8_1 th1 = new ThreadEx8_1();
ThreadEx8_2 th2 = new ThreadEx8_2();
th1.start(); th2.start();
try {
th1.sleep(2000);//메인쓰레드 sleep
} catch(InterruptedException e) {}
System.out.print("<<main 종료>>");
} // main
}
class ThreadEx8_1 extends Thread {
public void run() {
for(int i=0; i < 300; i++) System.out.print("-");
System.out.print("<<th1 종료>>");
} // run()
}
class ThreadEx8_2 extends Thread {
public void run() {
for(int i=0; i < 300; i++) System.out.print("|");
System.out.print("<<th2 종료>>");
} // run()
}
interrupt()
- 대기상태(WATING)인 쓰레드를 실행대기 상태 (RUNNABLE)로 만듦
void interrupt() //쓰레드의 interrupted상태를 false에서 true로 변경
boolean isInterrupted() //쓰레드의 interrupted상태를 반환
static boolean interrupted() //현재 쓰레드의 interrupted상태를 알려주고 false로 초기화
import javax.swing.JOptionPane;
class Ex13_9 {
public static void main(String[] args) throws Exception {
ThreadEx9_1 th1 = new ThreadEx9_1();
th1.start();
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하신 값은 " + input + "입니다.");
th1.interrupt(); //th1을 interrupt함. interrupt()를 호출하면, interrupted상태가 true가 된다.
System.out.println("isInterrupted():"+ th1.isInterrupted()); // true
System.out.println("Interrupted():"+ Thread.interrupted()); // false th1이 아닌 Thread로 써야함. 이떄는 메인스레드가 interrupt되었는지 확인함
}
}
class ThreadEx9_1 extends Thread {
public void run() {
int i = 10;
while(i!=0 && !isInterrupted()) { //거의 정해진 패턴. 어떤 작업을 수행하다가 멈추게 하고 싶으면 interrupt() - isinterrupted()로 어떤 스레드가 작업하고 있던것을 다른쓰레드가 멈추게 할 수 있음
System.out.println(i--);
for(long x=0;x<2500000000L;x++); // 시간 지연
}
System.out.println("Thread isInterrupted():"+ this.isInterrupted()); // true
System.out.println("Thread isInterrupted():"+ this.isInterrupted()); // true //호출해도 매번 똑같은값
//isInterrupted()와 달리 interrupted()는 interrupted라는 상태변수를 false로 초기화
System.out.println("Thread Interrupted():"+ Thread.interrupted()); // true
System.out.println("Thread Interrupted():"+ Thread.interrupted()); // false //다시 초기화됨
System.out.println("카운트가 종료되었습니다.");
}
}
**static 메서드인게 중요함 **
interrupted()는 인터럽트 상태를 확인하고 초기화하는 데 사용-> 한 번만 확인하려는 경우에 유용.
isInterrupted()는 인터럽트 상태를 확인하고 초기화하지 않으므로 반복해서 인터럽트 상태를 확인하고자 할 때 유용
초기화하지 않는 isInterrupted()를 사용하는 이유 : 스레드의 인터럽트 상태를 여러 번 확인할 때 그 값을 유지하기 위해
void suspend() : 쓰레드를 일시정지 시킴
void resume() : suspend()에 의해 일시정지된 쓰레드를 실행대기상태로 만듦
void stop() : 쓰레드를 즉시 종료시킴
deprecated됨 : 사용 X -> dead-lock일으키기 쉬운 메서드들 (간단한 프로그램에서는 상관없음)
- Thread 클래스에 정의된 suspended, stopped, resume 대신 직접 구현가능
class TrheadEx17_1 implements Runnable{
boolean suspended = false; //일시정지 상태를 저장하기 위한 변수
boolean stopped = false; // 정지 상태를 저장하기 위한 변수
public void run(){
while(!stopped){ //stop가 false일때만 실행
if(!suspended){ //suspended가 false일때만 실행
/*쓰레드가 수행할 코드 작성*/
}
}
public void suspend(){ suspended = true;}//suspend가 호출되면 suspended가 true로
public void resume() { suspended = false;}
public void stop() {stoped =true;}
- Thread 클래스에 정의된 suspended, stopped, resume 사용 -> deprecated
class Ex13_10 {
public static void main(String args[]) {
RunImplEx10 r = new RunImplEx10();
Thread th1 = new Thread(r, "*");// r객체를 실행하는 스레드를 생성하고 이 스레드의 이름을 "*"로 설정
Thread th2 = new Thread(r, "**");
Thread th3 = new Thread(r, "***");
th1.start();
th2.start();
th3.start();
try {
Thread.sleep(2000);
th1.suspend(); // 쓰레드 th1을 잠시 중단시킨다.
Thread.sleep(2000);
th2.suspend();
Thread.sleep(3000);
th1.resume(); // 쓰레드 th1이 다시 동작하도록 한다.
Thread.sleep(3000);
th1.stop(); // 쓰레드 th1을 강제종료시킨다.
th2.stop();
Thread.sleep(2000);
th3.stop();
} catch (InterruptedException e) {}
} // main
}
class RunImplEx10 implements Runnable {
public void run() {
while(true) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch(InterruptedException e) {}
}
} // run()
}
- 직접 정의
class Ex13_10 {
public static void main(String args[]) {
MyThread th1 = new MyThread("*");
MyThread th2 = new MyThread("**");
MyThread th3 = new MyThread("***");
th1.start();
th2.start();
th3.start();
try {
Thread.sleep(2000);
th1.suspend(); // 쓰레드 th1을 잠시 중단시킨다.
Thread.sleep(2000);
th2.suspend();
Thread.sleep(3000);
th1.resume(); // 쓰레드 th1이 다시 동작하도록 한다.
Thread.sleep(3000);
th1.stop(); // 쓰레드 th1을 강제종료시킨다.
th2.stop();
Thread.sleep(2000);
th3.stop();
} catch (InterruptedException e) {}
} // main
}
class MyThread implements Runnable {
volatile boolean suspended =false; //volatile : 쉽게 바뀌는 변수
volatile boolean stopped =false;
Thread th;
MyThread(String name){
th= new Thread(this, name);//Thread(Runnable r, String name)
}
void start() {
th.start();
}
void stop() {
stopped=true;
}
void suspend() {
suspended=true;
}
void resume() {
suspended=false;
}
public void run() {
while(!stopped) {
if(!suspended) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch(InterruptedException e) {}
}
}
} // run()
}
volatile 키워드를 변수에 적용하면 해당 변수를 메모리에서 캐시로 읽지 않고 항상 메인 메모리에서 읽거나 쓰도록함
volatile 변수의 특징
- 가시성 : volatile 변수의 값은 항상 메인 메모리에서 읽거나 쓰이므로 다른 스레드에서 변경된 값을 즉시 볼 수 있음 -> 하나의 스레드에서 변경한 값을 다른 스레드에서 확인할때 동기화문제 발생 x
- 순서 : 변수의 변경이 항상 변수의 이전 값에 따라 순서 유지
volatile 변수를 수정하는 스레드가 해당 변수를 수정한 순서대로 다른 스레드에서도 해당 변수를 읽어야 함.
( "Happens-Before" 관계)
다음과 같은 상황에서 중요하게 작용
- 하나의 스레드에서 volatile 변수를 수정한 후, 다른 스레드에서 이 변수를 읽는 경우: volatile 변수를 수정한 스레드에서의 변경 사항이 다른 스레드에서 읽히기 전까지 해당 변수의 변경 사항은 보장
- volatile 변수를 사용하여 스레드 간 통신을 하는 경우: volatile 변수를 통해 정보를 공유하는 스레드는 변수를 수정한 순서대로 다른 스레드에서 해당 정보를 읽을 수 있어야함
ex) flag 변수가 volatile로 선언
public class Example {
private static volatile boolean flag = false;
public static void main(String[] args) {
Thread writerThread = new Thread(() -> {
// flag 변수를 true로 설정
flag = true;
// 다른 작업 수행
});
Thread readerThread = new Thread(() -> {
// flag 변수가 true가 될 때까지 대기
while (!flag) {
// flag 변수를 읽어옴
}
// flag 변수가 true일 때 실행됨
System.out.println("Flag is true.");
});
writerThread.start();
readerThread.start();
}
}
이 코드에서 writerThread가 flag 변수를 true로 설정하면, readerThread에서는 flag 변수를 계속해서 읽어보고, flag 변수가 true가 될 때까지 대기.
volatile 변수인 flag는 항상 최신 값을 메인 메모리에서 읽으므로, writerThread에서 설정한 flag 변수의 변경 사항이 readerThread에서 올바르게 확인!
- lock-free : 락없이 동기화 달성 가능
volatile 변수는
1. 변수가 여러 스레드에서 읽기만 하는경우
2. 변수가 다른 스레드에서 변경된 값을 빠르게 확인해야하는경우
3. 변수가 상태 플래그 또는 프래그 변수로 사용되는 경우
에 유용하게 사용됨
변수에 대한 읽기와 쓰기 연산만을 보장하며, 변수간 복합적인 조작에 대해서는 보장하지 않음. 복합적인 조작에 대한 동기화는 synchronized블록 또는 lock 인터페이스와 같은 동기화 메커니즘을 사용해야함.
join()
- 지정된 시간동안 특정 쓰레드가 작업하는 것을 기다림
void join() // 작업이 모두 끝날 때까지
void join(long millis) //얼마나 기다릴지에 대한 시간을 줄 수 있음 : 천분의 일초동안
void join(long millis, int nanos) // 천분의 일초 + 나노초 동안
- sleep와 동일하게 예외처리를 해야함(interruptedException이 발생하면 작업재개)
class Ex13_11 {
static long startTime = 0;
public static void main(String args[]) {
ThreadEx11_1 th1 = new ThreadEx11_1();
ThreadEx11_2 th2 = new ThreadEx11_2();
th1.start();
th2.start();
startTime = System.currentTimeMillis(); //현재시간 저장(시작시간)
try {
th1.join(); // main쓰레드가 th1의 작업이 끝날 때까지 기다린다.
th2.join(); // main쓰레드가 th2의 작업이 끝날 때까지 기다린다.
} catch(InterruptedException e) {}
System.out.print("소요시간:" + (System.currentTimeMillis() - Ex13_11.startTime));//종료시간에서 시작시간을 뻄
} // main
}
class ThreadEx11_1 extends Thread {
public void run() {
for(int i=0; i < 300; i++) {
System.out.print(new String("-"));
}
} // run()
}
class ThreadEx11_2 extends Thread {
public void run() {
for(int i=0; i < 300; i++) {
System.out.print(new String("|"));
}
} // run()
}
ex ) 메모리가 부족할때 gc를 깨우고 메모리를 정리하는 시간을 기다리는데 join을 사용
yield()
-남은 시간을 다름 쓰레드에게 양보하고, 자신(현재 쓰레드)은 실행대기함
-static 메서드 : 쓰레드 자기자신한테만 사용가능
- yield()와 interrupt()를 적절히 사용하면 응답성과 효율성을 높일수 있음
- Os스케줄러에게 통보해주는 것이기 떄문에 반드시 동작한다는 보장이 없음.
public void run() {
while(!stopped) {
if(!suspended) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch(InterruptedException e) {}
}
else
Thread.yield(); //stopped가 False이고 suspended가 True이면 busy-waiting가 발생하므로 yield를 사용하는 것이 좋다
}
} // run()
void stop() {
stopped=true;
th.interrupt(); //자고있을 확률이 있으니까 interrupt 호출 -> 응답성이 좋아짐
}
void suspend() {
suspended=true;
th.interrupt();
}
쓰레드의 동기화
- 멀티 쓰레드 프로세스는 다른 쓰레드의 작업에 영향을 미칠 수 있음
- 진행중인 작업이 다른 쓰레드에게 간섭받지 않게 하려면 동기화가 필요
- 쓰레의 동기화 : 한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하게 하는것
- 동기화하려면 간섭받지 않아야 하는 문장들을 '임계 영역'으로 설정
- 임계영역은 락(lock)을 얻은 단 하나의 쓰레드만 출입가능(객체 1개에 락 1개)
synchronized
- 임계영역을 설정하는 방법
1. 메서드 전체를 임계 영역으로 지정
public synchronized void calcSum(){
/* 임계 영역*/
}
2. 특정한 영역을 임계 영역으로 지정 **
synchronized(객체의 참조변수){
/*임계영역*/
}
-임계 영역은 한번에 한 쓰레드만 사용할 수 있어서 영역을 최소화해야함! -> 임계영역을 많을 수록 성능 떨어짐 (멀티쓰레드의 장점 상실)
class Ex13_13 {
public static void main(String args[]) {
Runnable r = new RunnableEx13();
new Thread(r).start();
new Thread(r).start();
}
}
class Account2 {
private int balance = 1000; // private으로 해야 동기화가 의미가 있다. (변수에 직접접근이 되면 안되기 떄문)
//입금 메서드
public synchronized int getBalance() { //읽을때도 동기화 해줘야함 - 읽는 동안 값이 바뀌면 안됨
return balance;
}
//출금메서드
public synchronized void withdraw(int money){ // synchronized로 메서드를 동기화
if(balance >= money) {
try { Thread.sleep(1000);} catch(InterruptedException e) {}
balance -= money;
}
} // withdraw
}
class RunnableEx13 implements Runnable {
Account2 acc = new Account2();
public void run() {
while(acc.getBalance() > 0) {
// 100, 200, 300중의 한 값을 임으로 선택해서 출금(withdraw)
int money = (int)(Math.random() * 3 + 1) * 100;
acc.withdraw(money);
System.out.println("balance:"+acc.getBalance());
}
} // run()
}
synchronized가 없는 경우 - 통장잔고가 음수가 나올 수 있음
어떤 스레드가 돈을 빼는 작업까지 진행하기 전 들어온 스레드가 있을 수 있음
동기화 시 주의점 : 동기화 해야되는 객체를 읽고 쓰는 모든 메서드를 동기화 해야함 (ex 출금, 입금)
wati()과 noify()
- synchonized 를 사용하면 데이터는 보호가 되는데 한번에 한스레드만 들어갈 수 있으니 효율이 떨어짐
- 동기화의 효율을 높이기 위해 wait()와 notfy()를 사용함
- Object 클래스에 정의되어 있으며, 동기화 블록 내에서만 사용가능
- wait() : 객체의 lock를 풀고 쓰레드를 해당 객체의 watiing pool에 넣음
- notify() : wating pool에서 대기중인 쓰레드 중의 하나를 꺠움
- notifyAll() : waiting pool에서 대기중인 모든 쓰레드를 깨움 (일반적으로 notify보다 notifyAll을 쓰는것이 좋음 - 어쩌피 lock를 얻는건 1개)
[문제점] 음식이 없을 떄 , 손님이 Table의 lock을 쥐고 안놓음 -> 요리사가 lock을 얻지 못해서 Table에 음식을 추가하지 못함
[해결책] 음식이 없을떄, wait()로 손님이 lock을 풀고 기다리게함 -> 요리사가 음식을 추가하면 notify()로 손님에게 알림(손님이 lock재획득)
import java.util.ArrayList;
class Customer2 implements Runnable {
private Table2 table;
private String food;
Customer2(Table2 table, String food) {
this.table = table;
this.food = food;
}
public void run() {
while(true) {
try { Thread.sleep(100);} catch(InterruptedException e) {}
String name = Thread.currentThread().getName();
table.remove(food);
System.out.println(name + " ate a " + food);
} // while
}
}
class Cook2 implements Runnable {
private Table2 table;
Cook2(Table2 table) { this.table = table; }
public void run() {
while(true) {
int idx = (int)(Math.random()*table.dishNum());
table.add(table.dishNames[idx]);
try { Thread.sleep(10);} catch(InterruptedException e) {}
} // while
}
}
class Table2 {
String[] dishNames = { "donut","donut","burger" }; // donut의 확률을 높인다.
final int MAX_FOOD = 6;
private ArrayList<String> dishes = new ArrayList<>();
public synchronized void add(String dish) {
while(dishes.size() >= MAX_FOOD) {
String name = Thread.currentThread().getName();
System.out.println(name+" is waiting.");
try {
wait(); // COOK쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
}
dishes.add(dish);
notify(); // 기다리고 있는 CUST를 깨우기 위함.
System.out.println("Dishes:" + dishes.toString());
}
public void remove(String dishName) {
synchronized(this) {
String name = Thread.currentThread().getName();
while(dishes.size()==0) {
System.out.println(name+" is waiting.");
try {
wait(); // CUST쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
}
while(true) {
for(int i=0; i<dishes.size();i++) {
if(dishName.equals(dishes.get(i))) {
dishes.remove(i);
notify(); // 잠자고 있는 COOK을 깨우기 위함
return;
}
} // for문의 끝
try {
System.out.println(name+" is waiting.");
wait(); // 원하는 음식이 없는 CUST쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
} // while(true)
} // synchronized
}
public int dishNum() { return dishNames.length; }
}
class Ex13_15 {
public static void main(String[] args) throws Exception {
Table2 table = new Table2();
new Thread(new Cook2(table), "COOK").start();
new Thread(new Customer2(table, "donut"), "CUST1").start();
new Thread(new Customer2(table, "burger"), "CUST2").start();
Thread.sleep(2000);
System.exit(0);
}
}
요리사는 테이블에 임식이 가득차면 대기하고 음식을 추가하면 손님에게 통보
손님은 음식이 없으면 대기하고 음식을 먹고나면 요리사에게 통보
- wait()와 notify는 대상이 불분명함 (요리사와 손님 모두 같은 대기실에서 기다림) -> lock&condition
Lock
쓰레드 동기화 방법 (java.util.concurrent.locks 패키지에서 제공)
synchronized 블럭을 사용하면 같은 메서드 내에서만 lock을 걸수 있다는 제약사항과 쓰레드를 구분해서 통지가 불가능함
자동적으로 lock의 잠금과 해제가 관리되는 synchronized와 달리 수동으로 lock를 잠그고 해제해야함
- ReentrantLock : 재진입이 가능한 lock. 가장 일반적인 배타적(Exclusive) lock / 특정 수식어가 없는경우 이를 말함
* 생성자
ReetrantLock()
ReetrantLock(boolean fair) : 생성자의 매개변수 true를 주면 lock이 풀렸을떄가장 오래 기다린 쓰레드가 lock을 획
득할 수 있게 함 (성능 떨이짐)
* 메서드
void lock() : lock를 잠금
void unlock() : 잠금 해지
boolean isLocked() : lock이 잠겼는지 확인
public void ex(){
reetrantLock.lock();
try{
//임계영역
} finally{
retrantLock.unlock();
}
}
- ReentrantReadWriteLock : 읽기에는 공유적이고, 쓰기에는 배타적인 lock (읽기 lock이 걸려있으면 다른 쓰레드가 읽기 lock를 중복해서 걸고 읽기를 수행할 수 있지만 읽기 lock이 걸린 상태에서 쓰기 lock을 거는 것은 허용되지 않음/ 반대도 마찬가지)
- StampedLock : ReentrantReadWriteLock에 낙관적 읽기(Optimistic Reading) lock의 기능을 추가
lock를 걸거나 해지할때 stamp(long타입 정수)를 사용함 / 쓰기와 읽기가 충돌하는 경우에만 쓰기가 끝난 후 읽기 lock을 검
구분할 쓰레드 종류에 따라 각각의 Condition을 따로 만들어서 각각의 waiting pool에서 따로 기다리도록 할 수 있음
Condition은 이미 생성된 lock로부터 newCondition()을 호출하여 생성
public class Table {
String[] dishNames = {"피자", "피자", "치킨"};//피자가 더 자주 나온다
final int MAX_FOOD = 6;
private ArrayList<String> dishes = new ArrayList<>();
private ReentrantLock lock = new ReentrantLock();
private Condition custCond = lock.newCondition();
private Condition cookCond = lock.newCondition();
public void add(String dish) {
lock.lock();
try {
if (isFull()) {
String name = Thread.currentThread().getName();
System.out.println(name + " is waiting");
try {
//wait();
cookCond.await();
Thread.sleep(500);
} catch (InterruptedException e) {}
}
dishes.add(dish);
//notify();
cookCond.signal();
System.out.println("Dishes: " + dishes.toString());
} finally {
lock.unlock();
}
}
private boolean isFull() {
return dishes.size() >= MAX_FOOD;
}
public boolean remove(String dish) {
lock.lock();
try {
while (isEmpty()) {
System.out.println(Thread.currentThread().getName() + " is waiting");
try {
custCond.await();
Thread.sleep(500);
} catch (InterruptedException e) {}
}
while (true) {
for (String dishName : dishes) {
if (dishName.equals(dish)) {
dishes.remove(dish);
cookCond.signal();
return true;
}
}
try {
System.out.println(Thread.currentThread().getName() + " is waiting");
custCond.await();
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
} finally {
lock.unlock();
}
}
private boolean isEmpty() {
return dishes.isEmpty();
}
public double dishNum() {
return dishNames.length;
}
}
public class Customer implements Runnable {
private Table table;
private String food;
public Customer(Table table, String food) {
this.table = table;
this.food = food;
}
@Override
public void run() {
while (true) {
try { Thread.sleep(100); } catch (InterruptedException e) {}
eatFood();
}
}
private void eatFood() {
String name = Thread.currentThread().getName();
table.remove(food);
System.out.println(name + " ate a " + food);
}
}
public class Cook implements Runnable {
private Table table;
public Cook(Table table) {
this.table = table;
}
@Override
public void run() {
while (true) {
int idx = (int)(Math.random() * table.dishNum());
table.add(table.dishNames[idx]);
try {
Thread.sleep(10);
} catch (InterruptedException e) {}
}
}
}
public class Temp_13 {
public static void main(String[] args) throws InterruptedException {
Table table = new Table();
new Thread(new Cook(table), "COOK1").start();
new Thread(new Customer(table, "피자"), "CUST1").start();
new Thread(new Customer(table, "치킨"), "CUST2").start();
Thread.sleep(2000);
System.exit(0);
}
}
'Study > java' 카테고리의 다른 글
chapter 14 (0) | 2023.11.09 |
---|---|
chapter 10 (0) | 2023.11.05 |
chapter 12 (0) | 2023.08.31 |
chapter 9 (0) | 2023.08.12 |
chapter 11 (0) | 2023.08.07 |