[Java] Process - Thread, synchronized, deadLock, semaphore
원지다
2022. 10. 31. 14:01
728x90
Thread
프로그램에서 가장 중요한 개념 중의 하나
실제 생성해서 사용하는 경우는 안드로이드나 자바 애플리케이션을 만들 때 이고, Web Programming 에서는 직접 생성해서 사용하는 경우가 드뭄
작업 단위 (Process / Thread)
Process (공장): 실행 중인 프로그램, 자원(메모리, cpu 등) + thread 프로세서를 할당받아서 실행되는 것 한 번 실행되면 자신의 작업이 종료될 때 까지 제어권을 다른 프로세스에게 넘기지 않고 계속 수행 절대로 멈추지 않음
Thread (일꾼): Process를 작게 나누어서 작업을 수행하는 단위 Thread는 단독으로 실행할 수 없고Process 안에서 실행되야 함 자신의 작업 도중 쉬는 시간이 생기거나 일정한 시간이 지나면 다른 스레드에게 제어권을 양도할 수 있음 동시에 처리하는 것은 아님 (그건 Parallel), 다른 Thread가 동시에 수행하는 것은 가능
Thread Programming 할 때 반드시 알아야 될 사항
하나의 스레드가 사용 중인 자원은 다른 스레드가 수정하면 안됨 Mutual Exclusion(상호 배제), Synchronus(동기화)
생상자 와 소비자 문제 소비자는 생상자가 물건을 생성해 주어야만 작업을 수행
Dead Lock (교착 상태) 결코 발생할 수 없는 사건을 무한정 기다리는 것 여러개 같이 작업할 때 무한 대기 상태가 되는 것
try ~ catch Thread에서 발생한 예외 처리 하기
Thread 생성
## Thread 클래스를 상속받으면 다른 클래스를 상속받을 수 없기 때문에, Runnable 인터페이스를 구현하는 방법이 일반적 - run()을 오버라이딩 하여 그 안에 작업 내용을 적으면 됨
1. Thread 클래스로부터 상속받는 클래스를 만들고 public void run 이라는 메서드에 스레드로 수행할 내용을 작성한 후 인스턴스를 만들고start메서드를 호출 - run 메서드의 내용을 수행(run메서드 호출하면 안됨, 스레드가 아닌 프로세스처럼 동작되므로)
2. Runnable 인터페이스로부터 상속받는 클래스를 만들고 public void run 이라는 메서드에 스레드로 수행할 내용을 작성한 후 Thread 클래스의 인스턴스를 만들 때 생성자에 생성한 클래스의 인스턴스를 대입하고 Thread 클래스의 인스턴스가 start 메서드를 호출하면 됨
3. Callable 인터페이스를 구현한 클래스를 이용해서도 생성 가능
Thread 실행
생성 후 start() 호출해야만 실행
호출되었다고 바로 실행되는 것이 아니라 일단 실행 대기 상태
실행 순서는 OS의 스케줄러가 작성한 스케줄에 의해 결정 (임의로), 어떤 순서에 의해 동시성으로 실행할 것인가?
한번 종료된 쓰레드는 다시 실행할 수 없음, 두 번 호출하면 IllegalThreadStateException 발생
하나의 쓰레드에 대해 start()가 한 번만 호출 될 수 있으므로 쓰레드 작업을 한 번 더 수행해야 한다면 새로운 쓰레드를 생성한 후 start() 호출해야 함
run() 메서드를 작성했는데 왜 start()를 호출 하지? - run()을 호출하는 것은 생성된 쓰레드를 실행시키는 것이 아니라 단순히 클래스에 선언된 메서드 호출 - start()는 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택(call stack)을 생성한 후 run() 호출 ▶ 모든 쓰레드는 독립적인 작업을 수행하기 위해 자신만의 호출스택을 필요로 함
1. main 메서드에서 쓰레드의 start() 호출 2. start()는 새로운 쓰레드 생성, 작업하는데 사용될 호출 스택 생성 3. 호출 스택에 run() 호출되어 쓰레드가 독립된 공간에서 작업 수행 4. 호출 스택이 2개 이므로 스케줄러가 정한 순서에 의해 번갈아 가면서 실행
package chapter13;
public class ThreadCreate {
public static void main(String[] args) {
ThreadCreate_1 t1 = new ThreadCreate_1();
Runnable r = new ThreadCreate_2();
Thread t2 = new Thread(r);
//Thread t2 = new Thread(new ThreadCreate_2()); 한줄로
t1.start();
t2.start();
}
} //end main
class ThreadCreate_1 extends Thread {
@Override
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println(getName());
}
}
} //end ThreadCreate_1
class ThreadCreate_2 implements Runnable {
@Override
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName());
//currentThread() - 현재 실행중인 쓰레드의 참조 반환
//getName() - 쓰레드의 이름 반환
//자동적으로 이름은 Thread-n(숫자) 설정
}
}
} //end ThreadCreate_2
-----------------------------------------------------------------------
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
ThreadCreate -1. Thread 클래스로부터 상속받는 클래스 생성
package _api.thread;
//Thread 클래스로부터 상속받는 클래스를 생성
class ThreadEx extends Thread {
//public void run 이라는 오버라이딩
@Override
//위의 어노테이션은 상위 클래스 나 인터페이스에서 제공하는 메서드가 아닌 경우
//에러를 발생시켜 주는 어노테이션임
public void run() {
//스레드로 수행할 내용
//1초마다 Thread 클래스라는 문장을 10번 실행
for(int i = 0; i < 10; i++) {
try {
Thread.sleep(1000); //try, catch 예외 안에 작성
System.out.println("Thread 클래스");
}catch(Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
}
}
public class ThreadCreate {
public static void main(String[] args) {
//Thread 클래스로부터 상속받은 클래스를 이용해서 스레드를 생성하고 실행
ThreadEx th = new ThreadEx();
//start를 호출하면 run 메서드의 내용을 수행
th.start();
}
}
run메서드 호출하면 Thread가 아닌 Process처럼 동작되므로 start() 호출
ThreadCreate -2. Runnable 인터페이스로부터 상속받는 클래스
3.Runnable 인터페이스 -Anonymous Class를 이용
4.Runnable 인터페이스 -람다를 이용해서 작성
package _api.thread;
//Runnable 인터페이스를 구현한 클래스를 생성
class RunnableImpl implements Runnable {
@Override
public void run() {
for(int i = 0; i < 10; i++) {
try {
Thread.sleep(1000); //try, catch 예외 안에 작성
System.out.println("Runnable 인터페이스");
}catch(Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
}
}
public class ThreadCreate {
public static void main(String[] args) {
//Runnable 인터페이스를 implements 클래스를 이용해서 스레드를 생성하고 실행
Thread th2 = new Thread(new RunnableImpl());
th2.start();
//Runnable 인터페이스를 Anonymous Class를 이용해서 사용
Thread th3 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i < 10; i++) {
try {
Thread.sleep(1000); //try, catch 예외 안에 작성
System.out.println("Anonymous 활용");
}catch(Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
}
});
th3.start();
//Runnable 인터페이스는 public void run 메서드 1개만 소유
//람다를 이용해서 작성하는 것도 가능
Thread th4 = new Thread(() -> {
for(int i = 0; i < 10; i++) {
try {
Thread.sleep(1000); //try, catch 예외 안에 작성
System.out.println("Lambda 활용");
}catch(Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
});
th4.start();
}
}
Thread 종류
1. Main Thread
main 메서드의 코드를 수행하는 쓰레드
실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램 종료
non-deamon thread
2. Daemon Thread
다른 스레드가 수행 중이 아니면 자동으로 종료되는 스레드
스레드의 역할을 도와주는 보조적인 목적으로 사용
start 하기 전에 setDaemon 메서드를 호출하면 되는데 true 값을 설정해주면 됨
ex) MMORPG에서 사냥터에 사람이 없으면 몬스터 생성 중지하는 것 ex) 가비지 컬렉터, 워드프로세서의 자동저장, 화면자동갱신 등
boolean isDaemon() //데몬스레드인지 확인
void setDaemon(boolean on) //스레드 → 데몬 스레드 or 사용자 스레드오 변경, on값을 true로 지정하면 데몬 스레드가 됨
package _api.thread;
public class DaemonThread {
public static void main(String[] args) {
//1부터 10까지 1초씩 딜레이하면서 출력해주는 스레드
Thread th = new Thread(() -> {
for(int i = 1; i <= 10; i++) {
try {
Thread.sleep(1000);
System.out.println(i);
}catch(Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
});
//데몬 스레드로 설정 - 다른 작업이 없으면 자동을 종료
th.setDaemon(true); //setDaemon(true) 1,2,메인 종료 끝
th.start(); //위 줄 없으면 무조건 본인 작업 끝마침
try {
Thread.sleep(3000);
System.out.println("메인 종료");
}catch(Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
}
주의점은 스레드를 실행하는 start() 메소드가 호출되고 나서 setDaemon(true)를 호출하면 IllegalThreadStateException이 발생하기 때문에start() 메소드 호출 전에 setDaemon(true)를 호출해야 함
package chapter13;
public class DaemonThread implements Runnable{
static boolean autoSave = false;
public static void main(String[] args) {
Thread t = new Thread(new DaemonThread());
t.setDaemon(true); //데몬 스레드 지정
t.start();
//main 스레드 - 1부터 10까지 카운트하는 루프를 실행
for(int i = 1; i <= 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) { }
System.out.println(i);
if(i == 5) autoSave = true;
}
} //end main
//daemon 스레드
@Override
public void run() {
while(true) {
try {
Thread.sleep(3 * 1000); //3초마다 변수 autoSave 값을 인하여 autoSave() 메서드 호출
} catch (InterruptedException e) { }
//autoSave값이 true이면 autoSave 호출
if(autoSave) autoSave();
}
} //end run
public void autoSave() {
System.out.println("작업파일이 자동저장 되었습니다.");
}
}
--------------------------------------------------------------------------------------------
1
2
3
4
5
작업파일이 자동저장 되었습니다.
6
7
8
작업파일이 자동저장 되었습니다.
9
10
Thread의 매개값으로 Runnable 구현한 인스턴스를 넣고, 데몬 스레드 설정 후 시작(3초부터 시작됨) - main 스레드 시작(1초부터 시작됨)
main 스레드에서는 1부터 10까지 카운트 다운
1초마다 i를 출력하고, i가 5가 되면 autoSave를 true로 변환
daemon 스레드에서는 무한루프로 3초마다 autoSave값을 확인, 5초가 될 때 main에서 true로 변환하므로 autoSave() 메서드를 호출하여 "작업파일 자동저장 문구" 출력 ▶ daemon thread는 5초 이후부터 autoSave = true가 되므로 그때부터 문구 출력
Multi Thread
2개 이상의 Thread가 수행중인 상황
대부분의 경우 (99.9% 이상)
I/O 블락킹
입출력 처리를 위해 기다리는 것
두 쓰레드가 서로 다른 자원을 사용하는 경우 멀티 쓰레드 사용
I/O: 외부기기와의 입출력을 필요로 하는 경우, 시간이 오래 걸리는 작업 (파일 입출력, 네트워크 입출력, 화면 입출력) 연산: 시간이 짧게 걸리는 작업
I/O가 먼저 실행되면 기다리는데 너무 오래 걸리니까 연산 먼저 실행해야 함 시간이 오래 걸리는 작업은 Thread 나 비동기(순서대로 하지 않는 것) 필수
Thread의 동시성, 병렬성
동시성(Concurrency): 하나의 코어에서 멀티 스레드가 번갈아가며 실행
병렬성(Parallelism): 멀티 코어에서 개별 스레드를 동시에 실행하는 성질
스케줄링: 스레드의 개수가 코어 수보다 많을 경우, 어떤 순서에 의해 동시성으로 실행할 것인가
우선순위(Priority) 방식: 우선순위가 높은 스레드가 실행 상태를 더 많이 가지도록 스케줄링
순환 할당(Round-Robin) 방식: 시간 할당량을 정해서 하나의 스레드를 정해진 시간만큼 실행하고 다시 다른 스레드를 실행하는 방식, 자바 가상기계에 의해서 정해지기 때문에 코드로 제어할 수 없음
Thread Pirority(우선 순위) - 추가 / 먼저 / 자주
작업 중요도에 따라 우선순위를 다르게 하여 더 많은 작업 시간을 갖게 할 수 있음
여러 개의 스레드가 수행될 때 어떤 스레드가 먼저 되고 어떤 스레드가 나중에 수행될지는 알 수 없음
우선 순위를 설정하면 확률적으로 먼저 또는 자주 실행되도록 할 수 있음
setPriority(int priority) 메서드 이용 매개변수가 정수이지만 아무 정수나 사용하는 것은 안되고 제약이 있음 일반 정수를 사용해도 에러는 발생하지 않지만 되도록이면 Thread.MAX_PRIORITY, NORMAL, MIN의 상수를 사용하는 것을 권장
void setPriority(int newPriority) //우선순위를 지정한 값으로 변경
int getPriority() //우선순위를 반환
public static final int MAX_PRIORITY = 10 //최대 우선순위
public static final int MIN_PRIORITY = 1 //최소 우선순위
public static final int NORM_PRIORITY = 5 //기본 우선순위
package chapter13;
public class ThreadPriority {
public static void main(String[] args) {
ThreadPriority_1 th1 = new ThreadPriority_1();
ThreadPriority_2 th2 = new ThreadPriority_2();
th2.setPriority(7);
System.out.println("Priority of th1(-) : " + th1.getPriority());
System.out.println("Priority of th2(|) : " + th2.getPriority());
th1.start();
th2.start();
}
} //end main
class ThreadPriority_1 extends Thread {
@Override
public void run() {
for(int i = 0; i < 300; i++) {
System.out.print("-");
for(int x = 0; x < 100000000; x++); //시간 지연용 for문
}
}
} //end ThreadPriority_1
class ThreadPriority_2 extends Thread {
@Override
public void run() {
for(int i = 0; i < 300; i++) {
System.out.print("|");
for(int x = 0; x < 100000000; x++);
}
}
} //end ThreadPriority_2
----------------------------------------------------------------------------------
Priority of th1(-) : 5
Priority of th2(|) : 7
-|-|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||--------------------
----------------------------------------------------------------------------------
-||||||||||||||||----|||||||-|||||||||||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||||--------||||||||||||||||||||||--
------------------------||||||||||------------------------------------------------
----------------------------------------------------------------------------------
--------------------------
Thread Group
여러개의 Thread 관리
관련된 스레드들은 하나의 그룹으로 묶어서 사용하기 위한 개념
스레드 그룹을 지정하지 않고 생성하면 자동적으로 main 스레드 그룹에 속함
자신을 생성한 스레드(부모 스레드)의 그룹과 우선순위를 상속 받음
ThreaGroup 이라는 클래스를 제공하지만 몇 몇 메서드가 제대로 동작하지 않는 문제 때문에 대부분의 경우 배열이나 List를 이용해서 구현하는 것을 권장
스레드 그룹에 다른 스레드 그룹을 포함시킬 수 있음
자바 어플리케이션이 실행되면, JVM은 main과 system이라는 스레드 그룹을 만들고 JVM 운영에 필요한 스레드들을 생성해서 이 스레드 그룹에 포함시킴 main 메서드 수행하는 main이라는 이름의 스레드 - main스레드 그룹 가비지컬렉션을 수행하는 Finalizer 스레드 - system 스레드 그룹
ThreadGroup getThreadGroup() //스레드 자신이 속한 스레드 그룹 반환
void uncaughtException(Thread t, Throwable e)
//처리되지 않은 예외에 의해 스레드 그룹의 스레드가 실행종료 되었을 때,
//JVM에 의해 이 메서드가 자동적으로 호출
Thread 종료
run 메서드 종료
main 스레드가 종료되어도 다른 스레드가 존재하면 끝난 것은 아님
스레드의 interrupt 메서드를 호출하고 스레드의 run 메서드 안에서 InterruptedException이 발생하면 run 메서드를 종료하도록 만들어서 강제 종료하도록 할 수 있음
Deamon Thread를 Daemon이 아닌 다른 스레드가 존재하지 않으면 자동으로 종료
Thread 상태
static이 붙은 메서드는 자기 자신을 제어함
resume, suspend, stop 메서드는 deprecated
Thread 실행 제어 메서드
void join(long millis, int nanos) : 지정된 시간동안 스레드를 수행하고 다른 스레드에게 제어권을 넘기는 메서드
void suspend() : 스데르르 일시정지시키는 메서드로 resume 메서드로 다시 시작
void resume() : suspend 된 스레드를 다시 시작
void yield() : 다른 스레드에게 제어권을 넘겨주는 메서드
1. sleep (long msec, [long nano])
[long nano]는 있을수도 있고 없을수도 있음
msec 밀리초(1/1000) 동안 현재 스레드를 중지
nano를 입력하면 msec 밀리초 + nano 나노초 만큼 대기
시간이 다 되거나 interrupt()가 호출되거나 InterrupedException이 발생되면 잠에서 깨거나 실행대기 상태가 됨
이 메서드를 사용할 때는 InterrupedException 을 처리해주어야 함 → 항상 try ~ catch문으로 예외처리 필수
실습을 할 때 이 메서드를 사용하는 이유는 일정 시간 이상 대기를 해야만 다른 스레드에게 제어권이 이동되기 때문
실제 시간의 의미는 거의 없음
//매번 예외처리 하는 것은 번거롭기 때문에 메서드 만들어서 사용
void delay(long msec) {
try {
Thread.sleep(msec);
} catch(InterruptedException e) {} //Exception의 자손, 필수 예외 처리
//예외와 try~catch를 이용해서 잠자는 상태에서 벗어나게 하기 위해 사용하므로 아무것도 쓰지 않아도 됨
}
특정 스레드를 지정해서 멈추게 하는 것은 불가능 (static, 자기 자신만)
package chapter13;
public class SleepOfThread {
public static void main(String[] args) {
SleepOfThread_1 th1 = new SleepOfThread_1();
SleepOfThread_2 th2 = new SleepOfThread_2();
th1.start();
th2.start();
try {
//main 스레드에서 실행되고 있기 때문에 th1이 sleep되는 게 아님
//th1.sleep(2000); 오류는 없지만 오해할 수 있음
Thread.sleep(2000);
} catch (InterruptedException e) {}
System.out.print("<<main 종료>>");
}// end main
}
class SleepOfThread_1 extends Thread {
@Override
public void run() {
for(int i = 0; i < 300; i++) {
System.out.print("-");
}
System.out.print("<<th1 종료>>");
}
} //end SleepOfThread_1
class SleepOfThread_2 extends Thread {
@Override
public void run() {
for(int i = 0; i < 300; i++) {
System.out.print("|");
}
System.out.print("<<th2 종료>>");
}
} //end SleepOfThread_2
---------------------------------------------------------------------------------------
------------||||||||||||||||||||||-----------------------------------------------------
------------------------------------------------------------------------------------|||
|||||||------|||||||||||||||||||||--------------------------||-------------------------
----------------------------------------------||||||--------------|||||||||||||||------
----------------------------||||||<<th1 종료>>|||||||||||||||||||||||||||||||||||||||||
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|||<<th2 종료>><<main 종료>>
2. interrup
대기 상태(sleep, join, wait) 스레드를 실행대기 상태로 만듦
public static void main(String[] args) {
ThreadEx th1 = new ThreadEx();
th1.start();
...
th1.interrupt(); //interrupted 상태를 false → true 변경
...
System.out.println("isInterrupted() : " + th1.isInterrupted()); //현재 상태인 true 반환
//static boolean interrupted() : 현재 상태를 반환 후, false로 변경, 누가 interrupted를 호출했는지 알 수 있음
}
package chapter13;
import javax.swing.JOptionPane;
public class InterruptOfThread {
public static void main(String[] args) throws Exception{
InterruptOfThread_1 th1 = new InterruptOfThread_1();
th1.start();
String input = JOptionPane.showInputDialog("아무값이나 입력하세요.");
System.out.println("입력하신 값은 " + input + "입니다.");
th1.interrupt();
System.out.println("isInterrupted() : " + th1.isInterrupted()); //true
}
}
class InterruptOfThread_1 extends Thread {
@Override
public void run() {
int i = 10;
while(i != 0 && !isInterrupted()) {
System.out.println(i--);
for(long x = 0; x < 2500000000L; x++); //시간 지연
}
System.out.println("카운트가 종료되었습니다.");
}
}
-----------------------------------------------------------------------------------
10
9
8
7
6
5
4
3
2
1
카운트가 종료되었습니다.
입력하신 값은 132입니다.
isInterrupted() : true
3. suspend, resume, stop
deprecated
volatile - 자주 바뀌니까 복사본 쓰지 말고, 원본 가져와서 써라
4. join, yield
join() - 지정된 시간동안 특정 스레드가 작업하는 것을 기다림, 예외처리 필수
yiedl() - static, 자신에게 주어진 실행 시간을 다음 차례의 스레드에게 양보
작업 시간 확인
package chapter13;
public class JoinOfThread {
static long startTime = 0;
public static void main(String[] args) {
JoinOfThread_1 th1 = new JoinOfThread_1();
JoinOfThread_2 th2 = new JoinOfThread_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() - JoinOfThread.startTime)); //종료 시간
}
}
class JoinOfThread_1 extends Thread {
@Override
public void run() {
for(int i = 0; i < 300; i++) {
System.out.print(new String("-"));
}
}
} //end JoinOfThread_1
class JoinOfThread_2 extends Thread {
@Override
public void run() {
for(int i = 0; i < 300; i++) {
System.out.print(new String("|"));
}
}
} //end JoinOfThread_2
---------------------------------------------------------------------------------------------
---------||||||||||||||||||||------------------||||||||||||||||||||||||||||||||||||||||||||||
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||-----------------------------------
-------------------|||||||||||||||||||||||---------------------------------------------------
---------------------------|||--|||--||||||||------------------||||||||||||||||||--||||||----
--|||----------|||||||||||||||-----------------||||||||||||||||||||||||||||||||||||||||||||||
||||||||||||-|||--||||||---------------||||||||||||||||||||||||||||||------------------------
------------------------------------------소요시간: 8
Synchronization
진행중인 작업이 다른 스레드에게 간섭받지 않게 하려면 동기화 필요
동기화하려면 간섭받지 않아야 하는 문장들은 '임계 영역(critical section)'으로 설정
ShareData implements Runnable - 공유 자원으로 사용할 데이터 클래스
package _api.thread.multithread;
//자원을 가지고 연산을 하는 스레드에 사용할 클래스
public class ShareData implements Runnable{
//연산 결과를 저장할 속성
private int result;
//연산에 사용할 인덱스
private int idx;
//result 의 getter 메서드
public int getResult() {
return result;
}
@Override
public void run() {
try{
for(int i = 0; i < 10; i++) {
// synchronized가 없으면 th1, th2가 있을 때 이상한 결과가 출력됨
synchronized(this) {
idx++;
Thread.sleep(10); //없거나 너무 작으면 0,1같은 작은 숫자가 나옴
result = result + idx;
}
}
}catch(Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
}
MutexMain - 공유 자원을 사용하는 실행 클래스
package _api.thread.multithread;
public class MutexMain {
public static void main(String[] args) {
//Runnable 인터페이스로부터 상속받은 클래스
ShareData shareData = new ShareData();
//스레드 생성
Thread th1 = new Thread(shareData);
th1.start();
//하나 했을때는 제대로 결과가 나왔는데 두개하니 잘못된 결과가 나옴
//for문이 두 개 있는 것과 같음
Thread th2 = new Thread(shareData);
th2.start();
try{
//30초 대기 - 앞의 작업이 스데르도 동작하기 때문에 작업이 끝날때까지 대기하고 결과를 출력
Thread.sleep(3000);
System.out.println(shareData.getResult());
}catch(Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
}
(synchronized를 안하고) 실행을 하면 이상한 결과가 출력
ShareData 인스턴스를 1개만 만들어서 공유하는데 하나의 작업이 완료되기 전에 다른 스레드가 공유 자원을 수정하기 때문에 발생하는 문제
해결방법 한번에 실행되어야 하는 코드를 가진 영역을 찾고 메서드에 synchronized를 붙이던가 코드 영역을 synchronized(공유객체)()로 묶어주면 됨 이렇게 묶어주면 묶인 영역의 코드는 동시에 수정할 수 없음 되도록이면 synchronized 메서드를 만드는 것보다는 블럭을 만드는 것을 권장함 메서드를 묶게 되면 영역이 커져서 공유도가 떨어지기 때문
ShareData 클래스를 수정해서 다시 실행
최근에는ReentrantLock이라는 클래스 이용을권장
인스턴스를 생성 (구현받는 클래스에) lock 메서드로 공유 영역을 만듦 unlock 메서드로 공유 영역 해제
ShareData2 implements Runnable - ReentrantLock 이라는 클래스를 이용
package _api.thread.multithread;
import java.util.concurrent.locks.ReentrantLock;
//자원을 가지고 연산을 하는 스레드에 사용할 클래스
public class ShareData2 implements Runnable{
//연산 결과를 저장할 속성
private int result;
//연산에 사용할 인덱스
private int idx;
//공유 코드 영역을 설정하기 위한 객체
static final ReentrantLock Lock = new ReentrantLock();
//result 의 getter 메서드
public int getResult() {
return result;
}
@Override
public void run() {
try{
for(int i = 0; i < 10; i++) {
//자물쇠를 채워서 unlock 을 만날때까지는 이 영역의 자원을 수정할 수 없음
Lock.lock();
idx++;
Thread.sleep(10); //없거나 너무 작으면 0,1같은 작은 숫자가 나옴
result = result + idx;
Lock.unlock();
}
}catch(Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
}
2. 생산자와 소비자 문제
→ wait 와 notification 으로 해결
생산자와 서비자는 동시에 수핼할 수 있는데 소비자는 생산자가 자원을 생성해준 경우에만 동작을 해야 함 소비자가 자원이 생성되지 않았는데 작업을 수행하면 예외가 발생
앞에서 하던게 멈췄는데 lock을 가지고 있으면 아무것도 못하니까 기다리라고 하고, 다음 꺼에 넘기는 것
동기화의 효율을 높이기 위해 사용
notify(아무거나 하나만 깨우기), notifyAll(전부다 깨우기)
Thread에 사용하는데 Object 클래스에 있음 - synchronized 해서 Thread에 있다고 알려줘야 함
package _api.thread;
import java.util.ArrayList;
import java.util.List;
//공유자원의 역할을 수행할 클래스 - 진열대 역할
public class Product {
//문자를 저장할 수 있는 List - 공유 자원
List<Character> list;
//생성자
public Product() {
list = new ArrayList<>();
}
//생산자 메서드
public synchronized void put(Character ch) {
list.add(ch);
System.out.println("창고에 제품" + ch + " 입고 되었습니다.");
try {
Thread.sleep(1000);
System.out.println("재고 수량 : " + list.size());
} catch (Exception e) {
System.out.println(e.getLocalizedMessage());
}
//자원을 생산했다고 알림
notify();
}
//소비자 메서드
public synchronized void get() {
try {
if(list.size() == 0) {
wait();
}
} catch (Exception e) {
System.out.println(e.getLocalizedMessage());
}
//첫번째 데이터를 꺼내서 ch에 대입
Character ch = list.remove(0);
System.out.println("창고에서 제품" + ch + " 출고 하였습니다.");
try {
Thread.sleep(1000);
System.out.println("재고 수량 : " + list.size());
} catch (Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
}
생산자 클래스 Producer - put
package _api.thread;
public class Producer extends Thread {
//공유 자원 속성
private Product myList;
//생성자
public Producer(Product myList) {
this.myList = myList;
}
@Override
public void run() {
for(char ch = 'A'; ch <= 'Z'; ch++) {
myList.put(ch);
}
}
}
소비자 클래스 Customer - get
package _api.thread;
public class Consumer extends Thread {
public Product myList;
public Consumer(Product myList) {
this.myList = myList;
}
@Override
public void run() {
for(int i = 0; i < 26; i++) {
myList.get();
}
}
}
Main 클래스
package _api.thread;
public class ConsumerMain {
public static void main(String[] args) {
Product prd = new Product();
new Producer(prd).start();
new Consumer(prd).start();
}
}
실행을 하다보면 예외 발생 소비자 스레드인 Customer 스레드가 리스트에 아무런 데이터가 없는데 꺼내려고 해서 발생하는 문제 소비자 스레드의 메서드는 데이터가 없을 때는 대기하고 (wait) 생산자 스레드는 데이터를 만들어 낸 경우 데이터가 만들어졌다고 알려줘야 함 (notify) 기다리기 위해서 호출하는 메서드는 wait 알려주는 메서드는 notify, notifyAll 메서드 이 메서드들은 Object 클래스의 메서드이고,synchronized 메서드 안에서만 동작
package _api.thread.multithread;
public class SemaphoreThread implements Runnable{
String message;
public SemaphoreThread(String message) {
this.message = message;
}
@Override
public void run() {
try {
Thread.sleep(10000);
System.out.println(message);
}catch(Exception e) {}
}
}
SemaphoreMain
package _api.thread.multithread;
public class SemaphoreMain {
public static void main(String[] args) {
Thread th1 = new Thread(new SemaphoreThread("제니"));
Thread th2 = new Thread(new SemaphoreThread("지수"));
Thread th3 = new Thread(new SemaphoreThread("리사"));
Thread th4 = new Thread(new SemaphoreThread("로제"));
th1.start();
th2.start();
th3.start();
th4.start();
//4개의 스레드가 10초 동시에 메세지로 출력
}
}
4개의 스레드가 10초 동시에 메세지로 출력
세마포어를 이용하는 경우
SemaphoreThread implements Runnable2
package _api.thread.multithread;
import java.util.concurrent.Semaphore;
public class SemaphoreThread implements Runnable{
String message;
Semaphore semaphore;
public SemaphoreThread(String message, Semaphore semaphore) {
this.message = message;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
//리소스 확보
semaphore.acquire();
Thread.sleep(10000);
System.out.println(message);
}catch(Exception e) {}
//리소스 해제, try 안에 써도 됨
semaphore.release();
}
}
SemaphoreMain2
package _api.thread.multithread;
import java.util.concurrent.Semaphore;
public class SemaphoreMain {
public static void main(String[] args) {
//2개씩 실행할 수 있는 세마포어
Semaphore semaphore = new Semaphore(2);
Thread th1 = new Thread(new SemaphoreThread("제니", semaphore));
Thread th2 = new Thread(new SemaphoreThread("지수", semaphore));
Thread th3 = new Thread(new SemaphoreThread("리사", semaphore));
Thread th4 = new Thread(new SemaphoreThread("로제", semaphore));
th1.start();
th2.start();
th3.start();
th4.start();
//4개의 스레드가 10초 동시에 메세지로 출력
}
}
Timer
Thread와 비슷
관습적으로 이름쓰는 규칙
일반적으로 개발자들은 클래스의 인스턴스를 1개만 생성할 때는 변수의 이름을 클래스 이름과 동일하게 만들고 첫글자만 소문자로 변경 String string = "apple";
어떤 클래스로부터상속받는 클래스를 1개만 만들 때는클래스 이름 뒤에 Ex를 붙임 Thread 클래스로부터 상속받는 클래스 class ThreadEx extends Thread {
}
인터페이스를 구현한 클래스를 1개만 만들 때는클래스 이름 뒤에 impl을 붙임 Runnable 인터페이스를 구현한 클래스 class RunnableI(i)mpl implements Runnable{
}
extends 하거나 implements 했을 때 변수의 이름은 상위 클래스 이름이나 인터페이스 이름을 사용 ArrayList<String> list = new ArrayList<>(); ▶ List<String> list = new ArrayList<>(); ThreadEx th = new TreadEx(); ▶ Thread th = new TreadEx();