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{}에서는 멤버변수, 개수, 타입, 이름, 접근제한자, 생성자수, 파라미터, 메서드수 ,파라미터, 리턴 을 가지고 있다.
클래스타입이 있어야 클래스객체를 만들 수 있다.
//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, 쓰레드들에게만 영향
lock을 검사하는 대상이 다르다. 계속 왔다갔다 한다.
A가 잠들어 있는 시간동안 B가 계속 일한다 - 이 경우는 싱크로나이즈드가 한개는 되어있고 한개는 되어있지 않아서.
메소드 안에 동기화블록을 만들 수 있다.
블록은 내가 어느 구간까지 동기화할 것인가를 정할 수 있다. 병목구간으로 인한 피해를 좀 줄일 수 있다. 위의 코드에서도 블록위에 따로 Thread를 꺼내놓았다.(⬆️이부분)
//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밖에 안된다.
메서드의 경우는 범위를 지정할 수가 없음
//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()
: 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();
}
🍀정리
❣ 객체가 lock을 얻는 세가지 방법
1.그냥 일반 싱크로나이즈드 메소드에 진입 - lock의 주체 : this.
2.싱크로 블록에 진입했을 때 - lock의 주체 : 내가 설정할 수 있다.
3.static 싱크로나이즈드 메소드 - lock의 주체 : 거기 속해 있는 클래스의 클래스 객체.
lock얻었을 때만 불러올 수 있다. 그거 안하고 하면은 예외 발생한다.
❣ wait()되면, 이렇게 됐다는 얘기는 lock을 가지고 있었다는 것.
-lock 풀고 실행불가능상태로.
❣ notifyAll()이 호출되면 대기열로 돌아온다.
3.15일 수업