synchronized 는 단 하나의 스레드만 실행할 수 있는 메소드 또는 블록이다. 다른 스레드는 메소드나 블록이 실행이 끝날 때까지 대기해야한다.
동기화 메소드
public synchronized void methid(){
임계 영역; //단 하나의 스레드만 실행
}
동기화 블록
public void method() {
//여러 스레드가 실행 가능한 영역
...
synchronized(공유객체) {
임계 영역 //단 하나의 스레드만 실행
}
//여러 스레드가 실행 가능한 영역
...
}
아래의 예제를 통해 synchronized를 자세히 살펴보았다.
public class ThreadWorkTest1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Work work = new Work();
Worker worker1 = new Worker(work, 10, "1번 일꾼");
Worker worker2 = new Worker(work, 10, "2번 일꾼");
worker1.start();
worker2.start();
}
}
class Work{
int workCnt; //일거리 개수
public Work() {
workCnt = 0;
}
public void addCnt() {
workCnt++;
}
public String toString() {
return String.valueOf(workCnt);
}
}
>>> 실행할 ThreadWorkTest 클래스와 Work 클래스 생성
public class Worker extends Thread{
Work work; //일
int workLoad; //얼마나 일해라
public Worker(Work work, int workLoad, String name) {
super(name); //부모클래스로 값을 넘김
this.work = work;
this.workLoad = workLoad;
}
//Thread로 수행되는 메소드
public void run() {
for(int i=1; i<=workLoad; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//스레드가 동시에 접근하지 못하고 하나씩 접근하도록 보장
synchronized (work) {
work.addCnt();
}
System.out.println(Thread.currentThread().getName()
+ " -> " + work.toString());
}
}
}
>>> Thread를 상속하는 클래스 Worker를 생성
▼ 실행 결과
>>> 충돌 없이 일꾼이 10번씩 두 일꾼이 총 20번을 일한 결과를 확인할 수 있음
스레드간 협업 메소드는 동기화 메소드 또는 블록에서만 호출 가능한 Object의 메소드이다. 두 개의 스레드가 교대로 번갈아 가며 실행해야 할 경우 주로 사용한다.
public class ProducerConsumerTest {
public static void main(String[] args) {
//생산자 -> 생산 소비 <- 소비자
CubbyHole cubbyHole = new CubbyHole();
Producer p1 = new Producer(cubbyHole, 1);
Consumer c1 = new Consumer(cubbyHole, 1);
p1.start();
c1.start();
}
}
>>> 실행할 ProducerConsumerTest 클래스
class Consumer extends Thread{
CubbyHole cubbyHole;
int number;
public Consumer(CubbyHole c, int n) {
cubbyHole = c;
number = n;
}
public void run() {
for(int i=1; i<=10; i++) {
int value = cubbyHole.get();
System.out.println("Consumer " + number + " -> get -> " +value);
}
}
}
>>> Thread를 상속한 Consumer 클래스
class Producer extends Thread{
CubbyHole cubbyHole;
int number;
public Producer(CubbyHole c, int n) {
cubbyHole = c;
number = n;
}
public void run() {
for(int i=1; i<=10; i++) {
cubbyHole.put(i);
System.out.println("Producer " + number + " -> put -> " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
>>> Thread를 상속한 Producer 클래스
class CubbyHole{
int contents;
boolean flag;
public CubbyHole() {
contents = 0;
flag = false;
}
public synchronized int get() {
while(flag == false) {
try {
wait(); //쓰레드 대기
} catch (InterruptedException e) {
e.printStackTrace();
}
}
flag = false;
notifyAll(); //쓰레드 깨워주기
return contents;
}
public synchronized void put(int value) {
while(flag == true) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
contents = value;
flag = true;
notifyAll(); //쓰레드 깨워주기
}
}
>>> Consumer와 Producer에서 생산과 소비를 함께 사용할 공용 공간인 CubbyHole 클래스
해석 : Procuder 클래스에서 put한 후 Consumer 클래스에서 get할 수 있는 코드
>>> wait()과 notifyAll() 메소드를 통해 스레드를 대기하고 깨워주기를 반복
문제사항 : 처음 put()과 get() 메소드에 synchronized를 지정해주지 않으니 아래의 오류가 발생
>>> 이를 잡아주기 위해 두 클래스에서 번갈아가며 사용할 수 있게 put()과 get() 메소드를 동기화 메소드 synchronized로 작성
▼ 실행 결과