2023_1_15_TIL
동기화의 개념
- 동기화(synchronized) -> 하나의 작업이 완전히 완료된후 다른 작업을 수행하는 것
- 비동기화(asynchronous) -> 하나의 작업 명령 후 완료 여부와 상관없이 바로 다른 작업 명령을 수행
동기화의 필요성
- 동기화를 사용하지 않았을 때 -> 스레드가 겹침(원하는 값이 나오지 않음)
class MyThread extends Thread {
@Override
public void run() {
System.out.println(getName() + ": " + (isDaemon() ? "데몬스레드" : "일반스레드"));
for (int i = 0; i < 6; i++) {
System.out.println(getName() + ": " + i + "초");
try {Thread.sleep(1000);} catch (InterruptedException e){}
}
}
}
public class ThreadProperties_3_3 {
public static void main(String[] args) {
Thread thread1 = new MyThread();
thread1.setDaemon(false);
thread1.setName("thread1");
thread1.start();
Thread thread2 = new MyThread();
thread2.setDaemon(true);
thread2.setName("thread2");
thread2.start();
try {Thread.sleep(3500);} catch (InterruptedException e){}
System.out.println("main Thread 종료");
}
}
동기화 방법
- 하나의 스레드가 메소드 or 블록 사용을 종료 후 잠금이 풀리면 다른 스레드가 사용할 수 있는 것
- 메소드 동기화
- 2개의 스레드가 동시에 메소드를 실행 X
- 메소드 앞에 synchronized 붙이기
class MyData {
int data = 3;
public synchronized void plusData() {
int mydata = data;
try {Thread.sleep(2000);} catch (InterruptedException e){}
data = mydata + 1;
}
}
class PlusThread extends Thread {
MyData mydata;
public PlusThread(MyData mydata) {
this.mydata = mydata;
}
@Override
public void run() {
mydata.plusData();
System.out.println(getName() + "실행결과: " + mydata.data);
}
}
public class SynchronizedMethod {
public static void main(String[] args) {
MyData myData = new MyData();
Thread plusThread1 = new PlusThread(myData);
plusThread1.setName("plusThread1");
plusThread1.start();
try {Thread.sleep(1000);} catch (InterruptedException e){}
Thread plusThread2 = new PlusThread(myData);
plusThread2.setName("plusThread2");
plusThread2.start();
}
}
- 블록 동기화
- 꼭 필요한 부분에만 적용하는 것 -> 부분만 동기화하는 방법
- 2개의 스레드가 동시에 해당 블록을 실행 X
- 일반적으로 this를 넣어 자기객체를 가리킴 -> this는 생성과정없이 바로 사용할 수 있어서
class MyData {
int data = 3;
public void plusData() {
synchronized (this) {
int mydata = data;
try {Thread.sleep(2000);} catch (InterruptedException e){}
data = mydata + 1;
}
}
}
class PlusThread extends Thread {
MyData mydata;
public PlusThread(MyData mydata) {
this.mydata = mydata;
}
@Override
public void run() {
mydata.plusData();
System.out.println(getName() + "실행결과: " + mydata.data);
}
}
public class SynchronizedBlock {
public static void main(String[] args) {
MyData myData = new MyData();
Thread plusThread1 = new PlusThread(myData);
plusThread1.setName("plusThread1");
plusThread1.start();
try {Thread.sleep(1000);} catch (InterruptedException e){}
Thread plusThread2 = new PlusThread(myData);
plusThread2.setName("plusThread2");
plusThread2.start();
}
}
동기화의 원리
- 모든 객체는 자신만의 Key를 가짐
- 블록 동기화(synchronized(this){} -> 블록이 this객체가 갖고 있는 열쇠로 잠김
- 첫 번째로 동기화 block을 실행하는 스레드가 key를 가짐 -> 해당 스레드가 블록 완료전까지 다른 스레드는 key 못 얻음
- 메소드를 동기화 하는 경우 -> this객체의 열쇠만 사용
- 하나의 객체 내부에 3개의 동기화 메소드 있다? -> 모두 this의 열쇠로 잠겨 있음 -> 1개의 스레드가 해당 메소드들 중 1개만 실행해도 나머지 모두 잠김
class MyData {
synchronized void abc() {
for (int i = 0; i < 3; i++) {
System.out.println(i + "sec");
try {Thread.sleep(1000);} catch (InterruptedException e){}
}
}
synchronized void bcd() {
for (int i = 0; i < 3; i++) {
System.out.println(i + "초");
try {Thread.sleep(1000);} catch (InterruptedException e){}
}
}
void cde() {
synchronized (this) {
for (int i = 0; i < 3; i++) {
System.out.println(i + "번쨰");
try {Thread.sleep(1000);} catch (InterruptedException e){}
}
}
}
}
public class KeyObject_1 {
public static void main(String[] args) {
MyData myData = new MyData();
new Thread() {
public void run() {
myData.abc();
}
}.start();
new Thread() {
public void run() {
myData.bcd();
}
}.start();
new Thread() {
public void run() {
myData.cde();
}
}.start();
}
}
class MyData {
synchronized void abc() {
for (int i = 0; i < 3; i++) {
System.out.println(i + "sec");
try {Thread.sleep(1000);} catch (InterruptedException e){}
}
}
synchronized void bcd() {
for (int i = 0; i < 3; i++) {
System.out.println(i + "초");
try {Thread.sleep(1000);} catch (InterruptedException e){}
}
}
void cde() {
synchronized (new Object()) {
for (int i = 0; i < 3; i++) {
System.out.println(i + "번쨰");
try {Thread.sleep(1000);} catch (InterruptedException e){}
}
}
}
}
public class KeyObject_2 {
public static void main(String[] args) {
MyData myData = new MyData();
new Thread() {
public void run() {
myData.abc();
}
}.start();
new Thread() {
public void run() {
myData.bcd();
}
}.start();
new Thread() {
public void run() {
myData.cde();
}
}.start();
}
}
쓰레드의 6가지 상태

- 스레드 상태 값 가져오기 -> Thread클래스 인스턴스 메소드
- NEW, RUNNABLE, TERMINATED
- NEW -> 객체가 처음 생성
- RUNNABLE -> start()로 실행하면(실행과 대기를 반복, CPU를 다른 스레드와 나눠 사용)
- 3가지의 일시정지 상태 -> TIMED_WAITING, BLOCKED, WAITING
- TERMINATED -> run()종료되면 이 상태
- TIMED_WAITING
- 일정시간정지
- 해당 메소드로 일시정지되는 대상 -> 해당 메소드를 호출한 스레드
- static void Thread.sleep(long millis) -> 정적 메소드
- synchronized void join(long millis) -> 인스턴스 메소드
- RUNNABLE로 전환
- 시간이 다 지남
- void interrupt()
- BLOCKED
- 동기화 메소드, 블록을 실행하기 위해 먼저 실행 중인 스레드의 실행 완료를 기다리는 것
- 실행 중인 스레드가 key반납할 떄 까지 기다리고 있는 상태
- WAITING
- 시간을 정하지 않아 무지성으로 계속 기다려야 함
- synchronized void join()
- join대상의 스레드 종료
- void interrupt()
- void wait() -> Object 클래스로부터 상속받은 메소드
- wait대상의 스레드 종료
- void notify() -> 하나만 RUNNABLE
- void notifyAll() -> 모두 RUNNABLE
- 해당 메소드는 동기화 블록 내에서만 사용 가능
NEW, RUNNABLE, TERMINATED
public class NewRunnableTerminated {
public static void main(String[] args) {
Thread.State state;
Thread myThread = new Thread() {
@Override
public void run() {
for (int i = 0; i < 1000000000L; i++) {}
}
};
state = myThread.getState();
System.out.println("myThread state = " + state);
myThread.start();
state = myThread.getState();
System.out.println("getThread state = " + state);
try {
myThread.join();
} catch (InterruptedException e) {}
state = myThread.getState();
System.out.println("getThread state = " + state);
}
}
- static void Thread.yield() -> 한 번 자기 차례 양보(자기는 실행 대기 상태)
class MyThread extends Thread {
boolean yieldFlag;
@Override
public void run() {
while(true) {
if (yieldFlag) {
Thread.yield();
} else {
System.out.println(getName() + " 실행");
for (int i = 0; i < 100000000L; i++) {}
}
}
}
}
public class YieldInRunnableState {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
thread1.setName("thread1");
thread1.yieldFlag = false;
thread1.setDaemon(true);
thread1.start();
MyThread thread2 = new MyThread();
thread2.setName("thread2");
thread2.yieldFlag = true;
thread2.setDaemon(true);
thread2.start();
for (int i = 0; i < 6; i++) {
try {Thread.sleep(1000);} catch (InterruptedException e){}
thread1.yieldFlag = !thread1.yieldFlag;
thread2.yieldFlag = !thread2.yieldFlag;
}
}
}
- 스레드의 실행이 종료? -> 스레드 객체는 남아있다
- 다시 스레드 원한다? -> 새로운 객체 만들어야함
TIMED_WAITING
- 모두 InterruptedException을 처리해줘야함 -> interrupt()를 호출하기 때문에
- Thread.sleep(long millis)를 이용한 일시정지 및 RUNNABLE 상태 전환
- 일시정지 시간 동안 CPU를 어떤 스레드가 사용하든 상관 X
- 스레드 준비 시간을 줘야함 -> start()와 interrupt()에 0.1초 지연이유
- 전달 되기전까지는 TIMED_WAITING상태이기 때문에(0.1초 지연)
- 스레드 처음 시작시 JVM이 CPU를 독립적으로 사용해서 메모리 할당과 같은 시간이 필요
- 모든 준비 끝나야 run() 실행
- 만약 준비 안 끝나고 start() 실행? -> RUNNABLE 가능성 ㅇㅇ
class MyThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println(" -- sleep() 진행 중 interrupt() 발생");
for (long i = 0; i < 1000000000L; i++) {}
}
}
}
public class TimedWaiting_Sleep {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
try {Thread.sleep(100);} catch (InterruptedException e){}
System.out.println("MyThread State = " + myThread.getState());
myThread.interrupt();
try {Thread.sleep(100);} catch (InterruptedException e){}
System.out.println("MyThread State = " + myThread.getState());
}
}
- join(long millis)를 이용한 일시정지 및 RUNNABLE 상태전환
- 특정 스레드 객체에게 일정 시간 동안 CPU를 할당하라는 의미
- 특정 스레드가 작업이 일정 시간보다 빨리 끝나면 정지한 스레드는 RUNNABLE 상태로 변경
- 다른 스레드의 결과가 먼저 나와야 이후의 작업을 진행할 때 사용
class MyThread1 extends Thread {
@Override
public void run() {
for (long i = 0; i < 1000000000L; i++) {}
}
}
class MyThread2 extends Thread {
MyThread1 myThread1;
public MyThread2(MyThread1 myThread1) {
this.myThread1 = myThread1;
}
@Override
public void run() {
try {
myThread1.join(3000);
} catch (InterruptedException e) {
System.out.println(" -- join(...) 진행 중 interrupt() 발생");
for (long i = 0; i < 1000000000L; i++) {}
}
}
}
public class TimedWaiting_Join {
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1();
MyThread2 myThread2 = new MyThread2(myThread1);
myThread1.start();
myThread2.start();
try {Thread.sleep(100);} catch (InterruptedException e){}
System.out.println("MyThread1 State = " + myThread1.getState());
System.out.println("MyThread2 State = " + myThread2.getState());
myThread2.interrupt();
try {Thread.sleep(100);} catch (InterruptedException e){}
System.out.println("MyThread1 State = " + myThread1.getState());
System.out.println("MyThread2 State = " + myThread2.getState());
}
}
BLOCKED
- 먼저 도착하는 스레드가 실행 권한을 가짐
- key가 반납되는 순간 BLOCKED 상태의 모든 스레드가 다시 경쟁해 열쇠를 먼저 차지하느 스레드가 실행
- 실행 순서와 상관없음
class MyBlockTest {
MyClass mc = new MyClass();
Thread t1 = new Thread("thread1") {
public void run() {
mc.syncMethod();
}
};
Thread t2 = new Thread("thread2") {
public void run() {
mc.syncMethod();
}
};
Thread t3 = new Thread("thread3") {
public void run() {
mc.syncMethod();
}
};
void startAll() {
t1.start();
t2.start();
t3.start();
}
class MyClass {
synchronized void syncMethod() {
try {Thread.sleep(100);} catch (InterruptedException e){}
System.out.println("====" + Thread.currentThread().getName() + "====");
System.out.println("thread1 -> " + t1.getState());
System.out.println("thread2 -> " + t2.getState());
System.out.println("thread3 -> " + t3.getState());
for (long i = 0; i < 1000000000L; i++) {}
}
}
}
public class BlockedState {
public static void main(String[] args) {
MyBlockTest myBlockTest = new MyBlockTest();
myBlockTest.startAll();
}
}
WAITING
- wait()주의점
- 다른 스레드에서 notify() or notifyAll()을 호출해야 RUNNABLE 상태가 됨
- 다른 스레드에서 notify() or notifyAll() 되면, 일시정지 됐던 지점인 wait()의 다음 줄부터 실행
- 즉, 처음부터 다시 실행되는 것이 아님 -> wait() 위치 신경 쓸 것
- 스스로 RUNNABLE상태 X
- wait(), notify(), notifyAll() -> 동기화 블록에서만 사용가능
- 언제 wait() notify() 사용? -> 아래 코드의 예시를 보자
class DataBox {
int data;
synchronized void inputData(int data) {
this.data = data;
System.out.println("입력 데이터: " + data);
}
synchronized void outputData() {
System.out.println("출력 데이터: " + data);
}
}
public class Waiting_WaitNotify_1 {
public static void main(String[] args) {
DataBox dataBox = new DataBox();
Thread t1 = new Thread() {
public void run() {
for (int i = 1; i < 9; i++) {
dataBox.inputData(i);
}
}
};
Thread t2 = new Thread() {
public void run() {
for (int i = 1; i < 9; i++) {
dataBox.outputData();
}
}
};
t1.start();
t2.start();
}
}
class DataBox {
boolean isEmpty = true;
int data;
synchronized void inputData(int data) {
if (!isEmpty) {
try {wait();} catch (InterruptedException e){}
}
this.data = data;
isEmpty = false;
System.out.println("입력 데이터: " + data);
notify();
}
synchronized void outputData() {
if (isEmpty) {
try {wait();} catch (InterruptedException e){}
}
isEmpty = true;
System.out.println("출력 데이터: " + data);
notify();
}
}
public class Waiting_WaitNotify_2 {
public static void main(String[] args) {
DataBox dataBox = new DataBox();
Thread t1 = new Thread() {
public void run() {
for (int i = 1; i < 9; i++) {
dataBox.inputData(i);
}
}
};
Thread t2 = new Thread() {
public void run() {
for (int i = 1; i < 9; i++) {
dataBox.outputData();
}
}
};
t1.start();
t2.start();
}
}
참조
https://jongwoon.tistory.com/14