class Well { private String name; private int water; public Well(String name, int initial) { this.name = name; this.water = initial; } public void draw(int waterL) { water = water - waterL; } public void pour(int waterL) { water = water + waterL; } public int getWater() { return water; } } class Person { private String name; private int water = 10; private Well well; public Person(String name, Well well) { this.name = name; this.well = well; } public void drawFromWell(int waterL) { well.draw(waterL); water = water + waterL; } public void pourIntoWell(int waterL) { well.pour(waterL); water = water - waterL; } } class ActionThread extends Thread { Person person; public ActionThread(Person person) { this.person = person; } public void run() { for (int i = 0; i < 1000; i++) { person.pourIntoWell(1); } } } class ActionThread2 extends Thread { Person person; public ActionThread2(Person person) { this.person = person; } public void run() { for (int i = 0; i < 1000; i++) { person.pourIntoWell(1); } } } public class Main { public static void main(String[] args) { Well well = new Well("There is a well", 100); Person p1 = new Person("Person A", well); Person p2 = new Person("Person B", well); ActionThread a = new ActionThread(p1); ActionThread2 b = new ActionThread2(p2); b.start(); a.start(); try { a.join(); b.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(well.getWater()); } }
마을에 우물이 하나 있어요.
그 우물을 사용하는 사람은 2명이 있지요.
자, 이제 Thread를 만들어서 한명이 물을 1씩 1000번 부을 거예요.
동시에 땅~! 하고 2명이 물을 붓기 시작했어요.
2명이 쉬지 않고 정신없이 물을 붓고 나서
이제 우물에 얼마나 물이 들어있는지 확인해 봤어요.
분명히, 2000 이상은 있겠지요 ^^
왜냐면 각 사람당 1씩 1000번 부었으니까요.
그런데, 확인해 보니, 1920, 1896,... 등등 정확히 초기에 있었던 물 100 + 2000 = 2100
이 있어야 되는데, 그 값이 안 나오고 매번 실행 때 마다 다른 값이 나와요 ㅠㅠ
이건 쓰레드가 context switching 하면서 발생하는 부산물이라고 하네요.
솔루션1.
물을 부을 때에는 한 명씩 부을 수 있게. 다른 한명이 끝나야 다른 사람이
부을 수 있게 했어요.
public synchronized void pour(int waterL) { water = water + waterL; }
이렇게 하니 2100이란 값이 정확히 나옵니다.
멤버 함수에다 synchronized 를 걸면 여러 쓰레드에서 동시에 그 객체의 함수를 수행할 수가 없지요.
위의 코드의 예제처럼, 여러 개의 Action Thread에서 한 개의 Well의 오브젝트에 대해 작업을 할때에 유효하죠.
여기서 조금 더 들어가서, Well의 오브젝트가 여러 개 있다면, 그 모든 오브젝트에 대해서까지 동기화가 이뤄질까요?
지금 문제에서는 불필요한 동기화에 해당합니다.
정답은 해당 오브젝트에 대해서만 동기화한다는 것입니다.
객체가 다르면 메모리 상에 별개로 존재합니다.
메모리 상의 다른 공간에 존재하는 멤버 변수를 일괄적으로 동기화할 필요가 없지요.
static변수라면 모를까요.
즉 같은 클래스의 모든 오브젝트에 대해 동기화가 아니라 각 오브젝트별 동기화입니다.
위 코드는 다음과 동일하게 동작합니다.
public void pour(int waterL) { synchronized(this) { water = water + waterL; } }
this 가 의미하는 것이 현재 객체이므로, 그 객체에 대해서 블록 안의 코드가 동시에 수행이 되지 않게 됩니다.
솔루션2.
private Object waterLock = new Object(); public void pour(int waterL) { synchronized(waterLock) { water = water + waterL; } }
멤버 변수에 대해 락을 걸어도 원하는 효과를 거둘 수 있습니다.
필요에 따라 여러 개의 락을 둬서 서로 상호 배제되어야 하는 코드들끼리
락을 걸면 더 좋을 것입니다.
위의 코드에서 waterLock을 static으로 해버리면
모든 오브젝트에 대한 불필요한 동기화가 걸려 버립니다.
댓글 없음:
댓글 쓰기