ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TIL #21 - SingleTon, Synchronized
    프로그래밍/TIL(국비과정) 2020. 4. 24. 22:04

    #SingleTon

    싱글톤이란 어떤 클래스에서 최초로 객체를 static으로 생성해 메모리에 단 한 번만 할당하고 이후로 그 객체를 받아 계속 사용하는 것이다. 

    사실 아직 Java로 웹을 배우지 않은 상태이기 때문에, 싱글톤이 어떻게 쓰이는지만 얼핏 들은 상태이다. 

    개인적으로 좀 더 공부하여 싱글톤이 쓰이는 사례와 널리 다뤄지고 있는 싱글톤의 문제점에 대해서 따로 포스팅하도록 하겠다. 

    class SingleTon {
    	private int num = 3; 
    	private static SingleTon instance; // 메모리에 한번 잡힘 : static
    	
    	public static SingleTon getInstance() {
    		if(instance == null){
    			instance = new SingleTon(); //  
    		} 
    		return instance; 
    	}
        public static void main(String[] args) {
    		SingleTon aa = new SingleTon();
            }
      }

    메인 메서드를 보면 new를 이용해 객체를 하나 생성했다. 

    그리고 필드에 SingleTon이라는 타입으로 instance를 하나 static으로 잡아준다. 

    static으로 잡아준 instance는 메모리에 단 한 번 잡힌다. 

    이제 이것으로 객체를 불러와 같은 객체를 여러번 불러 사용할 것이다. 

    객체를 부르는 방법엔 new가 있지만, new를 사용하면 새로운 주소값이 생겨 결국 또 다른 객체를 생성하는 꼴이 된다. 

    싱글톤은 이미 한 번 할당된 주소값을 여러번 사용하는 것이다. 

     

    먼저 getInstance 메서드를 만들어 instance가 null 이면 객체를 할당해주고 그렇지 않으면 기존의 객체 instance를 리턴해준다. 

    객체를 또 생성하지 않으려고 객체의 유무를 확인하는 메서드이다. 

     

    위에서도 말했듯이 아래와 같이 new를 사용하면 안된다. 

    이는 주소값을 새로 할당해주므로 객체를 또 다시 생성하는 꼴이 되기 때문이다. 

    public static void main(String[] args) {
    		SingleTon aa = new SingleTon();
    		aa.num++;
    		System.out.println("aa = " + aa);
    		System.out.println("num = " + aa.num);
    		System.out.println();
    
    		SingleTon bb = new SingleTon();
    		bb.num++;
    		System.out.println("bb = " + bb);
    		System.out.println("num = " + bb.num);
    		System.out.println();

    위와 같이 각각 다른 주소값을 가지게 된다. 

     

    싱글톤은 아래와 같이 getInstance() 를 사용해 객체를 불러오는 것이다. 

    		SingleTon cc = SingleTon.getInstance(); 
    		
    		cc.num++;
    		System.out.println("cc = " + cc);
    		System.out.println("num = " + cc.num);
    		System.out.println();
    
    		SingleTon dd = SingleTon.getInstance(); 
    		
    		dd.num++;
    		System.out.println("dd = " + dd);
    		System.out.println("num = " + dd.num);
    		System.out.println();
    
    
    	}
    }
    

      코드는 위에서 아래로 흐르기 때문에 먼저 cc에게 객체가 할당이 된다.

    cc는 instance를 받아 필드에서 생성된 3을 ++ 해준다. 

    그런 다음 주소값과 가지고 있는 num 값을 리턴해주고 dd로 넘어간다. 

    dd에서는 기존에 cc가 가지고 있던 객체를 다시 받게 된다. 

    그렇기 때문에 cc에서 이미 한 번 ++ 해준 num을 또다시 ++ 해주게 된다. 

    둘의 주소값은 같고, dd의 num은 cc에서 한 번 ++ 해준 값을 다시 ++해주기 때문에 5가 되었다. 

     

    # Synchronized(동기화)

    여러개의 객체가 동시에 하나의 메소드에 접근하려고하면 부하가 발생한다. 

    이를 방지하기 위해 Lock을 거는 것이다. 

     

    동기화는 세가지 방법이 있다. 

    메소드에 직접 거는 방법, 클래스를 골라 직접 거는 방법, 클래스 전체에 거는 방법

     

    동기화의 예를 보자. 

    ATM기가 한대 있다. 

    그 ATM기를 이용하기 위해 엄마와 아들이 기다리고 있다. 

    ATM기에 먼저 도달한 사람이 돈을 인출할 것인데, 0.1초라도 먼저 ATM기에 도달한 사람에게 우선권이 주어지고 

    그 사람의 일이 끝난 후에 또 다른 사람에게 차례가 넘어갈 것이다. 

     

    먼저 스레드를 이용해서 엄마와 아들을 ATM기로 보낸다. 

    통장 잔액은 100000원이며 만원단위로 인출할 수 있고 잔액보다 많은 금액을 인출하려 하면 경고 메시지를 띄운다. 

    import java.util.Scanner;
    
    class ATMTest implements Runnable{
    	private long depositeMoney = 100000; // 잔액 
    	private long balance; // 찾고자하는 금액 
    
    	public static void main(String[] args) {
    		ATMTest atm = new ATMTest();
    		// 엄마와 아들 스레드 생성 
    		Thread mom = new Thread(atm, "엄마"); 
    		Thread son = new Thread(atm, "아들"); 
    											
    		// 스레드 두개 run
    		mom.start();
    		son.start();
    	}

    엄마와 아들 스레드를 생성해서 두 스레드를 START한다. 

    스레드를 통해 atm 객체 하나를 나누어 쓰는 상황이기 때문에 atm은 단 한개만 만들고 나누어 쓴다. 

     

    	@Override
    	public synchronized void run(){
    		System.out.println(Thread.currentThread().getName() + "님 안녕하세요."); 

    그리고 run() 이 실행되고 가장 먼저 ATM기에 도달한 스레드를 가져온다. (currentThread(0)

    getName()은 스레드의 이름만 가져온다. 

    여기서 run 메소드에 synchronized를 걸어 동기화를 시켜준다. 

    만약 synchronized를 걸어주지 않는다면 

    거의 동시에 도착한 것 같이 보이는 엄마와 아들 스레드에게 동시에 찾을 금액을 묻게 된다.

    이 프로그램에서 원하는 것은 차례로 먼저 들어온 스레드가 출금하는 것이기 때문에 

    synchronized로 동기화를 시켜 가장 먼저 들어온 스레드를 한번에 하나씩만 처리되게 한다.

    	Scanner scan = new Scanner(System.in);
    		System.out.print("찾으실 금액을 입력해주세요 : ");
    		balance = scan.nextLong();

     먼저 들어온 스레드에게 찾을 금액을 묻고 받아온다. 

    그런 다음 출금을 조정하기 위해서 withDraw() 메서드를 만든다. 

    withDraw();

     

    위에서 말했듯이 잔액보다 출금액이 많으면 경고메시지를 보낸다. 

    또 출금 금액은 만원단위로 한정한다. 

    public void withDraw(){
    		if(depositeMoney >= balance){ // 돈을 찾을 수 있는 상태 
    			if(balance%10000 == 0){ // 출금은 만원단위로 
    				depositeMoney = depositeMoney-balance;
    				System.out.println("잔액은 " +depositeMoney + "입니다");
    			}else {
    				System.out.println("만원 단위로 입력하세요");
    			}
    		}else {
    			System.out.println("잔액 초과");
    		}
    	}
    }

     

    댓글

Designed by Tistory.