코드 그라데이션

Day30-31. Thread(쓰레드) (3) Synchronized 본문

Java/Mega

Day30-31. Thread(쓰레드) (3) Synchronized

완벽한 장면 2023. 4. 26. 13:22

먼저

쓰레드 사용법

1. class 사용

-> 쓰레드를 사용하는 클래스에 Thread를 상속한다.

class A extends Thread{
     public void run(){

     }
}

=> 객체 만들고 start() 하면 됩니다.

A a = new A();

a.start(); -> run 메드 실행

 

2. interface 구현

-> 쓰레드 사용을 위해 Runnable 이라는 인터페이스를 구현한다.

class B implements Runnable{
     public void run(){

     }
}

=> Thread를 따로 만들어서 start() 해야 합니다.

B b = new B();

(b.start(); => 안 됨!)

Thread temp = new Thread(b); // 그래서 쓰레드를 따로 만들어줘야 함.

temp.start();

 


쓰레드 동기화

- 여러개의 쓰레드를 동작시킬 때면, 이것의 동작을 제어도 해줘야 하고, 접근하게 만들려면 동기화를 시켜줘야 한다. 

이런 상황일 때.

 

이 때 나오는 개념이 

임계영역

다수의 쓰레드가 하나의 쓰레드에 접근하려고 할 때, 그 영역

==> 작업 순간마다 한 개의 쓰레드만 사용할 수 있게 하는 영역

 

아까 말했던 예시처럼.

식당에서 요리를 하고 나서 서빙을 하는 것처럼.

요리 할 때는 서빙하는 사람이 방해를 하면 안 되죠.

 

이걸 만들 수 있는 메서드 키워드가

synchronized

=> 이게 없으면 난리 나버립니다.

 

상황 예시

 

int a = 5; 라고 할 때

- 증가 쓰레드가 먼저 동작에 들어가서 5를 가져와서 6으로 증가를 시킴.

- 안정적으로 6이 되어야 하는데 / 그 순간에 synchronized를 하지 않으면 감소 쓰레드가 들어와서 도로 5를 만들어버린다.

(원래 의도는 5 -> 6(먼저 증가쓰레드) -> 5(다시 감소쓰레드, 차례대로 동작하게 시키고 싶었음) 

- 그런데 감소 쓰레드가 가져올 때 순간 타이밍에 a가 5였다면, 아예 4를 만들어버리는 경우도 빈번하게 발생할 수 있음.

* 임계영역이 작동하면, 감소 쓰레드가 접근을 못 하고 있다가, 

  증가 쓰레드가 다 끝나고 6이라는 결과를 만들어 내고 나면, 감소쓰레드가 접근해서 그 6을 다시 5로 만든다.

 

ex. 공중화장실=> 한 번에 한 명만 문 잠그고 들어가서 사용. 만약 문 부수고 들어가면? 범죄자!

=> 데이터의 유효성 보장!!!!

 

하지만

주의사항

=> 한 쓰레드만 들어가는 것을 보장하는 것이지, 순서를 보장하는 건 아니다!!!


 

예시코드

package Day30;

class A {
	synchronized void plus(int i) throws InterruptedException {
		// 나에게 접근하고 싶으면 한 번에 하나씩 들어와!
		for (int j = 0; j<5; j++) {
			System.out.println(j*i);
			Thread.sleep(800); 
			// 얘 명령이 0.8초를 자기 때문에 원래는 다른 객체가 쏜쌀같이 들어오는데, 
			// 이걸 싱크로나이즈드 붙여서 막는다.
		}
	}
}

class B extends Thread {
	A a; // has 관계
	int i;
	
	B(A a, int i) {
		this.a = a;
		this.i = i;
	}
	
	public void run() {
		try {
			a.plus(i);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

public class EXThread2 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		// 객체는 하나로 돌려막기 해야함. // 객체를 동시에 똑같은 것 쓰게 만들어야.
		A a1 = new A();
		B b1 = new B(a1, 3); // 3이 먼저 시작한 건 맞으나, 꼭 3이 먼저 들어가는 건 아니다.
		B b2 = new B(a1, 7); // 만약에 CPU가 3을 뒤로 미뤄놨다면, 7이 들어갈 수도 있음(찰나의 상황)
		
		b1.start();
		b2.start();
	}

}

실행결과

0
3
6
9
12
0
7
14
21
28

// 차례대로 들어온다!

만약 synchronized가 없다면?

package Day30;

class A {
	void plus(int i) throws InterruptedException {
		// 키워드 없음
		for (int j = 0; j<5; j++) {
			System.out.println(j*i);
			Thread.sleep(800); 
		}
	}
}

class B extends Thread {
	A a; // has 관계
	int i;
	
	B(A a, int i) {
		this.a = a;
		this.i = i;
	}
	
	public void run() {
		try {
			a.plus(i);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

public class EXThread2 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		// 객체는 하나로 돌려막기 해야함. // 객체를 동시에 똑같은 것 쓰게 만들어야.
		A a1 = new A();
		B b1 = new B(a1, 3); 
		B b2 = new B(a1, 7); 
		
		b1.start();
		b2.start();
	}

}

실행 결과 => 순서 중구난방

0
0
3
7
6
14
9
21
28
12

 

728x90
Comments