에몽이

java Synchronized 본문

java

java Synchronized

ian_hodge 2017. 12. 21. 15:37


자바에서는 스레드를 동기화 하기 위해서 synchronized를 제공한다.

스레드는 synchronized 메소드에 들어가기 위해 lock을 얻고 메소드가 끝이나면 lock을 반환한다. 어떠한 스레드가 lock을 얻어 synchronized 메소드를 사용중이면 다른 메소드는 lock이 없으므로 synchronized에 접근할 수 없고, 다른 스레드가 lock을 반환 할 때까지 기다려야 한다.


synchronized(..변수..)

{

}



예) 안드로이드


class CBoard extends Activity

{

 void test_syn()

 {

      synchronized(this)  //이런식으로 해주어도 된다. this -> 자신

      {

          //코드들

          ....

          ....

      }

 }

}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



출처: http://dsnight.tistory.com/43


자바에서는 스레드를 동기화 하기 위해서 synchronized를 제공한다.

스레드는 synchronized 메소드에 들어가기 위해 lock을 얻고 메소드가 끝이나면 lock을 반환한다. 어떠한 스레드가 lock을 얻어 synchronized 메소드를 사용중이면 다른 메소드는 lock이 없으므로 synchronized에 접근할 수 없고, 다른 스레드가 lock을 반환 할 때까지 기다려야 한다.


쉽게 설명하자면 synchronized 메소드를 화장실에 비교하고 lock을 화장실 열쇠로 비교하겠다. 

화장실(synchronized)에 들어가기 위해서는 열쇠(lock)가 필요한데 열쇠(lock)은 단 1개만 존재하는 것이다. 그래서 어떤 사람이 화장실(synchronized)에 열쇠(lock)를 들고 들어가면 다른 사람은 화장실(synchronized)에 들어갈 열쇠(lock)가 없기 때문에 그 사람이 화장실(synchronized)에서 나올 때 까지 기다려야 한다고 생각하면 된다.


실제로 위와 같은 작업은 JVM에 의해 자동으로 수행되기 때문에 우리는 synchronized 메소드를 이용하기만 하면 된다.


synchronized를 이용하는 방법은 2가지가 있다.

1. synchronized method 방법

2. synchronized block 방법


1. synchronized method

메소드 앞에 synchronized 키워드를 붙여주면 간단하게 사용할 수 있다.


1
2
3
public synchronized 메소드명(파라미터) {
    ...
}


2. synchronized block

synchronized 메소드와 기능은 유사하다. 파라미터가 공유할 객체로 들어간다.

1
2
3
synchronized (공유할 객체) {
    ...
}



/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


출처: http://warmz.tistory.com/369



쓰레드와 동기화, 그리고 교착상태에 대한 설명 ::
http://warmz.tistory.com/350


공유
아래와 같은 방식으로 run()에서 공유할 객체에 접근하는 것이 가능해진다. 

예) 쓰레드가 객체를 공유하게 되는 방식 1 - Runnable 인터페이스 상속

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.*;
 
class example {
    public static void main(String[] args) {
        RunnableImpl r = new RunnableImpl();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
         
        t1.start();
        t2.start();
    }
}
 
class RunnableImpl implements Runnable {
    int iv = 0;
 
    @Override
    public void run() {
        int lv = 0;
        String name = Thread.currentThread().getName();
 
        while (lv < 3) {
            System.out.println(name + " Local Var:" + ++lv);
            System.out.println(name + " Instance Var:" + ++iv);
            System.out.println();
        }
    }
}

결과)

Thread-0 Local Var:1
Thread-0 Instance Var:1

Thread-0 Local Var:2
Thread-0 Instance Var:2

Thread-0 Local Var:3
Thread-0 Instance Var:3

Thread-1 Local Var:1
Thread-1 Instance Var:4

Thread-1 Local Var:2
Thread-1 Instance Var:5

Thread-1 Local Var:3
Thread-1 Instance Var:6


예) 쓰레드가 객체를 공유하게 되는 방식 2 - Thread 상속

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.util.*;
 
class example {
    public static void main(String[] args) {
        Data d = new Data();
        MyThread t1 = new MyThread(d);
        MyThread t2 = new MyThread(d);
         
        t1.start();
        t2.start();
    }
}
 
class Data {
    int iv =0;
}
 
class MyThread extends Thread{
     
    Data d;
     
    MyThread(Data d) {
        this.d = d;
    }
 
    @Override
    public void run() {
        int lv = 0;
 
        while (lv < 3) {
            System.out.println(getName() + " Local Var:" + ++lv);
            System.out.println(getName() + " Instance Var:" + ++d.iv);
            System.out.println();
        }
    }
}

결과) 

Thread-0 Local Var:1
Thread-0 Instance Var:1

Thread-0 Local Var:2
Thread-0 Instance Var:2

Thread-0 Local Var:3
Thread-0 Instance Var:3

Thread-1 Local Var:1
Thread-1 Instance Var:4

Thread-1 Local Var:2
Thread-1 Instance Var:5

Thread-1 Local Var:3
Thread-1 Instance Var:6

 
 
동기화(Synchronized) 
 - 공유 데이터에 lock을 걸어 작업중이던 쓰레드가 마칠때까지 다른 쓰레드에게 제어권이 넘어가지않게 보호한다.
 - synchronized 블럭이 끝나면 lock이 풀리고 다른 쓰레드도 접근가능하게 된다.
 - 교착상태(dead-lock)에 빠질 위험이 있으므로 주의한다.

synchronized를 이용한 동기화 방법 
 - 가능하면 메서드에 synchronized를 사용하는 메서드 단위의 동기화를 권장

1
2
3
4
5
6
7
8
9
1. 특정한 객체에 lock을 걸고자 할 때
synchronized(객체의 참조변수){
    // ...
}
 
2. 메서드에 lock을 걸고자할 때
public synchronized void calcSum(){
    // ...
}
 


예) 동기화 적용 전 : if문을 통과하고 출금을 수행하려는 순간 다른 쓰레드에게 제어권이 넘어가서 다른 쓰레드가 출금을 해버렸고(잔고:0) 또 이전의 쓰레드로 제어권이 넘어오면서 출금을 수행했기 때문에 잔고가 음수가 되어버렸다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.util.*;
 
class example {
    public static void main(String[] args) {
        MyThread r = new MyThread();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
         
        t1.start();
        t2.start();
    }
}
 
class Account{
    int balance = 1000;
     
    public void withDraw(int money){
        if(balance >= money){
            try{
                Thread.sleep(1000);
            }catch (Exception e) {}
            balance -= money;
        }
    }
}
 
class MyThread implements Runnable{
    Account acc = new Account();
     
    @Override
    public void run() {
        while(acc.balance > 0){
            // 100, 200, 300 중의 한 값을 임의로 선택해서 출금(withDraw)
            int money = (int)(Math.random() * 3 + 1) * 100;
            acc.withDraw(money);
            System.out.println("balance:" + acc.balance);
        }
    }
}

결과) 

balance:500
balance:500
balance:300
balance:0
balance:-300



예) 동기화 적용 후

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import java.util.*;
 
class example {
    public static void main(String[] args) {
        MyThread r = new MyThread();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
         
        t1.start();
        t2.start();
    }
}
 
class Account{
    int balance = 1000;
     
    // 동기화 적용 (메서드 단위의 동기화를 추천한다.)
    public synchronized void withDraw(int money){
        /* 객체에 lock을 걸 경우
        synchronized (this) {
            여기에 소스 코드를 집어 넣어도 된다.
        }
        */
        if(balance >= money){
            try{
                Thread.sleep(1000);
            }catch (Exception e) {}
            balance -= money;
        }
    }
}
 
class MyThread implements Runnable{
    Account acc = new Account();
     
    @Override
    public void run() {
        while(acc.balance > 0){
            // 100, 200, 300 중의 한 값을 임의로 선택해서 출금(withDraw)
            int money = (int)(Math.random() * 3 + 1) * 100;
            acc.withDraw(money);
            System.out.println("balance:" + acc.balance);
        }
    }
}

결과)
balance:700
balance:500
balance:400
balance:200
balance:0
balance:0



///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


http://blog.naver.com/meelong0/140094191195

1. Java에서 동기화란?
 
- 
"날 방해하지 말아줘~ 내가 됐다고 할 때까지 그냥 내버려둬!"

  - 네이버 국어 사전에서는 다음과 같이 동기화에 대해서 언급을 하고 있습니다.

  - "작업들 사이의 수행 시기를 맞추는 것. 사건이 동시에 일어나거나, 일정한 간격을 두고 일어나도록

    시간의 간격을 조정하는 것을 이른다."

  - 즉, 프로그램에 가져와서 이야기를 한다면... 이게 참 한 줄로 딱 짤라서 말하기 힘든데,

     "어떤 작업의 독립적이고 완전한 실행권을 보장하기 위한 방법"인 데 그냥 위에 갈색 표현이 맘에 듭니다.

  - 단, 방해하지 말라고 했다고 그냥 다른 사람은 무작정 됐다고 할 때까지 기다리는 문제가 발생할 수 있으니깐

    좀 더 폭넓은 관점에서 들여다 봤을 때에는 이 방법이 완전하다. 안전하다. 최고다. 이렇게는 단정 지을 수 없겠네요. 

  

2. 어떻게 쓸까? 함수, 블록, 변수에서 사용

  - synchronized 키워드는 다양한 곳에 붙어서 사용되어질 수 있습니다. 

 1) 함수 : 해당 함수가 실행되고 있는 동안에 동기화를 보장한다.

  - ex) public synchronized void importMoney(int money) { ... }

  - 예를 들어 은행에서 돈을 입금할 때는 이체나 인출이나 또 다른 입금에 대해서 독립적인 실행이 보장되어야 한다.

    따라서, 입금과 관련된 동작은 동기화를 보장하여야 한다.

 

 2) 블록 : 해당 블록내에서는 동기화를 보장한다.

  - ex) public void importMoney(int money) {

              synchronized (this)    {

                  ...

              }

              ...

          }

  - 예를 들어 은행에서 돈을 입금할 때 필요한 구간에만 딱 걸어두면 된다.

    동기화 구문이 커질수록 다른 작업은 그에 따라 작업 수행 속도가 저하될 수 있다.

    여기서 사용된 this 객체는 자기 자신 즉, 현재 이 함수를 수행하고 있는 객체에 대해서 동기화를 시킨다는 의미다.

    여러 사람이 여러 계좌의 각각의 구좌에 대해서 독립적으로 동기화 되어야 하므로 이 함수가 발생되는 동안에는

    해당 구좌에 대해서만 동기화를 확실히 수행하는 것이다.

 

 3) 변수 : 해당 블록내에서 해당 변수에 대하여 동기화를 보장한다.

  - ex) public void importMoney(int money) {

              synchronized (money)    {

                  ...

              }

              ...

          }

  - 구간 내에서 변수에 대해서 동기화를 수행한다. 물론 위에서처럼 객체 변수로 통한 접근도 가능하지만,

    이 방법을 사용하면 블록 내에서 해당 변수에 한하여 동기화를 보장하도록 한다.

  

3. 조심해야 할 부분 적재적소에 사용

  - 잘못된 방법으로 동기화를 할 경우, 원래 의도와는 다르게 동작하는 경우가 발생할 수 있다.

    2-2에서도 언급하였듯이 각각의 객체별로 해줄때와 단순히 하나의 메소드를 통째로 하는 방법,
    그리고 변수 하나를 이용하여 동기화 하는 방법은 그 쓰임새가 분명 조금씩 다를 수 있다.

    따라서 이런 실수를 범하지 않기 위해서 소스를 구현하기 이전에 어떻게 구현할지에 대해서

    먼저 잘 따져보는 것이 중요하다.




///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



자바 애플리케이션이 수행되면 서버나 WAS에 프로세스가 생성된다.
그리고 하나의 프로세스에는 하나이상의 Thread(스레드)가 생성된다. 
단일 스레드가 생성되었다가 종료될 수도 있고 여러개의 스레드가 생성되고 수행되다가 소멸될 수도 있다. 즉 1대多의 관계다.
스레드는 WAS가 관리하며 시스템 개발시에 스레드를 개발자가 직접 컨트롤하는 일은 별로 없으며, 오히려 제대로 알지 못한 상태에서 직접 제어하면 서비스의 안전성에 문제가 생길 수도 있으니 어떤 원리로 작동되는지 정도만 알아도 충분하다고 본다. 

스레드는 같은 프로세스내에서 동일한 데이터를 공유한다. 따라서 하나의 데이터에 대해서 동시에 여러개의 스레드가 접근이 가능하게 되고 그에 따라 데이터의 일관성에 관한 문제가 생길 수가 있다.
그래서 자주 사용되는 것이 synchronized(동기화)이다. 동기화란 하나의 자원(데이터)에 대해서 여러 스레드가 사용하려고 할때 한 시점에서 하나의 스레드만 사용할 수 있도록 하는 것이다.
synchronized 식별자는 보통 메소드의 선언부에 쓰고 이 키워드가 붙은 메소드는 한번에 하나의 스레드만 접근이 가능하며
메소드가 사용중일 때 다른 스레드가 메소드를 호출하면 앞의 스레드가 종료될때까지 기다려야 한다.

이 정도 설명이면 synchronized가 어떤 의미인지는 알 수 있을 것이다.
synchronized는 하나의 객체에 여러개의 객체가 동시에 접근해 처리하는것을 막기위해 사용한다.

public synchronized void exampleMethod() {
//code
...
..
}


private Object obj = new Object();
public void exampleMethod() {
synchronized(obj) {
//code
...
..
}
}


메소드를 동기화하려면 메소드 선언부에 synchronized 식별자를 쓰고, 특정부분을 동기화 하려면 해당 코드 블록에 선언해서
사용하면 된다. 애플리케이션 성능에 있어 이 식별자의 영향력이 막강함에 비해 사용법은 의외(?)로 간단하다.
이말은 거꾸로 해석하자면 synchronized를 잘 알고 쓰면 애플리케이션 자원의 안전성에 득이 되지만 제대로 알지못한 상태에서 남용해서 사용하면 오히려 시스템 성능에 치명적인 독이 될수도 있다는 점을 반드시 인지해야 한다.

그렇다면 어떤 때에 synchronized 식별자를 선언해야 할까?
가장 대표적인 경우이다.

* 하나의 객체에 여러개의 스레드가 접근해서 처리하고자 할때
* static으로 선언한 객체에 여러 스레드가 동시에 사용할때

이런 상황이 발생했을시에 시스템의 안전성 및 데이터의 일관성에 문제가 생길 수 있는 때는 
해당 메소드에 동기화를 부여해야 한다.
어쩌다 한번씩 생길 수 있는 오류라고 해도 개발자라면 항상 참이 될도록 가능한한 튼튼한 구조물을 지어야 하지 않을까...

한마디로 줄이자면,  synchronized... 꼭 필요한때만 사용하자!



///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


출처: http://iilii.egloos.com/4071694



java의 synchronized 분석

요즘 jsr 133 자바 메모리 모델을 보고 있는데, 처음에 lock거는 부분에 대해 나오더군요.
자바에서 lock을 거는 것은 보통 synchronized 구문을 이용하는데, 일반적인 자바 책에서는 "synchronized를 걸면 동시 접속이 안된다." 까지만 나와 있었는데, 보다가 보니 재미있는 부분들이 있어서 정리해볼랍니다.

synchronized (anObject){
// code!!
}
위와 같은 구문은 anObject를 기준으로 잡습니다. 그렇기 때문에 완전히 다른 객체끼리도 동기화가 가능합니다. 다음은 제가 주로 사용하는 디버깅 코드입니다.

synchronized (java.lang.Object.class) {
        System.out.println("===========디버깅 시작했다~================");
        System.out.println("time:"
                + new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()));
        System.out.print(new Throwable().getStackTrace()[0].getClassName() + "."
                + new Throwable().getStackTrace()[0].getMethodName() + "()");
        System.out.println("  line: " + new Throwable().getStackTrace()[0].getLineNumber());
        System.out.println(something);
        System.out.println("===========디버깅 끝났다~================");
    }

java.lang.Object는 하나이므로 java.lang.Object를 기준으로 동기화를 시키면 저 코드가 어디 들어가 있더라도 저 코드들끼리 꼬이는 일은 없습니다. 한번 =======디버깅 시작했다.========= 구문이 찍히면, ======== 디버깅 끝났다.===== 구문이 찍히기 전까지는 다른 쓰레드에서 저런 디버깅 메시지를 찍지 않고 기다리게됩니다.

메쏘드에 선언된 synchronized는 자기 객체(this)를 기준으로 동기화합니다.(static 메쏘드의 경우는 자기 클래스를 기준으로!) 그 객체에서 synchronized를 건 모든 메쏘드끼리도 동기화가 됩니다.

다음 코드를 보세요.

package synch.method1;

public class BlackOrWhite {
    private String str;
    private final String BLACK = "black";
    private final String WHITE = "white";
    
    public synchronized void black(){
        str = BLACK;
        try {
            long sleep = (long) (Math.random()*100L);
            Thread.sleep(sleep);
            if (!str.equals(BLACK)) {
                System.out.println("+++++++++++++++++broken!!++++++++++++++++++++");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public synchronized void white(){
        str = WHITE;
        try {
            long sleep = (long) (Math.random()*100L);
            Thread.sleep(sleep);
            if (!str.equals(WHITE)) {
                System.out.println("+++++++++++++++++broken!!++++++++++++++++++++");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

package synch.method1;

import java.util.Iterator;

public class Test {

    /**
     * @param args
     */
    public static void main(String[] args) {
        final BlackOrWhite bow = new BlackOrWhite();

        Thread white = new Thread() {
            public void run() {
                while (true) {
                    bow.white();
                }
            }
        };
        Thread black = new Thread() {
            public void run() {
                while (true) {
                    bow.black();
                }
            }
        };
        white.start();
        black.start();
    }
}

두개의 쓰레드가 BlackOrWhite 클래스의 인스턴스인 bow라는 변수를 공유하고 있습니다. 하나의 쓰레드는 black()이란 메쏘드만 주구장창 호출하고, 다른 하나의 쓰레드는 white()만 주구장창 호출해댑니다. 그런데, black()이란 메쏘드와 white()라는 메쏘드가 서로 동기화가 됩니다. 위 코드를 실행시키면 broken!이 찍히는 일은 절대 없습니다. 다시 말해 서로 다른 쓰레드에서 black()과 white()를 호출하지만 두 메쏘드 모두 bow라는 인스턴스에 소속되기 때문에 두 메쏘드가 동시에 진행되는 일은 일어나지 않습니다.

정리를 하자면.
synchronized foo(){
    //메쏘드 내용.
}

foo(){
    synchronized(this){
        //내용
    }
}
은 같은 얘기가 됩니다.

경우에 따라서 method 간에 동기화가 필요 없는 경우도 있을 수 있습니다. 다음의 예를 봅시다.

package synch.two;

import java.util.HashMap;
import java.util.Map;

public class TwoMap {
    private Map<String, String> map1 = new HashMap<String, String>();
    private Map<String, String> map2 = new HashMap<String, String>();
    
    public synchronized void put1(String key, String value){
        map1.put(key, value);
    }
    public synchronized void put2(String key, String value){
        map2.put(key, value);
    }
    
    public synchronized String get1(String key){
        return map1.get(key);
    }
    public synchronized String get2(String key){
        return map2.get(key);
    }
}

위의 예제에서는 두개의 map을 가진 객체가 있습니다. 두 map에 각각 put과 get을 지원합니다. put1()과 get1()은 동기화가 되어야 하지만, put1()과 put2()가 동기화될 필요는 없습니다. 그러나 method에 synchronized가 걸려 있기 때문에 4개의 메쏘드 모두 상호 동기화가 됩니다. 그다지 바람직하지 않은 코드죠.

위의 코드는 아래와 같이 바뀌는 것이 좋습니다.
package synch.two;

import java.util.HashMap;
import java.util.Map;

public class TwoMap {
    private Map<String, String> map1 = new HashMap<String, String>();
    private Map<String, String> map2 = new HashMap<String, String>();
    private final Object syncObj1 = new Object();
    private final Object syncObj2 = new Object();
    
    public void put1(String key, String value){
        synchronized (syncObj1) {
            map1.put(key, value);
        }
    }
    public void put2(String key, String value){        
        synchronized (syncObj2) {
            map2.put(key, value);
        }
    }
  
    public String get1(String key){
        synchronized (syncObj1) {
            return map1.get(key);
        }
    }
    public String get2(String key){
        synchronized (syncObj2) {
            return map2.get(key);
        }
    }
}

이렇게 하면 put1()과 get1() 끼리만, put2()와 get2()끼리만 동기화가 됩니다. 위의 예제에서는 굳이 syncObj1, syncObj2를 만들지 않고, map1과 map2를 이용하여 동기화하여도 됩니다만, 동기화를 위해 저렇게 동기화 전용 멤버 변수를 만들 수도 있다는 것을 보여드릴라고 한 겁니다.

 by 삼실청년 | 2008/01/09 15:46 | 컴터질~ | 트랙백(1) | 핑백(4) | 덧글(30)

트랙백 주소 : http://iilii.egloos.com/tb/4071694
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
 Tracked from Do your best at 2008/03/01 20:54

제목 : Java Synchronized
문서를 처리할 때, 동시에 액세스 하는것을 막기 위해 사용되기도 한다....more

 Linked at 건실성실착실 3실 청년! : .. at 2008/06/16 22:56

... 3,4와 같이 읽어들인 String을 풀에 등록시켜서 써야 할 때가 있습니다. 동일한 user에 대해서 어떤 로직에 synch를 걸어야 할 경우가 있죠.(synchronized 분석은 여기서)예를 들어, 한 번만 응모할 수 있는 이벤트가 있다고 칩시다. 일반적으로 디비에 이미 응모한 값이 있는지만 체크하지만 이는 완벽하지 않습니다. 브라우저를 두개 열어놓고 ... more

 Linked at 건실성실착실 3실 청년! : .. at 2010/09/09 21:03

... sp;add2. remove3. iterator를 이용하는 부분 전체 이 세 가지를 하나의 sync 객체를 이용해 줘야 합니다.synchronized 분석은 여기!!또 재밌는 부분은 list의 element가 2개만 있을 경웁니다.List&lt;String&gt; list = new ArrayList&lt;St ... more

 Linked at Brian's Secret P.. at 2011/02/17 21:04

... 지 않고, map1과 map2를 이용하여 동기화하여도 됩니다만, 동기화를 위해 저렇게 동기화 전용 멤버 변수를 만들 수도 있다는 것을 보여드릴라고 한 겁니다.#http://iilii.egloos.com/4071694 ... more

 Linked at 건실성실착실 3실 청년! : .. at 2011/10/28 00:09

... 트죠. 이게 다르면 IllegalMonitorStateException 이 발생합니다. synchronized에 대해서 명확하게 이해하셔야 하며, 잘 모를 때는 요기 클릭!! wait은 반드시 loop 안에서 쓰라고 합니다. 왜냐하면 여러 개의 Thread에서 접근하기 때문에 항상 조건을 따져봐야 하기 때문입니다. 이 내용은 ... more

 Commented by A2 at 2008/01/09 22:44  
중요 부분에 색을 입혀주셔서 보기가 참 좋네요.
다른 분들도 많은 도움이 될 것 같아요.
 Commented by yellowdeve at 2008/01/09 23:42  
스레드 관련해서 궁금했었는데...
좋은 정보 감사합니다.
 Commented by 용식 at 2008/12/09 18:42  
쓰레드 공부하다가 이곳에서 동기화에 대한 내용이 있던 것이 기억나서 보고 갑니다.
감사합니다~
 Commented by 삼실청년 at 2008/12/11 17:53
이제 용식님은 친구 같네요.^^
용식님 위에 덧글 다신 분들이 있네요. 첨에 블로그질 시작해서 다른 사람들이 덧글 달았을 때 당황했었습니다. 대답을 해줘야 되는 건지 말아야 되는 건지 뭐라고 대답을 해줘야 되는 건지.. 막 당황했었더랬습니다. 나도 알고 쓴 건지 모르고 쓴 건지 모르겠는 글에 누가 덧글 달아서 화들짝~ 했었습니다.
이게 벌써 거의 1년 전 일이 되었네요.
 Commented by 세훈 at 2009/03/11 12:25  
마지막 예제에서 put2와 get2메소드가 synchronized되있는데 오기 하신 것인지 아니면 싱크한 이유가 있는것인지 궁금 합니다.
 Commented by 삼실청년 at 2009/03/12 21:36
HashSet의 add와는 달리 HashMap의 put은 기존에 있던 값을 override합니다.
따라서 put과 get이 거의 동시에 진행될 경우 put이 먼저 호출되었는데, get이 먼저 작업이 끝나서 갱신되지 않은 값을 가져올 수도 있습니다. 이런 경우 put과 get에 synch를 걸어주게 되면, put이 완료된 후에야 get이 진행되기 때문에 get으로 가져오는 값은 항상 최신값이 됩니다.
질문하신 게 이게 맞나요?
 Commented by 세훈 at 2009/03/22 16:27  
아뇨.. 마지막예제 보시면 put2메소드와 get2메소드가 두번 동기화 되어있습니다. 
this와 syncObj2로요... 아무리 생각해봐도 오기하신것 같아서 말씀드린 겁니다.

덕분에 자바 관련글 재미있게 보고 공부도 많이 하고 있습니다. 감사합니다
 Commented by 삼실청년 at 2009/03/22 19:54
아~~ 그러네요.. 오기 맞습니다. 후딱 고쳐놓겠습니다. 메쏘드 선언부에 synchronized가 들어있네요. 지적 감사합니다^^
 Commented by 몽백작 at 2010/04/01 08:53  
자꾸 헷갈렸는데, 설명 잘 보고 갑니다. 정리를 너무 잘해주셨어요. ^^
 Commented by 삼실청년 at 2010/04/09 14:27
우와~ 만우절날 칭찬들었다~
그..그런 거 아니죠??
만우절이라서 그랬던 건 아니죠??ㅜㅜ
 Commented by 최자 at 2010/11/18 10:47  
글 잘 읽었습니다 도움이 되었습니다
 Commented by 삼실청년 at 2010/12/01 02:57
다행입니다^^ 가끔이지만 저도 누군가한테 도움을 주는 존재인 거로군요~ 기쁩니다~~
 Commented by 유충근 at 2011/02/08 17:17  
잘 읽었습니다. 정말 이해가 쏙 되네요 ... ^^
 Commented by 삼실청년 at 2011/02/14 23:52
^^ 도움이 되셨길~~
 Commented by 쎄미 at 2011/09/07 10:34  
고맙습니다 ^^
 Commented by 삼실청년 at 2011/09/26 23:12
별말씀을요^^
 Commented by 미스터리 at 2012/01/05 10:27  
쉬운설명 감사 드립니다.^^
 Commented by 삼실청년 at 2012/02/21 00:21
넹^^
 Commented by 하준호 at 2012/01/12 10:41  
머리에 쏙쏙 들어오네요~
 Commented by 삼실청년 at 2012/02/21 00:23
최대한 쉽게 정리할라고 했었어요. 도움이 되셨다면 다행^^
 Commented by 호러블캣 at 2012/02/17 16:53  
감사합니다. 정리 너무 깔끔하네요^^
 Commented by 삼실청년 at 2012/02/21 00:23
색깔이 깔끔했으면 하는 바램이 있어요.. 저는 색맹도 아닌데 색감이 왜이리 떨어지는지..ㅜㅜ
 Commented by 괜한기대 at 2012/06/06 18:06  
감사합니다~~~!
쉽게 설명되어있어서 좋아요!ㅋㅋ
 Commented by 삼실청년 at 2012/06/16 23:45
^^ 찾아주셔서 감사합니다.
 Commented by Xeon at 2012/12/27 19:41  
좋은 내용 감사합니다.
 Commented by 삼실청년 at 2013/05/06 05:48
매우 오랜 시간이 지났지만 그래도... 답글은 달아야겠다는 일념으로!!!
늦게나마...
도움이 되셨길 바래요...^^
 Commented by rtrt at 2013/03/22 23:07  
잘보고 갑니다. 많은 도움이되었어요.
 Commented by 삼실청년 at 2013/05/06 05:49
진짜 진짜 도움이 되셨으면 좋겠어요..^^
 Commented by 여리 at 2013/10/02 16:01  
좋은 내용 감사합니다. 그런데 마지막 예제는 
private final Object syncObj1 = new Object();
private final Object syncObj2 = new Object()

같은 선언 없이 그냥

public void put1(String key, String value){
synchronized (map1) {
map1.put(key, value);
}
}
public void put2(String key, String value){ 
synchronized (map2) {
map2.put(key, value);
}
}

public String get1(String key){
synchronized (map1) {
return map1.get(key);
}
}
public String get2(String key){
synchronized (map2) {
return map2.get(key);
}
}
}

하는게 더 깔끔하고 의미도 더 잘 전달 될거 같은데요?
 Commented by 삼실청년 at 2013/10/05 04:06
위에서도 적었지만 필요에 따라 저런 식으로 sync 전용 객채를 만들 수도 있다는 것을 보여주기 위해서 쓴 겁니다. 말씀하신대로 그냥 map 객체를 기준으로 sync 잡는 게 더 깔끔하긴 하죠.



////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


출처: http://iilii.egloos.com/5565036



제 홈페이지의 모든 글은 anti-nhn license에 따릅니다.



java.lang.Object 메쏘드 분석 6 - wait , notify

몇 년만에 java.lang.Object에 대한 마지막 글을 씁니다.

toString은 분석 안 할 겁니다. toString은 알아볼 수 있게 잘 구현하면 됩니다. 이클립스의 toString 구현하기 기능을 쓰시면 됩니다.

또 java.lang.Object에 private으로 registerNatives 함수가 있는데, 이것도 분석 안 할겁니다.

wait과 notify는 한 묶음으로 같이 이해하셔야 합니다.


wait은 아래와 같이 3가지 오버로딩된 메쏘드로 있습니다.

    public final void wait() throws InterruptedException               : 누군가 깨울 때까지 기다리겠음. 안 깨워주면 안 일어남.
    public final native void wait(long timeout) throws InterruptedException    : 누군가 깨워주거나 timeout 까지 안 깨워주면 알아서 일어나겠음.
    public final void wait(long timeout, int nanos) throws InterruptedException  : 누군가 깨워주거나 timeout에 nanos 까지 고려한 시간까지 안 깨워주면 일어나겠음. ( 그러나 실제 코드에서 nanos는 크게 의미가 없음.)

포인트는 가운데 놈만 native!! 즉, 첫번째 놈과 마지막 놈은 가운데 놈에 적당히 인자를 넣어서 호출합니다.( 마지막 놈의 원래 의도는 그게 아니었던 것 같지만, 어쨌거나 까보니 그렇게 생겼네요.) 

애들은 전부 기다리기 위한 애들이고, 깨우는 것은 notify()와 notifyAll()로 합니다. notify()는 하나만 깨우는 거고, notifyAll()은 전부 깨우는 겁니다. 그 외에는 비슷하다고 보면 됩니다. 이 후부터는 따로 구분하지 않는 한 notify라고 하면 notifyAll도 똑같다고 보시면 됩니다. 자세한 건 뒤에서 다시 다루겠습니다.



wait의 의미는 "기다린다" 입니다. "무엇이" "무엇을 기준으로" "언제까지" 기다리는 것인지를 명확히 이해해야 합니다.

1. 무엇이?
기다리는 주체는 wait이 호출된 Thread 입니다. wait 코드가 있는 곳에서 Thread.currentThread() 했을 때 나오는 그 Thread.

2. 무엇을 기준으로?
obj.wait() 이 호출되었다면, obj를 기준으로 기다리는 것입니다. 기준이 왜 중요한가 하면 깨우는 기준과 wait하는 기준이 같기 때문입니다.

3. 언제까지?
다음 중 하나가 만족할 때까지입니다.
* timeout 될 때까지.
* notify나 notifyAll로 깨워줄 때까지. ( notify에 대해서는 아래서 자세히 설명할 겁니다.) 여기서 중요한 것은 깨우는 기준이 wait을 가진 instance입니다. 즉, obj.wait() 이라고 호출이 되었으면, obj.notify()가 호출되어야 깨어납니다. 물론 notify()는 다른 Thread에서 호출합니다.
* Thread의 interrupt가 호출 될 때까지. 얘도 notify와 비슷하지만 기준이 다릅니다. 잠자는 Thread를 기준으로 깨웁니다. obj.wait()이 걸려있다고 하더라도 그것을 건 Thread를 기준으로 깨웁니다. 그리고 이렇게 깨우면 위의 두 가지 경우와는 다르게 InterruptedException이 발생합니다. 이건 언젠가 먼 훗날 Thread에 관련된 글을 쓰겠습니다. 다 쓰면 링크 달께요^^
* 기타 있어서는 안 될 상황. Thread.stop() 이나 jvm이 죽는 다거나.. 등등등..


obj.wait을 걸기 위해서는 반드시 그 obj를 기준으로 synchronized 블럭을 만들고 그 안에서 걸어야 합니다.

synchronized (obj) { 
while (뭔가 조건){
obj.wait();
}
}

synchronized 의 obj와 obj.wait()에서의 obj는 같다는 게 포인트죠. 이게 다르면 IllegalMonitorStateException 이 발생합니다. synchronized에 대해서 명확하게 이해하셔야 하며, 잘 모를 때는 요기 클릭!!

wait은 반드시 loop 안에서 쓰라고 합니다. 왜냐하면 여러 개의 Thread에서 접근하기 때문에 항상 조건을 따져봐야 하기 때문입니다. 이 내용은 뒤에 notify와 묶어서 다시 다루겠습니다.



wait set이라는 개념이 있는데, 어떤 object가 소유한 set 이라고 보시면 됩니다.
obj.wait이 호출되면
1. Thread.currentThread() 가 멈춘다.
2. obj의 wait set에 그 Thread가 들어간다.
3. synchronized 로 잡은 monitor의 lock을 푼다. 

로 보시면 됩니다. 왜 obj의 wait set에 들어가냐하면 나중에 notify() 깨울 기준이 되기 때문입니다.

wait이 걸리면 synchronized 블럭에서 일단 빠져나온다고 보시면 됩니다. 즉 모니터를 놔 줍니다. 그리고 notify에 의해서 깨어나면 다시 synchronized 안 으로 들어간다고 보시면 됩니다.


obj.nofiy() 는 obj.wait을 호출해서 obj의 wait set에 박혀서 잠자고 있는 Thread를 깨워줍니다. 여러 놈이 있을 때 notify()는 그 중 아무 놈이나 한 놈을 깨우고, notifyAll()은 전부 깨웁니다.

notify도 마찬가지로 synchronized 블럭 안 에 있어야 합니다.

synchronized (obj) { 
obj.notify();
}

wait과 마찬가지로 같은 monitor!! 입니다.

obj.notify()가 호출되는 시점에는 아직 synchronized 블럭 안이기 때문에 그에 의해서 깨어난 쓰레드는 다시 시작을 못합니다. wait()도 같은 모니터를 가지기 때문에 wait()이 호출된 이후의 코드를 호출하려면 다시 synchronized 구문 안에 들어가야 하는데 notify()가 아직 synchronized 구문을 빠져나오지 않았으므로 아직 실행이 안 되는 겁니다. notify를 실행한 Thread가 synchronized 블럭을 빠져나가는 순간부터 monitor 쟁탈전이 벌어지지만 notify에 의해 깨어난 Thread가 그 mornitor를 쟁취할 수 있을 지는 모르는 일입니다. 아래 코드를 실행시켜 보시면 프로그램이 안 끝나는 사태를 볼 수 있습니다. (여러 번 하다 보면 한 번쯤은 일어날 겁니다.)

public class ThreadTest {
private volatile boolean active = false;
private volatile int i = 0;

public synchronized void mWait() throws InterruptedException {         // 모니터 쟁탈 포인트 1. synchronized method라서.
while (!active) {
wait();   //모니터 쟁탈 포인트 2. 깨어나는 순간 모니터를 다시 획득해야 Thread가 진행됨.
}
active = false;
System.out.println("print\t" + i);
}

public synchronized void mNotify() { // 모니터 쟁탈 포인트 3. 마찬가지로 synchronized method라서.
active = true;
notifyAll();
i++;
System.out.println("after notify\t" + i);
}

private static class WaitCaller extends Thread{
private final ThreadTest a;
public WaitCaller(ThreadTest a) {
this.a = a;
}
public void run() {
try {
a.mWait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class NotifyCaller extends Thread{
private final ThreadTest a;
public NotifyCaller(ThreadTest a) {
this.a = a;
}
public void run() {
a.mNotify();
}
}
public static void main(String[] args)  {
final ThreadTest a = new ThreadTest();
final int cnt = 2;
for (int i = 0; i < cnt; i++) {
new WaitCaller(a).start();
}
for (int i = 0; i < cnt; i++) {
new NotifyCaller(a).start();
}
}
}

ThreadTest라는 놈은 wait에서 loop를 위한 active라는 멤버 변수를 가지고 있습니다. 그리고 notify가 호출될 때 마다 값이 증가하는 int로 i 를 가지고 있습니다.
이 ThreadTest에 대한 인스턴스 1개를 만들어서 wait을 호출하는 WaitThread 가 2개 (WaitCaller 1 , 2 라고 가정) 뜨고, notify를 호출하는 NotifyCaller가 2개 (NotifyCaller 1,2)가 뜹니다. 

monitor 쟁탈전이 일어나는 포인트는 총 3개 입니다. 4개의 Thread 중 어떤 놈이 모니터를 쟁취하느냐에 따라 결과가 달라집니다.

        WaitThread 1: 포인트 1에서 모니터 획득.
        WaitThread 1: 포인트 2에서 모니터 상실.(wait에 빠지니까..)
        WaitThread 2: 포인트 1에서 모니터 획득.
        WaitThread 2: 포인트 2에서 모니터 상실.(wait에 빠지니까..)
NotifyThread 1: 포인트 3에서 모니터 획득.
        NotifyThread 2: 포인트 3에서 모니터 획득 실패.
NotifyThread 1: notify 호출하고 i 증가 시키고 할 꺼 다하고 죽음. 당연히 모니터 상실.

여기서 보면 notifyAll로 호출되었기 때문에 WaitThread 1,2 가 모두 포인트 2에서 모니터를 얻으려고 하며, NotifyThread 2도 모니터를 획득하려고 하고 있으므로, NotifyThread 1을 제외한 3개의 Thread가 모니터를 노림.

시나리오 1.
WaitThread 1: 포인트 2에서 모니터 획득. 일 하고 죽고 모니터 놔주고.
WaitThread 2: 포인트 2에서 모니터 획득. 그러나 while 문 때문에 active가 false이므로 모니터 다시 상실하고 다시 wait 상태로.
NotifyThread 2: 포인트 3에서 모니터 획득. 할거 다하고 모니터 놓고 죽음.
WaitThread 2: NotifyThread2 덕에 깨어나서 할 거 하고 죽음.
아래와 같은 결과

after notify 1
print 1
after notify 2
print 2


시나리오 2.
NotifyThread 2: 포인트 3에서 모니터 획득. 할거 다하고 모니터 놓고 죽음.
WaitThread 1: 포인트 2에서 모니터 획득. 일 하고 죽고 모니터 놔주고.
WaitThread 2: 포인트 2에서 모니터 획득. 그러나 while 문 때문에 active가 false이므로 모니터 다시 상실하고 다시 wait 상태로.
아래와 같은 결과 - 안 끝남.. WaitThread 2가 wait 상태에 빠졌는데 더 이상 아무도 안 깨워줌.

after notify 1
after notify 2
print 2


지금 모든 가능한 시나리오를 설명한 것도 아니고 쓰레드도 꼴랑 4개 뿐인데도 마구 복잡해질 기미가 보이고 있습니다.

위 프로그램에서의 문제는 갯수에 관련된 게 없다는 것입니다. wait이 기다리는 조건이 boolean 값이기 때문에 두번 true로 설정해 봤자 1번 true로 설정한 것과 별반 다르지 않지요.

뭔가 좀 비슷하지만 아래 프로그램을 실행시켜보면 멈추지 않습니다. 아래 프로그램은 notify에서 list에 값을 넣고, wait에서 그 list에서 값을 빼내는 프로그램입니다.  위에서 얘기한 시나리오 2가 다음과 같이 달라집니다.

NotifyThread 2: 포인트 3에서 모니터 획득. 할거 다하고 모니터 놓고 죽음.
WaitThread 1: 포인트 2에서 모니터 획득. 일 하고 죽고 모니터 놔주고.
WaitThread 2: 포인트 2에서 모니터 획득. while 문에서 보니 아직 list에 뭔가 남아있으므로 while문 빠져나오고 실행하고 죽고.
데드락 같은 거 안 생김!



import java.util.LinkedList;


public class ThreadTest{
private volatile LinkedList<Integer> list = new LinkedList<Integer>();

public synchronized void mWait() throws InterruptedException {
while (list.isEmpty()) {
wait(1000);
}
Integer i = list.pop();
System.out.println("print\t" + i);
}

public synchronized void mNotify(int i) {
notifyAll();
list.add(i);
System.out.println("after notify\t" + i);
}

private static class WaitCaller extends Thread{
private final ThreadTest threadTest;
public WaitCaller(ThreadTest threadTest) {
this.threadTest = threadTest;
}
public void run() {
try {
threadTest.mWait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class NotifyCaller extends Thread{
private final ThreadTest threadTest;
private final int i;
public NotifyCaller(ThreadTest threadTest, int i) {
this.threadTest = threadTest;
this.i = i;
}
public void run() {
threadTest.mNotify(i);
}
}
public static void main(String[] args) throws InterruptedException {
final ThreadTest threadTest = new ThreadTest();
final int cnt = 2;
for (int i = 0; i < cnt; i++) {
new WaitCaller(threadTest).start();
}
for (int i = 0; i < cnt; i++) {
new NotifyCaller(threadTest,i).start();
}
}
}


loop 안에서 wait을 걸어야 하는 이유?
여러 개의 쓰레드 중 wait에 관련된 thread가 있고, notify에 관련된 thread가 있는데, 모두 하나의 모니터 안에서 놀게 됩니다. 따라서 wait에 관련된 thread가 어떤 일을 처리하고 나서 다음에 모니터를 가지는 놈이 또 다른 wait에 관련된 놈이면 또 어떤 일을 처리하려고 할 겁니다.

위의 예제 중 두번째 예제를 보면 while문이 없을 경우에 문제가 생길 수 있습니다.
list에 1개가 있을 때 , WaitCalller 1이 list.pop()을 실행하고, 끝나려는데, WaitCaller 2도 list.pop()을 실행하려고 합니다. 이미 WaitCalller 1이 가져갔기 때문에 WaitCaller 2에서는 에러가 날 수 있습니다.



notify는 가급적 자제 하고 notifyAll을 쓰기를 권장합니다.

notify는 하나의 Thread만 깨웁니다. WaitThread 1,2가 잠자고 있을 때 notifyAll은 한 번만 호출 되어도 두 쓰레드가 모두 깨어나고,  모니터 쟁탈전을 하더라도 어쨌건 둘이 순차적으로 실행은 될 겁니다. 하지만, notify로 깨울 경우 둘 중 하나만 깨어나기 때문에 좀비 Thread가 생길 가능성이 생깁니다. 따라서 가급적 notifyAll()을 쓰는 게 바람직 합니다.

 by 삼실청년 | 2011/10/28 00:09 | 컴터질~ | 트랙백 | 핑백(1) | 덧글(5)

트랙백 주소 : http://iilii.egloos.com/tb/5565036
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
 Linked at java.lang.Object.. at 2013/03/26 17:49

... gloos.com/4022941 finalize()http://iilii.egloos.com/4091133 wait(), notify() http://iilii.egloos.com/5565036About these ads 이 글 공유하기:트위터Facebook이것이 좋아요:좋아하기 가져오는 중...응답 취소 여기에 댓글을 입력하세요. ... more

 Commented by 용식 at 2011/11/01 21:15  
거의 사용을 하지 않았던 메서드라서 그런지
이해하기가 쉽지 않네요..^^;;

몇번 정도 차분하게 더 읽어봐야 할 것 같습니다. :)
 Commented by 동글이 at 2011/11/05 16:58  
초급자들에게 큰 도움이 되는 블로그입니다. 감사합니다. 특히 자바를 공부하면서 어려워서 그냥 지나가거나 잘 안쓸거라고 간과했던 부분들이 여기서 많이 정리되어 있어서 놀랐습니다. 종종 찾아뵐게요~
 Commented by 삼실청년 at 2011/11/10 14:49
찾아주셔서 감사합니다.
저도 종종 글 올릴께요.^^
 Commented by 잡부 at 2011/11/09 00:25  
정말 감사합니다. 제가 나이만 먹었지..여태 자바를 C처럼 코드를 짜다 님 글들 보면서 정말 감동입니다.
그런데..진짜 청년이신가요.?ㅋ
 Commented by 삼실청년 at 2011/11/10 14:52
소년이라고 하기에는 나이가 좀 많이 많고, 중년이라고 하기에는 아직 어리다고 생각합니다. 그럼 대략 청년이라고 봐도 되지 않을까 싶네요.
건실성실착실 3실 청년은 제가 대학 다닐 때부터 쓰던 닉인지라 중년이 되어도 안 바뀔 것 같네요.
소녀시대가 더 이상 소녀가 아닐 지라도 소녀시대인 것처럼 저도 나이 들어도 청년할랍니다.ㅋㅋ



/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


출처: http://blog.naver.com/inho860/220267279813




동기화의 필요성


동기화 하지 않는다면 공용메모리, 즉 공용변수에 동시에 접속해

하나의 작업의 쓰기(WRITE)가 끝나기 전에

다른 작업에서 읽기(READ)를 해가는 것이 가능해진다.


아래의 예로 보면

만약 이런일이 발생한다면.. 좋겠지만ㅋㅋ

프로그래머들은 다 짤릴것이다..





위 그림은 동시에 계좌 정보를 불러와 한명은 입금 한명은 출금을 하는 상황이다

1000+500-1000=500 돼야 하는 상황이지만..

결과는 잔고:1500이 되는 것이다.


이유는 작업1과 작업2는 동시에 잔고를 불러 왔기 때문이다.


이처럼 하나의 작업이 끝나기전에 공용메모리나 즉 공용변수에

접근이 허용이 된다면 이런일이 빈번히 발생 하게 됨으로

이를 방지하기 위한 뮤텍스, 세마포어등 다양한

방법들로 해결해야 한다.


Java 에서 Thread를 사용하다보면 발생하는 하는데

위 같은 상황을 방지 하기 위해 synchronized 을 사용한다.


-예제-

Thread에서 1~100까지 프린트 하는 프로그램이다.




끝에 결과는 99 이다


STATIC 으로 선언된 클래스 객체간의 공용변수를

스레드 마지막에 프린트 하는 코드이다

위 결과를 보면 중간에 35에서 중복프린트가 일어 났음을 확인 할 수 있다.

컴퓨터는 존니스트 빠른 처리를 하기때문에 동기화를 해주지 않는다면

언제든지 이런일이 일어 날수 있다!!

.

.

이럴때 사용하는 synchronized 키워드

count++을 해주는 메소드에 synchronized 키워드를 아래와 같이

사용하면 해결 된다. 



여러번 돌려보아도 결과는 항상 100이 나오게 된다.



출처: http://202psj.tistory.com/716 [알레폰드의 IT 이모저모]

Comments