Thread3

제이·2023년 3월 15일
post-thumbnail

Thread는 어떻게 돌아가는가.

class Some {
	public synchronized void todo1() {
		try {
			//this의 lock을 쥔 채로 들어온다.
			System.out.println("inside todo1");
			//5초동안 잠듦
			Thread.sleep(5000);
			🔥System.out.println("t2 state :" + ThreadEx.t2.getState());	
			System.out.println("done : todo1");
          // 이 메소드가 끝나는 시점(lock을 들고 나올 때)에 t2는 따라 나온다.
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	//여기 못들어온다.
	public synchronized void todo2() {
		try {
			System.out.println("inside todo2");
			Thread.sleep(5000);
			System.out.println("t1 state :" + ThreadEx.t1.getState());
			System.out.println("done : todo2");
		}catch (Exception e) {
			e.printStackTrace();
		}
	}
}
public class ThreadEx {
	public static Thread t1;	
    //main이 static이기 때문에 불려쓰려고 static을 붙여줌.
	public static Thread t2;
	public static void main(String[] args) {
		final Some s = new Some();
		t1 = new Thread() {
			@Override
			public void run() {
				s.todo1();
			}
		};
		t2 = new Thread() {
			@Override
			public void run() {
				s.todo2();
			}
		};
		// main 순서대로
		t1.start();
		t2.start();
		//main, t1, t2 경쟁
	}
}
//결과
/*inside todo1
t2 state :BLOCKED	//슬립하고 나서 
done : todo1
inside todo2
t1 state :TERMINATED
done : todo2
프로그램 종료
 */

🔥t1이 lock을 들고 들어가서 t2는 blocked t2가 t1을 따라가는 이유는 run에 계속 나두면 일도 못하고 하기 때문에 그 다음 다른 스레드가 일할 수 있겠끔 하기 위해서.
그래서 t2를 blocked 상태로 만들어 버린다.

class Class

class Class{}에서는 멤버변수, 개수, 타입, 이름, 접근제한자, 생성자수, 파라미터, 메서드수 ,파라미터, 리턴 을 가지고 있다.
클래스타입이 있어야 클래스객체를 만들 수 있다.

Syncronized Block(싱크로나이즈드 블록)

//2
class Some1 {
	private Object key = new Object();
	public void methodA() {
		Thread temp = Thread.currentThread();
    //⬆️싱크로나이즈드 블록 밖에 빠져있음. 
    `
	⭐⭐⭐⭐synchronized(this) {
       //Some객체 lock가지고 검사하겠다. - this => Some1 객체
       `
			for(int i =0 ; i<100; i++ ) {
				try {
					System.out.println(
                    temp.getName() + "-> doing methodA"
                    );
					Thread.sleep(100);
				}catch(Exception e) {
				}
			}
		}
	}
	public void methodB() {
		Thread temp = Thread.currentThread();
        `
	⭐⭐⭐⭐synchronized (Some.class) {	
    //static메소드가 할 때 쓰는 거. 
    //Some클래스의 형체를 가지고 있는 Class 객체
			`
			for(int i =0; i<100 ; i++) {
				try {
					System.out.println(
                    temp.getName() + "-> doing methodB"
                    );
					Thread.sleep(50);
				}catch(Exception e) {
				}
			}
		}
	}
}
public class ThreadTest {	
	public static void main(String[] args) {
		final Some1 s = new Some1();
		// 지역변수를 이너클래스 안에 가져가서 쓸 때, final 달고 써야함
		// 멤버변수는 상관없다.
		Thread t1 = new Thread() {
			@Override
			public void run() {
				s.methodA();
			}
		};
		Thread t2 = new Thread() {
			@Override
			public void run() {
				s.methodB();
			}
		};
		t1.start();
		t2.start();
	}
}
/*결과
 * 
 * Thread-0-> doing methodA
Thread-1-> doing methodB
Thread-0-> doing methodA
Thread-1-> doing methodB
Thread-1-> doing methodB
Thread-0-> doing methodA
Thread-0-> doing methodA
Thread-1-> doing methodB
Thread-1-> doing methodB
Thread-0-> doing methodA
Thread-0-> doing methodA
Thread-1-> doing methodB
Thread-0-> doing methodA
Thread-1-> doing methodB
Thread-1-> doing methodB
Thread-0-> doing methodA
Thread-0-> doing methodA
Thread-1-> doing methodB ....

싱크로나이즈드 블록

synchronized( 객체 ) : 객체의 lock을 쓰겠다는 의미 - > 객체에는 아무런 영향 x, 쓰레드들에게만 영향

  1. 병목구간을 최소화할 수 있다. 구간 설정가능.
  2. lock검사 대상을 설정할 수 있다.(서로 다른 멤버변수에 대한 연산.)
  • lock을 검사하는 대상이 다르다. 계속 왔다갔다 한다.
    A가 잠들어 있는 시간동안 B가 계속 일한다 - 이 경우는 싱크로나이즈드가 한개는 되어있고 한개는 되어있지 않아서.

  • 메소드 안에 동기화블록을 만들 수 있다.

  • 블록은 내가 어느 구간까지 동기화할 것인가를 정할 수 있다. 병목구간으로 인한 피해를 좀 줄일 수 있다. 위의 코드에서도 블록위에 따로 Thread를 꺼내놓았다.(⬆️이부분)

synchronized method와 block

//3-1 - 둘 다 synchronized 메서드로 사용
class Human {
	private String name;
	private int age;
	public synchronized void setName(String name) {//name을 바꾸는 메소드
		try {
			this.name = name;
			System.out.println("name changed");
			Thread.sleep(3000);
		}catch(Exception e) {
		}
	}
	public synchronized void setAge(int age) {//age를 바꾸는 메소드
		try {
			this.age = age;
			System.out.println("age changed");
			Thread.sleep(3000);
		}catch(Exception e) {}
	}
}
public class SynchronizedMethodCase {
	public static void main(String[] args) {
		final Human h = new Human();
		Thread t1 = new Thread() {
			@Override
			public void run() {
				h.setName("춘식");
			}
		};
		t1.setName("first");
		Thread t2 = new Thread() {
			@Override
			public void run() {
				h.setAge(10);
			}
		};
		t2.setName("second");
		t1.start();
		t2.start();
	}
}
-------------------------------------------------------------
//3-2 - 하나는 blocked하고 하나는 메소드로 사용.
class KeyA {
}
class KeyB {
}
class Human1 {
	private String name;
	private int age;
	private KeyA keyA = new KeyA();
	private KeyB keyB = new KeyB();
	`
	public void setName(String name) {
		synchronized (keyA) {	//A로 lock검사하게끔.
			try {
				this.name = name;
				System.out.println("named changed");
				Thread.sleep(3000);
			}catch(Exception e) {}
		}
	}
	public void setAge(int age) {
		synchronized (keyB) {	//B로 lock검사하게끔.
			try {
				this.age = age;
				System.out.println("age changed");
				Thread.sleep(3000);
			}catch(Exception e) {}
		}
	}
}
public class SynchronizedMethodCase2 {
	public static void main(String[] args) {
		final Human1 h = new Human1();
		Thread t1 = new Thread() {
			@Override
			public void run() {
				h.setName("춘식");
			}
		};
		t1.setName("first");
		`
		Thread t2 = new Thread() {
			@Override
			public void run() {
				h.setAge(10);
			}
		};
		t2.setName("second");
		`
		t1.start();
		t2.start();
	}
}

위의 코드 설명

3-1과 3-2를 비교해서 보면,
3-1의 코드는 두개의 메서드를 synchronized를 하였다.
3-2의 코드는 한개는 blocked하고 하나는 synchronized메서드를 하였다.

3-2처럼 코드를 짠다면, 각각 다르게 작동시킬 수 있다.
그러나 3-2 처럼 코드를 짜는 것은 단순하게 멤버변수 2개라서 하는 게 아니다. 서로 연관이 없는지 있는지에 대해서 고민하고 사용해야한다.

💡권장사항 - 성능은 좀 늦어질 지 몰라도 그냥 앞의 3-1에 더 안전하다.

싱크로나이즈드 블록과 싱크로나이즈드 메서드의 차이점

  • 싱크로나이즈드메소드는 락주체 바꿀 수 있다.
    blocked은 락주체 바꿀수 있다.

  • 블록은 임의로 정할 수 있다 (직접 설정 가능), 병목구간을 최소화 할 수 있다. method보다 더 작은 범위지정 가능, 꼭 필요한 부분만 동기화 구간 지정가능

  • 메서드로만 구성되어있으면 lock의 대상이 this밖에 안된다.
    메서드의 경우는 범위를 지정할 수가 없음

wait(), notifyAll(), 생산사와 소비자문제

//4-1 : synchronized method 안 했을 때와 했을 때
class Producer extends Thread {//생산자
	private MyBox box;
	public Producer(MyBox box) {
		this.box = box;
	}
	public void run() {
		for(int i =0 ; i <20 ; i++) {
			box.put(i);
			try {
				sleep(100);
			}catch(InterruptedException e) {}
		}
	}
}
class Consumer extends Thread {//소비자
	private MyBox box;
	public Consumer(MyBox c ) {
		box =c;
	}
	public void run() {
		int value = 0;
		for(int i =0; i<10 ; i++) {
			box.get();
			try {
				sleep(100);
			}catch(InterruptedException e) {}
		}
	}
}
class MyBox {
	private int contents;
	private boolean isEmpty = true;
    `
	public synchronized void get() {	
    //비워있지 않으면 false를 true로 바꾸라
		if(!isEmpty) {
			isEmpty = !isEmpty;
			System.out.println(
            Thread.currentThread().getName() + ": 소비" + contents
            );
		}
	}
	public synchronized void put(int value) {
    //비었을 때만 true;
		if(isEmpty) {	
			contents = value;
			System.out.println(
            Thread.currentThread().getName() + ": 생산" + value
            );
			isEmpty = !isEmpty;
		}
	}
}
public class ProducerConsumer {
	public static void main(String[] args) {
		MyBox c = new MyBox();
		Producer p1 =  new Producer(c);
		Consumer c1 = new Consumer(c);
		Consumer c2 = new Consumer(c);
		p1.start();
		c1.start();
		c2.start();
	}
}
//싱크로 안했을 때 결과
/*
Thread-0: 생산0
Thread-0: 생산1
Thread-1: 소비1
Thread-2: 소비1
Thread-0: 생산2
Thread-2: 소비3
Thread-0: 생산3
Thread-0: 생산4
Thread-2: 소비4
Thread-2: 소비4
Thread-0: 생산5
Thread-1: 소비5
Thread-2: 소비5
Thread-0: 생산7
Thread-2: 소비7
Thread-0: 생산8
Thread-1: 소비8
Thread-0: 생산9
*/
//싱크로 한 결과
/*
Thread-0: 생산0
Thread-2: 소비0
Thread-0: 생산1
Thread-2: 소비1
Thread-0: 생산2
Thread-2: 소비2
Thread-0: 생산3
Thread-1: 소비3
Thread-0: 생산4
Thread-1: 소비4
Thread-0: 생산5
Thread-2: 소비5
Thread-0: 생산6
Thread-1: 소비6
Thread-0: 생산7
Thread-1: 소비7
Thread-0: 생산8
Thread-1: 소비8
Thread-0: 생산9
Thread-2: 소비9
Thread-0: 생산10*/
-------------------------------------------------------------
//4-2
class Producer extends Thread {
	private MyBox box;
	public Producer(MyBox box) {
		this.box = box;
	}
	public void run() {
		for(int i=0; i<20; i++) {
			box.put(i);
			try {
				sleep(100);
			} catch (InterruptedException e) {
			}
		}
	}
}
class Consumer extends Thread {
	private MyBox box;
	public Consumer(MyBox c) {
		box = c;
	}
	public void run() {
		int value = 0;
		for(int i=0; i<10; i++) {
			box.get();
			try {
				sleep(100);
			} catch (InterruptedException e) {
			}
		}
	}
}
class MyBox {
	private int contents;
	private boolean isEmpty = true;
	💕public synchronized int get() {
		while(isEmpty) {
			try {wait();
				//락을 가진 상태로만 호출할 수 있음 . 
                //여기서는 this. 앞에 아무것도 없으니까.
			} catch(InterruptedException e) {}
		}
		isEmpty = !isEmpty;notifyAll();
		System.out.println(Thread.currentThread().getName() + ": 소비" + contents);
		return contents;
	}
	💕public synchronized void put(int value) {
		while(!isEmpty) {
			try {wait();
			} catch(InterruptedException e) {}
		}
		contents = value;
		System.out.println(Thread.currentThread().getName() + " : 생산" + value);
		isEmpty = !isEmpty;notifyAll();
	}
}
public class ProducerConsumer {
	public static void main(String[] args) {
		MyBox c = new MyBox();
		Producer p1 = new Producer(c);
		Consumer c1 = new Consumer(c);
		Consumer c2 = new Consumer(c);
		p1.start();
		c1.start();
		c2.start();
	}
}
/*
 * 결과
 * Thread-0 : 생산0
Thread-2: 소비0
Thread-0 : 생산1
Thread-1: 소비1
Thread-0 : 생산2
Thread-1: 소비2
Thread-0 : 생산3
Thread-1: 소비3
Thread-0 : 생산4
Thread-2: 소비4
Thread-0 : 생산5
Thread-1: 소비5
Thread-0 : 생산6
Thread-1: 소비6
Thread-0 : 생산7
Thread-1: 소비7
Thread-0 : 생산8
Thread-1: 소비8
Thread-0 : 생산9
Thread-1: 소비9
Thread-0 : 생산10
Thread-1: 소비10
Thread-0 : 생산11
Thread-2: 소비11
Thread-0 : 생산12
Thread-2: 소비12
Thread-0 : 생산13
Thread-1: 소비13
Thread-0 : 생산14
Thread-2: 소비14
Thread-0 : 생산15
Thread-2: 소비15
Thread-0 : 생산16
Thread-2: 소비16
Thread-0 : 생산17
Thread-2: 소비17
Thread-0 : 생산18
Thread-2: 소비18
Thread-0 : 생산19
Thread-2: 소비19
*/

wait(), notifyAll()

  • wait()와 notifyAll()은 현재객체의 락을 소유한 애들만 사용할 수 있다.
    notifyAll()의 세가지 조건.
  • wait()
    : object에 있음. 어떤 객체든지 다 할 수 있다.
    : final붙음(오버라이드가 안된다. 못바꾼다.)
    : 오브젝트의 lock를 취득한상태에서만 호출할 수 있다
    . . - 싱크로했을 때가 lock취득한 상태.
    : 어기게 되면, illgalmonitorStateException이 발생.
    다른 스레드가 notifyAll()을 부를 때까지 갇혀있음.
    : this의 락을 들고 들어 왔는데,
    💡wait()를 만나면 lock을 풀고 notrunnable상태가 된다.
    💡notify()로 깨워줄 때까지 notrunnable 상태로 있다.
    : lock풀고 가는 거 - wait()
  • notifyAll()
    : object에 있음. 어떤 객체든지 다 할 수 있다.
    : notifyAll()은 a라고 정했으면, a와 관련된 애들을 전부 다 깨운다.
    `
    t1이 : a.wait()를 만났다. 실행불가로 빠진다.
    t2 : a.wait() 되면 실행불가상태로 된다(a는 this이다).
    t3 : b.wait()가 된다면,
  • 안되는 예
    synchronized(b){
    a.wait();
    }
  • 되는 예
    synchronized(a){
    a.wait();
    }
  • notify()는 잘 안쓰인다.
    락을 쥔 채로 어떤 스레드가 웨이팅되었을 때, 락의 주체가 동일한 한놈만 깨운다.
    (a.1번과 a.2번이 있다면 어떤 놈을 깨울 지 모른다. 아무나 깨운다.)
    이렇게 되면, 영원히 못깨어나는 스레드가 생길 수 있어서 잘 안쓴다.
    선택을 평생 못받으니까 기아상태가 된다.

🍀정리

❣ 객체가 lock을 얻는 세가지 방법
1.그냥 일반 싱크로나이즈드 메소드에 진입 - lock의 주체 : this.
2.싱크로 블록에 진입했을 때 - lock의 주체 : 내가 설정할 수 있다.
3.static 싱크로나이즈드 메소드 - lock의 주체 : 거기 속해 있는 클래스의 클래스 객체.
lock얻었을 때만 불러올 수 있다. 그거 안하고 하면은 예외 발생한다.

wait()되면, 이렇게 됐다는 얘기는 lock을 가지고 있었다는 것.
-lock 풀고 실행불가능상태로.
notifyAll()이 호출되면 대기열로 돌아온다.

3.15일 수업

profile
Hello :)

0개의 댓글