동기화란 하나의 작업이 완전히 완료된 후 다른 작업을 수행하는 것이다.
멀티쓰레드를 사용할 땐 동기화가 필요하다.
동기화를 사용하지 않았을 때 문제 발생
// 공유 객체
class MyData{
int data = 3;
public void plusData(){
int mydata=data; // 데이터 가져오기
try {Thread.sleep(2000);} catch(InterruptedException e){}
data=mydata+1; // 2초 후에 값을 1만큼 증가
}
}
// 공유 객체를 사용하는 쓰레드
class PlusThread extends Thread{
MyData myData;
public PlusThread(MyData myData){ // 생성자의 매개변수로 MyData 객체를 입력받음
this.myData=myData;
}
@Override
public void run(){
myData.plusData(); // 객체 내부의 plusData() 메서드 호출
System.out.println(getName()+"실행 결과: "+myData.data);
}
}
public class Main{
public static void main(String[] args) {
// 공유 객체 생성
MyData myData = new MyData();
// plusThread 1
Thread plusThread1 = new PlusThread(myData);
plusThread1.setName("plusThread1");
plusThread1.start();
try{Thread.sleep(1000);} catch(InterruptedException e) {} // 1초 기다림
// plusThread2
Thread plusThread2 = new PlusThread(myData);
plusThread2.setName("plusThread2");
plusThread2.start();
}
}
plusThread1실행 결과: 4
plusThread2실행 결과: 4
두개의 쓰레드가 각각 MyData 객체의 data 필드 값을 1씩 증가시켰는데도 두 쓰레드 모두 4의 결괏값을 가진다.
이유는 두 번째 쓰레드가 data 필드를 증가시키는 시점에 아직 첫 번째 쓰레드의 실행이 끝나지 않았기 때문이다.
원하는 결괏값인 5가 나오게 하려면 하나의 쓰레드가 완전히 종료된 후 다른 쓰레드를 실행해야 한다.(동기화)
동기화 방법은 메서드 동기화와 블록 동기화로 나눌 수 있다.
메서드 동기화
동기화하고자 하는 메서드의 리턴 타입 앞에 synchronized 키워드를 넣는다.
class MyData{
int data = 3;
public synchronized void plusData(){
int mydata=data; // 데이터 가져오기
try {Thread.sleep(2000);} catch(InterruptedException e){}
data=mydata+1; // 2초 후에 값을 1만큼 증가
}
}
plusThread1실행 결과: 4
plusThread2실행 결과: 5
블록 동기화
메서드 전체 중에 동기화가 필요한 부분만 동기화
class MyData{
int data = 3;
public void plusData(){
synchronized (this){
int mydata=data; // 데이터 가져오기
try {Thread.sleep(2000);} catch(InterruptedException e){}
data=mydata+1; // 2초 후에 값을 1만큼 증가
}
}
}
plusThread1실행 결과: 4
plusThread2실행 결과: 5
동기화의 원리
모든 객체는 자신만의 열쇠를 하나씩 갖고 있다.
블록 동기화의 소괄호안에는 어떤 객체가 와도 무방하며 블록은 그 객체가 갖고 있는 열쇠로 잠긴다.
이 때, 메서드를 동기화하는 경우에는 this 객체의 열쇠만을 사용한다.
3개의 동기화 영역이 동일한 열쇠로 동기화됐을 때
class MyData{
synchronized void abc(){ // this 객체가 갖고 있는 하나의 열쇠를 함께 사용
for(int i=0; i<3; i++){
System.out.println(i+"sec");
try{Thread.sleep(1000);} catch(InterruptedException e){}
}
}
synchronized void bcd(){ // this 객체가 갖고 있는 하나의 열쇠를 함께 사용
for(int i=0; i<3; i++){
System.out.println(i+"초");
try{Thread.sleep(1000);} catch(InterruptedException e){}
}
}
void cde(){
synchronized (this){ // this 객체가 갖고 있는 하나의 열쇠를 함께 사용
for(int i=0; i<3; i++){
System.out.println(i+"번째");
try{Thread.sleep(1000);} catch(InterruptedException e){}
}
}
}
}
public class Study {
public static void main(String[] args) {
MyData myData = new MyData(); // 공유 객체
// 3개의 쓰레드가 각각의 메서드 호출
new Thread(){
public void run(){
myData.abc();
};
}.start();
new Thread(){
public void run(){
myData.bcd();
};
}.start();
new Thread(){
public void run(){
myData.cde();
};
}.start();
}
}
0sec
1sec
2sec
0번째
1번째
2번째
0초
1초
2초
모두 this 객체의 열쇠를 사용하기 때문에 이들 중 1개의 메서드가 실행되는 도중에는 다른 어떤 메서드도 동시에 실행할 수 없다.
동기화 메서드와 동기화 블록이 다른 열쇠를 사용할 때
class MyData{
synchronized void abc(){ // this 객체가 갖고 있는 하나의 열쇠를 함께 사용
for(int i=0; i<3; i++){
System.out.println(i+"sec");
try{Thread.sleep(1000);} catch(InterruptedException e){}
}
}
synchronized void bcd(){ // this 객체가 갖고 있는 하나의 열쇠를 함께 사용
for(int i=0; i<3; i++){
System.out.println(i+"초");
try{Thread.sleep(1000);} catch(InterruptedException e){}
}
}
void cde(){
synchronized (new Object()){ // Object 객체가 갖고 있는 열쇠를 사용
for(int i=0; i<3; i++){
System.out.println(i+"번째");
try{Thread.sleep(1000);} catch(InterruptedException e){}
}
}
}
}
public class Study {
public static void main(String[] args) {
MyData myData = new MyData();
new Thread(){
public void run(){
myData.abc();
};
}.start();
new Thread(){
public void run(){
myData.bcd();
};
}.start();
new Thread(){
public void run(){
myData.cde();
};
}.start();
}
}
0번째
0sec
1sec
1번째
2번째
2sec
0초
1초
2초
'BACK > JAVA' 카테고리의 다른 글
[JAVA] 쓰레드의 6가지 상태 (0) | 2022.08.26 |
---|---|
[JAVA] 프로그램, 프로세스, 쓰레드 개념 (0) | 2022.08.18 |
[JAVA] 쓰레드의 속성 (0) | 2022.08.09 |
[JAVA] 쓰레드 생성 및 실행(Thread클래스, Runnable 인터페이스) (0) | 2022.08.08 |
[JAVA] 추상 클래스 (특징, 객체 생성 방법) (0) | 2022.08.04 |