고전적인 싱글턴 패턴

- 객체가 하나만 필요한 경우는 굉장히 많다, 스레드풀, 레지스트리 설정 객체 아니면 그래픽카드객체 등등... 이런경우에는 객체가 한가지만 필요하다.

- 분명히 static 전역 객체를 쓰면, 구지 싱글턴을 안써도 될텐데....? 라는 말을 할 수 있을것이다. 그렇지만 메모리 효율이라는 명목아래 우리는 싱글턴 패턴을 쓴다.

public class Singleton {
	private static Singleton unique;
    
    //접근 제한자를 써서 외부에서 객체를 생성못하게함.
    private Singleton() {}
    
    public static Singleton getInstance () {
    	if (unique == null) {
        	uniqueInstance = new Singleton();
        }
        return unique;
    }

}

 

- 위와같은 코드는 누구나 한번쯤 봤을것이다. 그러나, 이런 식으로 싱글턴을 쓰지않도록 주의해야 한다.

 

문제 제기

- 우리는 초콜릿바 공장을 운영하고 있고, 초콜릿을 녹이는 가마솥이 있다고 가정하자, 이 솥은 컴퓨터로 제어된다.

- 이 가마솥에서는 초콜릿과 우유를 받아서 끓이고 다음공정으로 넘기는 역할을 한다. 빈 가마솥을 끓이다거나 가득찬 가마솥에 끓인다거나 하는 실수는 절대 하지말아야한다.

public class ChocolateGamasot {
	private boolean empty;
	private boolean heat;
	
	public ChocolateGamasot() {
		empty = true;
		heat = false;
	}
	
	//가마솟이 비어있을때만 집어넣음.
	public void fill() {
		if(isEmpty()) {
			empty = false;
			heat = false;
			// 원유를 집어넣음
		}
	}
	
	public void drain() {
		if(!isEmpty() && isHeat()) {
			//끓인 재료를 다음 단계로 넘김
			empty = true;
		}
	}
	
	public void boil() {
		if(isEmpty() && isHeat()) {
			// 끓임
			heat = true;
		}
	}
	
	private boolean isHeat() {
		return heat;
	}

	public boolean isEmpty() {
		return empty;
	}
}

 

- 이런 가마솥 객체를 만들었다고 가정하자, 가마솥 객체가 두개가 있으면 어떻게될까?

- 대개가 있으면 안되므로 이를 싱글턴으로 바꿔보자.

public class ChocolateGamasot {
	
	private static ChocolateGamasot unique;
	private boolean empty;
	private boolean heat;
	
	private ChocolateGamasot() {
		empty = true;
		heat = false;
	}
	
	public static ChocolateGamasot getInstance() {
		if(unique == null) {
			unique = new ChocolateGamasot();
		}
		return unique;
	}
	
	//가마솟이 비어있을때만 집어넣음.
	public void fill() {
		if(isEmpty()) {
			empty = false;
			heat = false;
			// 원유를 집어넣음
		}
	}
	
	public void drain() {
		if(!isEmpty() && isHeat()) {
			//끓인 재료를 다음 단계로 넘김
			empty = true;
		}
	}
	
	public void boil() {
		if(isEmpty() && isHeat()) {
			// 끓임
			heat = true;
		}
	}
	
	private boolean isHeat() {
		return heat;
	}

	public boolean isEmpty() {
		return empty;
	}
}

 

- 위와같이 싱글턴으로 바꾼뒤 이 소스를 실행시켰더니 문제가생겼다.

//Main 함수
ChocolateGamasot gamasot = ChocolateGamasot.getInstance();

gamasot.fill();
gamasot.boil();
gamasot.drain();

- JVM 내에 두개의 스래드가 위의 코드로 접근했는데 2개의 객체가 생긴것이다!!! (이럴수가!!!)
1번스레드가 if === null 을 확인하고, 그 다음구문을 실행하던중, 다음 스레드가 똑같이 이프문 안으로 분기된것이다.

 

	public static synchronized ChocolateGamasot getInstance() {
		if(unique == null) {
			unique = new ChocolateGamasot();
		}
		return unique;
	}

- 위와같이 동기화 하여 문제를 해결하긴 했는데, 왠지 오버헤드가 너무아까운 생각이든다.. 일단 unique 변수에 ChocolateGamasot를 한번 초기화하기만하면 구지 getInstance() 전체 함수에 동기화를 거는 오버헤드는 필요 없을듯하다.

 

더 효율적으로 getInstance의 동기성을 제어하자

1. getInstance() 의 속도가 중요하지않다면 그냥 둔다.
메소드를 동기화하면 성능이 100배정도 저하된다고한다. 그럼에도 불구하고 getInstance의 속도가 그닥 중요하지않다면 놔두도록하자.

2. 인스턴스를 필요할때 생성하지않고, 바로 생성하여둔다.

	private static ChocolateGamasot unique = new ChocolateGamasot();
	private boolean empty;
	private boolean heat;
	
	public static ChocolateGamasot getInstance() {
		return unique;
	}

- 위 같은방법도 그닥 나빠보이지 않는다 (근데 static 변수만 쓸때와 차이점은 뭐지??)

3."DCL(Dobule Checking Locking)"을 써서 getInstance()에서 동기화 되는 부분을 줄여보자.

private static volatile ChocolateGamasot unique;
private boolean empty;
private boolean heat;

public static ChocolateGamasot getInstance() {
	
	//인스턴스가 있는지 확인하고 synchronized 블럭으로 접근
	if(unique == null) {
		// 이렇게하면 처음에만 동기화 진행.
		synchronized (ChocolateGamasot.class) {
			if(unique == null) {
				unique = new ChocolateGamasot();
			}
		}
	}
	return unique;
}

 

- 잠깐! volatile 이란 무엇일까?
“… the volatile modifier guarantees that any thread that reads a field will see the most recently written value.” - Josh Bloch

이를 해석해보자면, volatile 이란 어떤 쓰레드가 read 하더라도 항상 최근값만 불러온다는 뜻이다.

Volatile을 사용하게 된다면, RAM에서 직접 변수를 읽고 쓰도록 Thread에게 강제한다. 이렇게 되면 스레드는 캐시에 존재하는 복사본이 아닌, RAM에 있는 마지막버전을 확인한다. 스레드가 동기화된 블록으로 들어가게 되면 ( 코드상 sychronizied 부분) 다른 모든 스레드는 첫번째 스레드가 동기화된 블록에서 나갈때 까지 기다린다. (캐시에 접근하지않고 직접 RAM에 직접 읽고 기록)

출처:https://stackoverflow.com/questions/106591/what-is-the-volatile-keyword-useful-for

 

What is the volatile keyword useful for

At work today, I came across the volatile keyword in Java. Not being very familiar with it, I found this explanation: Java theory and practice: Managing volatility Given the detail in which that

stackoverflow.com

c언어의 예시로 쉽게 volatile 설명 :https://dojang.io/mod/page/view.php?id=749

 

- 참고로 DCL은 자바 1.4 버전 이후부터만 쓸 수 있다. (1.4 이전 JVM 중에는 volatille 을 사용하더라도 동기화가 안되는 것이 많았다)

문제 제기

- 지난시간(링크). 우리는 피자가게 스토리와 엮어서 Factory Method Pattern에 대해 알아보았다.

- KorPizzaStore, IndiaPizzaStore를 만들어 Pizza 객체를 생성하는 createPizza에 대한 메소드는 Localize 된 피자스토어에 맡겼다. (India에서는 피자를 손으로 찢어먹기때문에 cutting 하지 않는다는 상황가정때문)

- IndiaPizzaStore에서는 cheese를 파니르(채식) 치즈와 채식 도우와 닭고기 불고기 토핑을 쓴다고 한다.

- 이런 차이를 극복하기위해 Abstaract Factorty Pattern을 써서 KorPizzaStore와 다르게 재료생산해보자.

 

- 위와같이 피자마다 같은구성요소를 가지긴 하지만, 지역마다 다른 방식으로 구성요소를 구현한다. 또한 위의 3재료뿐만아니라 피자마다 추가로 새로운 토핑 및  향신료도 추가될 수 있을것이다.

 

Abstract Factory Pattern을 스토리에 적용시켜보자.

public interface PizzaIngredientFactory {
	public Dough createDough();
	public Cheese createCheese();
	public Boolgogi createBoolgogi();
	//이건 지역별로 함께 쓸 수 있다.
	public Veggies[] createVeggies();
}

- 먼저 추상화된 재료 Pizza 재료 공장을 만들고, 지역별로 공장을 만들어보자.

- 도우, 불고기, 치즈는 다르지만, 베지는 공통적으로 함께 쓸 수있다.

 

public class IndiaPizzaIngredientFactory implements PizzaIngredientFactory{

	@Override
	public Dough createDough() {
		return new VeggieDough();
	}

	@Override
	public Cheese createCheese() {
		return new VeggieCheese();
	}

	@Override
	public Boolgogi createBoolgogi() {
		return new ChickenBoolgogi();
	}

	@Override
	public Veggies[] createVeggies() {
		Veggies veggies[] = {new Garlic(), new Onion(), new Mushroom()};
		return veggies;
	}
}

 

public class KorIngredientPizzaFactory implements PizzaIngredientFactory{

	@Override
	public Dough createDough() {
		return new OriginalDough();
	}

	@Override
	public Cheese createCheese() {
		return new OriginalCheese();
	}

	@Override
	public Boolgogi createBoolgogi() {
		// TODO Auto-generated method stub
		return new BeefBoolgogi();
	}

	@Override
	public Veggies[] createVeggies() {
		Veggies veggies[] = {new Garlic(), new Onion(), new Mushroom()};
		return veggies;
	}

}

 

- 위와같은식으로 나라에따라 피자 ingredient Factory를 만들었다.

- 이제 재료를 생산할 준비가 끝났다. 피자에 따라 재료가 달라지니 한번 Pizza Class를 바꿔보도록 하자.

 

public abstract class Pizza {
	String name;
	Dough dough;
	Cheese cheese;
	Boolgogi boolgogi;
	Veggies veggies[];
	
	//원재료 생산공장을 달리하면서 추상화시켯다.
	public abstract void preare();

	public void bake() {
		System.out.println("굽는중.....");
	}
	
	public void cut() {
		System.out.println("자르는중.....");
	}
	
	public void box() {
		System.out.println("박싱중.....");
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

- prepare와 원재료들을 변수 이외에는 달라진 로직은 없다. 그러면 Boolgogipizza 클래스를 한번 만들어보자.

public class BoolGogiPizza extends Pizza {
	PizzaIngredientFactory pizzaIngredientFactory;
	
	public BoolGogiPizza(PizzaIngredientFactory pizzaIngredientFactory) {
		setName( " Korea Style Cutting Pizza");
		this.pizzaIngredientFactory = pizzaIngredientFactory;
	}

	@Override
	public void prepare() {
		System.out.println("재료준비중... " +name);
		dough = pizzaIngredientFactory.createDough();
		cheese = pizzaIngredientFactory.createCheese();
		boolgogi = pizzaIngredientFactory.createBoolgogi();
		
		System.out.println(dough.getClass().getSimpleName());
		System.out.println(cheese.getClass().getSimpleName());
		System.out.println(boolgogi.getClass().getSimpleName());
	}
}

- 전 포스팅에서는 KorKorBoolGoggiPizza, IndiaBoolGoggiPizza 등, 구체적으로 로컬별 피자객체를 만들었으나, 이제는 ingredient Factory만 달리 생성하면 되므로 이런식으로 BoolGogiPizza 객체를 만들었다.

-다만 이제 KorPizzaStore에서 피자객체를 생성할때 코드가 바뀌어야한다..

//바뀌기전
public class KorPizzaStore extends PizzaStore {

	@Override
	protected Pizza createPizza(String type) {
		Pizza pizza= null;
		
		if(type.equals("boolgogi")) {
	        pizza = new KorBoolGoggiPizza();
	    } 
		
		return pizza;
	}

}

 

//바뀐후
public class KorPizzaStore extends PizzaStore {

	@Override
	protected Pizza createPizza(String type) {
		Pizza pizza= null;
		PizzaIngredientFactory fac = new KorIngredientPizzaFactory();
		
		if(type.equals("boolgogi")) {
			pizza = new BoolGogiPizza(fac);
			pizza.setName("Korea Style Pizza From KOR ingredient Factory");
	    } 
		
		return pizza;
	}

}

 

- 바뀌기전에는 피자 자체를 store에서 생성해주었지만, 이제는 원재료를 로컬별 IngredientPizzaFactory에서 받아쓰기때문에, IngredientPizzaFactory 객체를 BoolGogiPizza 생성자에 넘겨주어 피자 재료를 달리생성하게된다.

- 전 포스팅한 메인함수를 그대로 실행시켜보니 내가 의도한대로 잘 나온다.

 

Abstract Factory Pattern 내용 정리

- 지역별 PizzaStore에 피자라는 Product가 종속되어 있지않고, Abstract된 Ingredient Pizza Factory에 따라 제품을 받아쓰기 때문에, 프로그램이 실행중에도, 같은 불고기피자라도 다른 재료를 적용 시킬 수 있다.

 

 

 

 

Abstarct Factory Pattern 정의

추상 팩토리 패턴
추상 팩토리 패턴에서는 인터페이스를 이용하여 서로 연관된, 또는 서로 의존하는 객체를 구상 클래스를 지정하지 않고도 생성 할 수있다.

- 추상 팩토리 패턴을 이용하면, 인터페이스를 통해 Product 들을 생성 할 수있다. 
(pizza = new BoolgogiPizza(korPizzaIngredientFactory); 처럼 말이다.)

- 따라서 클라이언트와 Product를 분리시킬수가 있다.

 

- 위와같은 클래스 다이어그램으로 Abstract Factorty Pattern을 표현할 수 있다.

 

- 실제 우리가 만든 피자스토어의 클래스다이어그램은 이렇게 만들 수 있다.

 

Abstract Factory Pattern Vs Factory Method Pattern

 

- Abstract Factory Pattern은 Factory Method Pattern에 비해 다소 복잡하다.

- 새로운 Product를 추가하려면 Factory Method Pattern는 Interface를 바꿔야 한다. (India boolgogi Pizza에서 Original Dough를 쓰고싶다면?? 코드자체가 바뀌어야함)

- Abstract Factory Pattern에서는 소스코드 자체를 추가하지않고, 구현체만 갈아끼우면 해결 할 수있다.

'To be Developer > DesignPatterns' 카테고리의 다른 글

6-1.Command Pattern (Simple)  (0) 2020.01.10
5.Singleton Pattern  (0) 2020.01.07
4-1.Factory Pattern (Factory Method Pattern)  (0) 2020.01.04
3.Decorator Pattern  (0) 2020.01.02
2.Observer Pattern  (0) 2020.01.01

문제 제기

 - "new"는 "구상객체" 를 뜻한다. 즉 new를 사용해서 인스턴스를 만든다는 것은, 특정 구현된 클래스의 인스턴스를 만든다는 것이다. new를 바탕으로 코딩을 하면 나중에 코드를 수정해야할 가능성이 높아진다. 예를들어보자

Beverage beverage = new DarkRoast()

 

- 전에 데코레이트 패턴에서 카페시나리오에서 만들었던, 음료에 관한 코드이다. 만약 카페인이 싫은 사람은 무조건 디카페인을 먹는다면?, DarkRoast의 진한맛을 싫어하는 사람이 있다면?

Beverage beverage;

  if(isNoCaf) {
  	beverage = new Decaf();
  } else if(isStrong) {
  	beverage = new DarkRoast();
  } else {
  	beverage = new HouseBlend();
  }

- 이런식으로 조건에따라. 몇 가지의 구상 클래스의 인스턴스가 만들어지는 코드를 써야할것이다.
  위와 같은 코드는 변경, 또는 확장이 필요할때 코드를 다시 지우거나 추가해야한다는 뜻이다. 휴먼오류를 일으킬 가능성과 OCP의 원칙을 지키기 어렵다.

- 사실 "new" 자체에는 문제가 없다. 그러나 변화를 원하는 고객과 요구사항이 항상 문제인것이다..... 그러면 우리는 어떻게 이러한 "new" 키워드의 OCP 문제를 해결할 수 있을까?

 

- 자, 우리가 피자가게를 차리는 시나리오를 생각해보자.

public Pizza orderPizza(String type) {
	Pizza pizza;
    
    if(type.equals("cheese")) {
    	pizza = new CheesePizz();
    } else if(type.equlas("pepperoni") {
    	pizza = new PepperoniPizz();
    } else if(type.equlas("boolgogi") {
    	pizza = new BoolgogiPizz();
    }

	pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    
    return pizza;
}

 

- 일단은 우리가 아까 고민했던, 위와같은 코드를 사용하여 보자.
치즈피자에 대한 단가가 안맞아, 치즈피자는 제외하고 야채피자(Veggie)를 새로 만든다고 해보자.

public Pizza orderPizza(String type) {
	Pizza pizza;
    
    //분기문에 피자 타입 코드변경에 관한 부분은 닫혀있지않다.
    //즉 메뉴를 변경하려면 직접 코드를 수정해야한다.
    if(type.equals("cheese")) {
    	pizza = new CheesePizz();
//    } else if(type.equals("pepperoni") {
//    	pizza = new PepperoniPizza();
    } else if(type.equals("boolgogi") {
    	pizza = new BoolgogiPizza();
    } else if(type.equals("veggi") {
    	pizza = new VeggiPizza();
    }
    
    
    //이부분은 바뀌지 않는다.
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    
    return pizza;
}

- 위와같이 변경되지 않는부분과, 변경되는 부분이 명확하게 나뉘었다. 지금 orderPizza 메소드에서 가장 문제가 되는부분은, 인스턴스를 만들 피자종류 클래스를 선택하는 부분인것이다(OCP에 위배).

- 객체지향 디자인원칙에 따라, 바뀌는 부분에 대하여 캡슐화를 진행해보자,

 

팩토리 패턴에 단계적 적용

1)Simple Pizza Facotory

- 진짜 팩토리 패턴에 들어가기전에 워밍업으로 Simple Factory 을 만들어봅시다

 

public class SimplePizzaFactory {
  public Pizza createPizaa(String type) {
    Pizza pizza= null;
    
    if(type.equals("cheese")) {
        pizza = new CheesePizz();
    } else if(type.equals("boolgogi")) {
        pizza = new BoolgogiPizza();
    } else if(type.equals("veggi")) {
        pizza = new VeggiPizza();
    }

    return pizza;
  }
}

 

public class PizzaStore {
  SimplePizzaFactory factory;

  public Pizza orderPizza(String type) {
    Pizza pizza;
	
    pizza = factory.createPizza(type);
    
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();

    return pizza;
  }
}

- 위와같은 형태의 SimpleFactory는 널리 쓰이기는하나, 진짜 팩토리패턴이라고 말할수 없다. 왜냐하면, 실행시에 동적으로 SimplePizzaFactory에 관한 객체구성을 바꿀 수 없기때문이다. 이는 위 코드를 "팩토리 패턴" 이라고 부르는 사람이 있으면, 귓속말로 그건 팩토리 패턴이 아니라고 알려주자.

 

2)Factory Method Pattern

- 위의 심플피자 팩토리가 문제가 되는 경우에 대해서 이야기해보자. 우리의 피자가게가 번성하여, 우리의 피자가게가 해외로 진출을 하게되었다. 근데 인도에서는 피자를 Cutting하지 않고 손으로 찢어먹는다고한다고 가정해보자. 이 경우에 PizzaStore와 SimpleFactory 객체와의 강력한 결합떄문에 이를 해결 할 수 없다. (근데 만약 팩토리 자체를 인터페이스로 다중화시킨다면??)

-결론적으로 우리의 사업의 확장으로 Pizza Instance를 만들때 한 객체에서 만들기 보다는, 팩토리 메소드를 추상화하여 일련의 서브클래스에서 처리하는 방식으로 바꿔보자

public abstract class PizzaStore {

  public Pizza orderPizza(String type) {
    Pizza pizza;
	
    //바뀐부분
    pizza = createPizza(type);
    
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();

    return pizza;
  }
  
  //
  protected abstract Pizza createPizza(String type);
}

 

- 위의 추상 PizzaStore 클래스로 여러가지 로컬 피자스토어 클래스를 만들 수 있게 되었다.

-이제 공장에서 Creator는 완성 되었으니, Product(Pizza)에 대해서 이야기 해보자,

public abstract class Pizza {
	String name;
	ArrayList<String> toppings;
	
	public void prepare() {
		System.out.println("준비중 " + name);
		for (int i = 0; i < toppings.size(); i++) {
			System.out.println("토핑추가 : "+ toppings.get(i));
		}
	}

	public void bake() {
		System.out.println("굽는중.....");
	}
	
	public void cut() {
		System.out.println("자르는중.....");
	}
	
	public void box() {
		System.out.println("박싱중.....");
	}
}

 

- nation 별로 다른 Product를 적용해보자.

public class KorBoolGoggiPizza extends Pizza {
	public KorBoolGoggiPizza() {
		this.name = "Korea Style Cutting Pizza";
	}
}

 

public class IndiaBoolGoggiPizza extends Pizza {
	
	public IndiaBoolGoggiPizza() {
		this.name = "India Style No cutting Pizza";
	}
	
	@Override
	public void cut() {
		System.out.println("인도에서는 피자를 컷팅하지 않아요");
	}
}

 

- 위와같은 abstract Pizza class로 인해, Localize한 피자를 생산할 수 있게 되었다.

 

- 실제로 다른 nation 별 피자를 생성해보자.

public class PizzaStoreMain {

	public static void main(String[] args) {
		PizzaStore korStore = new KorPizzaStore();
		PizzaStore indiaStore = new IndiaPizzaStore();
		
		Pizza pizza = korStore.orderPizza("boolgogi");
		System.out.println("완성" + pizza.getName() + "\n");
		
		pizza = indiaStore.orderPizza("boolgogi");
		System.out.println("완성" + pizza.getName() + "\n");
		
	}
}

 

결과

 

 

 

- 전체적인 다이어그램은 이런식으로 그려진다고 보면된다.

 

 

팩토리 메소드 패턴
 객체를 생성하기 위한 interface(또는 abstract method)를 정의하게 되는데, 어떤클래스의 인스턴스를 만들지는 서브클래스에 의해서 결정되게 된다.

- 위 정의에서, "결정한다" 라고 표현한 이유는, 이 패턴을 사용할 때 서브클래스에서 실행중에 어떤 클래스의 인스턴스를 만들지 결정하기 때문이 아니라, 생산자 클래스 자체가 실제 생산될 제품에대한 사전지식이 전혀 없이 만들어지기 때문이다.

- 저금더 풀어서 표현하자면, "사용하는 서브클래스에 따라 생산되는 개체 인스턴스가 결정된다" 이다.

 

 

팩토리 패턴을 몰랐을때로 돌아가볼까?

 

public class DependentPizzaStore {
	public Pizza createPizza(String nation, String type) {
		
		Pizza pizza = null;
		if (nation.equals("kor")) {
			if(type.equals("bollgogi")) {
				pizza = new KorBoolGoggiPizza();
			} else if (type.equals("cheese")) {
				pizza = new KorCheesePizza();
			} else if (type.equals("veggie")) {
				pizza = new KorVeggiePizza();
			}
		} else if (nation.equals("india")) {
			if(type.equals("bollgogi")) {
				pizza = new IndiaBoolGoggiPizza();
			} else if (type.equals("cheese")) {
				pizza = new IndiaCheesePizza();
			} else if (type.equals("veggie")) {
				pizza = new IndiaVeggiePizza();
			}
		}
		
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();

		return pizza
	}
}

 

- 나중에 local이 많아지면 많아질수록 더욱 힘들어질것이다. 왜냐하면, PizzaStore 코드또한 변경되기 때문이다. 의존성에 대하여 다이어그램으로 표현해보자.

- 이제는 구상 클래스에 의존성을 줄이는것이 좋다는 것을 확실히 알 수 있다. 이런내용의 디자인 원칙을 표현하는 말이 있다..
"의존성 뒤집기 원칙(Dependency Inversion Principle)" 이라고 한다.

디자인 원칙
추상화된 것에 의존하도록 만들어라, 구상클래스에 의존하도록 만들지 않도록 한다.

- 이 원칙에서는 고수준 구성요소저수준 고성요소에 의존하면 안된다는 의미가 내포되어 있다. 이게 무슨말일까?
- PizzaStore는 고수준 구성요소, Pizza class들은 저수준 구성요소 라고 할 수 있다.
- 고수준 구성요소는 다른 저수준 구성요소에 의해 정의되는 행동이 들어있는 구성요소를 뜻한다. 예를들어 PizzaStore의 메소드는 Pizza에 의해 정의되어진다. PizzaStore는 피자를 만들고 또한 피자를 준비,굽기,자르고,포장한다.

 

 

- 디자인 원칙을 적용해 우리는 이렇게 멋진 디자인 다이어그램을 만들어냈다.!!

 

- 피자를 Localize하다보니 또하나 문제가 생겼다. 같은 boolgogi 피자인데, 인도에서는 소고기를 먹지않아 치킨불고기 재료를 써야하고, 또한 피자치즈는 염소치즈를 쓴다고한다. 

- 위 문제를 추상 팩토리 패턴을 써서 해결하는법을 한번 알아보자.

'To be Developer > DesignPatterns' 카테고리의 다른 글

5.Singleton Pattern  (0) 2020.01.07
4-2 Factory Pattern (Abstract Factory Pattern)  (0) 2020.01.05
3.Decorator Pattern  (0) 2020.01.02
2.Observer Pattern  (0) 2020.01.01
1.Strategy Pattern  (0) 2019.12.31

문제 제기

- 우리가 스타** 커피집을 차렸다고 생각해보자.

 

-장사 초기에는 POS에서 구현된  음료객체는 많지않았고, 위와같이 아주 심플했다.

 

-그러나 스팀 우유나, 두유, 초콜릿을 추가하고 그위에 휘핑크림을 얹기도 하면서 굉장히 많은 메뉴들이 생겨나기 시작했다.

-주문시스템에서도 이러한 메뉴들을 반영하기 시작했고 초기에는 무조건 상속받아 구현하였다.

- 앞으로 녹차가 추가될수도있고 많은 토핑들이 추가될수도있다. 그러면 이렇게 클래스가 늘어나는 것을 지켜보고만 있어야할까?

- 이 스토리에서는 앞서 배운 두가지 디자인패턴 원칙을 따르지 않고있다.

 

- 그러면 폭팔적으로 늘어나는 음료 class를 막기위해 다음과같이 super 클래스를 정의해보면 어떨까?

-각 cost() 에서는 일단 음료의 가격을 구한다음, 첨가된 항목에따라 추가 가격을 더하면 된다.

-그러나 여기서 야기될 수 있는 문제점이 몇가지 보인다.
 1) 첨가물 가격이 바뀔때마다 기존코드 수정.
 2) 첨가물의 종류가 추가되면 setter메소드 및 has 메소드 추가
 3) 새로운 음료가 출시됐는데 거기에 기존 첨가물이 안들어가도 오버라이드 되어야함. (녹차에 whip 등)
 4) 손님이 더블 휩을 주문한다면? boolean에서 int로 설계해야 할까?

- 서브클래스를 만드는 방식으로 행동을 상속받게되면, 그 행동은 컴파일시에 완전히 결정되어진다.
-> 반면 구성을 통해서 객체의 행동을 확장하면 실행중에 동적으로 행동을 설정할 수 있다.
 또한, 기존의 코드는 건드리지않기 때문에, 기존코드에서 버그가 생기거나 의도하지 않은 부작용이 발생하는 것을 원천 봉쇄할 수 있다. 

디자인 원칙
클래스는 확장에 대해서는 열려 있어야 하지만, 코드변경에는 닫혀있어야 한다.

- 이 원칙을 준수하기 위해서는, 바뀔 확률이 높은 부분을 중점적으로 살펴보고, 원칙을 적용하는 방법이 가장 현명하다.
(무조건 OCP(Open Closed Principale)를 적용하는 것은 시간낭비가 될 수도있고, 쓸데없이 일을 크게 벌일 수도 있으니 유의하자)

 

데코레이터 패턴의 개념 추상화

-상속을 써서 음료의 가격과 첨가물(휘핑,우유,모카)의 총 가격을 계산하는 것은 그닥 좋지 않았다. 음료를 첨가물로 Decroeate 해보면 어떨까?
 1) DarkRoast 객체를 가져온다.
 2) Mocha 객체로 장식한다.
 3) Whip 객체로 장식한다.
 4) cost() 메소드를 호출한다. 이때 첨가물의 가겨을 계산하는 일은 해당 객체들에 위임된다.

 

-위 로직을 그림으로 표현해보자.

1) DarkRoast 객체를 가져온다.

 

 

 2) Mocha 객체로 장식한다.

 


 3) Whip 객체로 장식한다.

 

 


 4) cost() 메소드를 호출한다. 이때 첨가물의 가겨을 계산하는 일은 해당 객체들에 위임된다.

 

데코레이터 패턴의 정의

데코레이터 패턴에서는 객체에 추가적인 요건을 동적으로 첨가한다. 데코레이터는 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있다.

- 데코레이터 패턴의 추상화된 클래스 다이어그램부터 살펴보자.

- 조금은 복잡하지만, 원래 Super 클래스를 상속받아, 구체화 시킨 class를 Decorator의 내부 인스턴스로 가지고있다는 것 밖에 없다.

 

드디어 우리 카페에 Decorator 패턴을 적용시켜보자.

 

 

- 자 이제 코드로 구현해보자, 추상 클래스들(음료, 데코레이터)를 구현해보자

public abstract class Beverage {
	String description = "제목 없음";
	
	public String getDescription() {
		return description;
	}
	
	public abstract int cost();

}

 

//Beverage 객체가 들어갈 자리에 있어야하므로, 베버리지 상속
public abstract class ToppingDecorator extends Beverage {
	//모든 토핑은 description을 구현하도록 만들예정임
	public abstract String getDescription();
}

 

- 다음은 구현체들을 만들어보자.

public class DarkRoast extends Beverage{

	public DarkRoast() {
		this.description = "다크로스트";
	}
	
	@Override
	public int cost() {
		return 3000;
	}
}

 

- 아래부터는 토핑 데코레이터의 구현체이다.

public class Mocha extends ToppingDecorator{
	
	Beverage beverage;
	
	public Mocha(Beverage beverage) {
		this.beverage = beverage;
	}
	
	@Override
	public String getDescription() {
		return beverage.getDescription() + ", 추가 모카";
	}

	@Override
	public int cost() {
		return 500 + this.beverage.cost();
	}

}

 

public class Whip extends ToppingDecorator{

	Beverage beverage;
	
	public Whip(Beverage beverage) {
		this.beverage = beverage;
	}
	
	@Override
	public String getDescription() {
		return beverage.getDescription()  + ", 추가 휘핑";
	}

	@Override
	public int cost() {
		return 500 + beverage.cost();
	}

}

 

 

- 이제 데코레이터들을 통해 음료를 wrapping 하는법을 알아보자.

public class CafeMain {

	public static void main(String[] args) {
		Beverage beverage = new DarkRoast();
		
		System.out.println(beverage.getDescription() + " 가격은: " + beverage.cost());
		
		//모카 추가
		beverage = new Mocha(beverage);
		System.out.println(beverage.getDescription() + " 가격은: " + beverage.cost());
		
		//휘핑 추가
		beverage = new Whip(beverage);
		System.out.println(beverage.getDescription()  + " 가격은: " + beverage.cost());
		
	}

}

 

-결과는 우리가 바라던 대로 잘 나온다.

'To be Developer > DesignPatterns' 카테고리의 다른 글

5.Singleton Pattern  (0) 2020.01.07
4-2 Factory Pattern (Abstract Factory Pattern)  (0) 2020.01.05
4-1.Factory Pattern (Factory Method Pattern)  (0) 2020.01.04
2.Observer Pattern  (0) 2020.01.01
1.Strategy Pattern  (0) 2019.12.31

문제제기

 

- 위처럼 기상 모니터링 애플리케이션을 만든다고 가정해보자.
- WeatherData 자체는 기상측정소에서 가져온다.
- 우리는 어플리케이션에서 총 3가지 어플리케이션을 구현해야한다
 1)번 디스플레이에는 기상통계를 보여준다.(평균, 최고 최저기온)
 2)번 디스플레이에는 현재 조건을 보여준다. (기온, 습도, 기압)
 3)번 디스플레이에는 기상 예보를 보여준다(기온)
- 우리는 위에 조건을 가진 디스플레이를 어떻게 확장가능하게 보여줄 수 있을까?

- 다음과 같이 프로그래밍을 한다고 가정해보자

public class WeatherData {
	public void measurementsChange() {
    	float temp = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();
        
        display1.update(temp, humidity, pressure);
        display2.update(temp, humidity, pressure);
        display3.update(temp, humidity, pressure);
    }
	//....
}

 

- 위처럼 만들수는 있겠지만, 무엇인가 유연하지 않다. 그리고 전에 배웠던 디자인패턴의 원칙과는 동떨어져보인다

 

- 코드에서 전에 배웠던 디자인 패턴 원칙에 따라 구분지어보니, 바뀔수 있는 부분과, 바뀌지 않는 부분을 나눠보았고. 공통된 update라는 기능을 추출해보았다.

 

 

Observer 패턴에 대한 개요

- 위 기상모니터링 애플리케이션 코드를 리팩토링하기전에 Observer 패턴에 대해서 먼저 알아보자,

- 위 그림처럼, Subject 객체가 데이터를 전달하는 주체이고, Observer 객체들은 그 주제들을 전달받는 객체인것만 알면 Observer 패턴에대해서 모두 이해한 것이다. 그리고 Observer들은 언제든지 구독과 구독취소를 실행할 수 있다.

- 또한 옵저버 패턴은 Subject와 Observer사이에 일대다(one-to-many) 의존성을 갖는다.

- 위 그림에서 Subject 객체와 Observer 객체는 Loose Coupling 하다고 불수있다.
 1) Subject 객체가 옵저버에 대해 아는 것은 옵저버가 특정 인터페이스만 구현한 것만 알 수있다.
 2) 옵저버는 언제든지 추가, 삭제 될수있다.
 3) 새로운 형식의 옵저버를 추가하려고 할때, Subject를 전혀 변경할 필요가 없다.
 4) Subject와 Observer는 독립적으로 재사용 될 수있다.
 5) 주제와 옵저버의 코드가 변경되더라도 Code에 대한 Side effect가 없다.

디자인 원칙
서로 상호작용을 하는 객체 사이에서는 가능하면 느슨한 결합을 하는 디자인을 사용해야한다.

 

 

Observer 패턴을 기상 어플리케이션에 적용

-위와 같은 형태로 클래스 다이어그램을 만들 수 있을것이다.

 

public class WeatherData implements Subject{
	
	private ArrayList<Observer> observers;
	private float humidity;
	private float temperature;
	private float pressure;
	private Random random;
	
	
	public WeatherData() {
		observers = new ArrayList<Observer>();
	}

	@Override
	public void notifyObserver() {
		observers.stream().forEach(o -> o.update(temperature, humidity, pressure));
	}
    
    // ... omit
}

-WeatherData 객체에서 notify 하는 코드에 대해서만 보자면 위와같이 프로그래밍 할 수 있을것이다.

'To be Developer > DesignPatterns' 카테고리의 다른 글

5.Singleton Pattern  (0) 2020.01.07
4-2 Factory Pattern (Abstract Factory Pattern)  (0) 2020.01.05
4-1.Factory Pattern (Factory Method Pattern)  (0) 2020.01.04
3.Decorator Pattern  (0) 2020.01.02
1.Strategy Pattern  (0) 2019.12.31

문제제기

- 우리가 기존의 객체지향 프로그래밍을 활용하여 게임을 만든다고 가정해보자.
Person 이라는 Super Class를 사용하여 다음과같이 여러 직업군의 캐릭터를 정의할 수 있을 것이다.

 

- 만약 우리가 attack 이라는 method를 구현하고자 한다. 그러나 여기서 야기될 수 있는 문제점이 여러개가 보인다.
 1) 만약 Person을 상속받은 허수아비 가있다면 attack을 할 수 없어야한다. (그렇다면 그냥 override 해버리면 되잖아?)
 2) attack은 override를 해서 해결했지만, 만약에 추가적으로 sound 같은 행위를 구현해야한다면? interface가 계속늘어가고, 계속해서 override를 통해 재정의 해야할 것이다.

 

- 그렇다면 차라리 Interface를 implements 하면 어떻게 될까?
 이런식으로 만든다면, 물론 일부만 attack을 할 수 있고, 일부는 안하게 선택적으로 행위를 정의 할 수 있지만, Attack 행동에 대한 코드재사용성은 전혀 기대할 수 없다(클래스별로 공격 행위에 대한 코드를 작성해야 하며, 새로운 클래스가 생겼을때나, 클래스의 공격을 패치를 통해 변동시키고 싶다면??). 그리고 별개로 새로운 행동이 추가되면 될수록 재사용성은 전혀 기대할 수 없다.

 

문제를 명확하게 구체화

- attackAble 인터페이스 사용은 처음에는 괜찮아 보이지만, 자바의 인터페이스는 코드가 전혀 들어가있지 않기 때문에, 재사용성은 꽝이다.
- 즉 attack 이외의 한 행동이 추가될때마다, 그 interface를 구현한 obejct를 찾아가 일일히 코딩해야 한다는 것이다.
- 여기서 찾아낼 수 있는 디자인원칙이 있다.

디자인 원칙1
어플리케이션에서 달라지는 부분을 찾아 달라지지 않는 부분으로부터 분리시킨다

 

디자인 패턴 원리 적용


 - 위 다이어그램에서는 attack, run, drink 등의 행동이, 바뀌는 부분 일 것이다. 그러나 글쓴이의 편의를 위해, attack의 예시만 한번 확인 해보겠다.

- attack에 대한 class 집합은 어떻게 디자인 할 수 있을까?
 우선 최대한 유연하게 만들어야한다. 또한 Person의 인스턴스에 attack을 할당할 수 있어야한다. 즉, Person의 클래스에 Setter를 만들어서 프로그램의 실행중에도 동적으로 할당할수 있는게 가장 좋다.
여기서 디자인원칙 2번째를 알 수있다.

디자인 원칙2
구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다.

 

 

- 위와 같이 attackBehavior 라는 interface를 구체화하여 공격을 나타내는 클래스를 만들었다.
 (물론 객체지향 패턴에서 클래스는 변수와 메소드를 둘다가지고 있어야한다. 행동만 나타내는 클래스는 이상할 수 있다. 그러나 더 구체화 시키다보면, 클래스별 axe 시너지등을 계산할때 getter setter등을 통해 내부 멤버변수를 다룰 수도 있을것이다.)

 

 

- 위에서 만든 캡슐화된 공격 행동을 Person과 합쳤다.
 기존 객체지향 프로그래밍의 "A는 B이다" 라는 관계가 아니라 "A에는 B가있다" 와 같이 합치는 것을 구성(composition) 이라고 한다. 이 테크닉은 매우 중요하며 디자인의 3번째 원칙이다

디자인 원칙3
상속보다는 구성을 활용한다.

 

 위 패턴이 바로 Starategy Pattern 이다. 즉 각각을 캡슐화 하여, 교환해서 사용할 수 있도록 만든다. Starategy Pattern을 적용한다면, Object를 사용하는 클라이언트와는 독립적으로 Obejct의 캡슐화된 행위등을 변경할 수 있다.

 

 

위 글은 Head first design Patterns책의 내용을 기초로 작성되었습니다.

'To be Developer > DesignPatterns' 카테고리의 다른 글

5.Singleton Pattern  (0) 2020.01.07
4-2 Factory Pattern (Abstract Factory Pattern)  (0) 2020.01.05
4-1.Factory Pattern (Factory Method Pattern)  (0) 2020.01.04
3.Decorator Pattern  (0) 2020.01.02
2.Observer Pattern  (0) 2020.01.01

3-way handshake 

 3-way handshake란 TCP/IP 에서 호스트와 서버가 Connection 을 맺을때 수행되는 전송제어 프로토콜(Transmission Control Protocol) 이다.  (SYN-SYN-ACK 라고도 한다.)

순서는 다음과 같다. Host A가 server, B는 client라고 가정하자.

1. HOST A는 B에게 TCP SYNchronize packet을 보낸다
2. B는 A에게  SYN 패킷을 받는다. 이후 SYNchronize-ACKnowledgement 패킷을 보낸다.
3. A는 B의 SYN-ACK를 수신후 다시 A에게 ACKnowledge 패킷을 보낸다.
4. B가 ACK를 수신하고 TCP 소켓이 ESTABLISHED 된다.

 

 위 그림에서 SYN 패킷을 보낼때, M과 N이 보이는데 이는 Sequence Number 라 한다, 여기에 랜덤한 숫자가 담겨진다. 초기 Sequence Number를 ISN이라 하는데, 이는 0이 아닌 랜덤한 숫자가 보내진다. 
 

왜 ISN에는 랜덤한 수가 담겨질까?

 https://www.quora.com/Why-in-a-TCP-sequence-is-a-number-taken-as-a-random-number-and-what-is-the-actual-number-at-the-start?awc=15748_1574583388_8da30461914e738de7495e39d9ab34b1&uiv=6&txtv=8&source=awin&medium=ad&campaign=uad_mkt_en_acq_us_awin&set=awin&pub_id=85386

 

Why in a TCP sequence, is a number taken as a random number and what is the actual number at the start?

Answer: There are two underlying reasons for the use of random sequence number Why they came into existence? The port numbers in TCP connections come from a finite range and, as such, are reused over time. As such, it is possible that two communicating hos

www.quora.com

 

단순히 응답을 주고받는데 2-way Handshake면 충분해보이지 않는가? 왜 3-way 일까?

 TCP/IP 통신은 양방향성 connection 이다. A host가 B host에게 존재를 알리고 패킷을 받을수있다는 것을 증명하듯이, B host도 패킷을 보낼수 있다는 신호를 보내야한다. 이는 2-way handshaked에서는 성립될 수 없다.

 

4-way handshake

 TCP연결이 해제 될때는 FIN 패킷, ACK 패킷을 각각 한번 씩 주고 받으면서 연결을 종료 하게 된다. 이를 4 way handshake라 한다


ESTABLISHED 된 소켓을사용하는 A Host와 B Host가 있다. A HOST는 이제 연결을 종료하려고한다.
 1) A host는 B호스트에게 FIN Flag 패킷을 보내고 socket의 상태가 FIN_WAIT_1 이 된다.
 2) FIN 을 수신한 B는 ACK를 전송하고 socket의 상태가 CLOSE_WAIT로 변경한다.
 3) ACK를 수신한 A는 B가 잘받았다고 판단하고 FIN_WAIT_2 의 상태로 소켓을 변경한다.
 4) B는 A에게 FIN을 전송하고 소켓을 LAST_ACK로 변경한다.
 5) FIN을 수신한 A는 B에게 ACK를 전송한뒤 상태를 TIME_WAIT 상태로 변경한다.
 6) B는 ACK를 수신하고 소켓을 CLOSED 한다.

 

CLOSE_WAIT 와 TIME_WAIT 상태란 무엇일까?

 TIME_WAIT 상태로 대기하는(60s) 이유는, 세션 종료후, 혹시나 네트워크에 아직 라이브 패킷이 존재할수도 있기때문이다.

process와 thread의 차이점?

 

 

Garbage Collector

 가비지컬렉터란 JVM에서 실행되는 프로그램으로, Application 내부에서 사용되지 않는 객체를 제거한다. 이게무슨말일까?? 다음소스를 한번 봐보자.

for (File f : files) {
    String s = f.getName();
}

 이 소스코드는 files의 길이에 따라 달라지겠지만, files의 length가 2개 이상일경우, 계속해서 String 객체를 생성한다. 이는 계속해서 메모리를 String객체를 만들기위해 할당하고있다.

 그러나 2번째 반복에서는 첫번째 만들어진 String s 객체를 사용하지않는다. 이것을 "Garbage" 라고 간주한다. 이런 Garbage가 쌓이고 쌓이게 된다면, 새로운 객체를 만들 공간이 부족해질 것이다. 이를 위해 Garbage Collector가 있는 것이다.

any live threads or by any static references 에서 도달할수 없는 Object의 경우, Garabage Collection 의 적격의 대상이다.

 즉 개체의 모든참조가 null일경우 Garbage Collection의 대상이 될수있다.
 또한 A -> B 의 의존성이있고, B -> A의 의존성이있지만, A와 B가 다른 reference가 없을경우 A와 B 모두 수거대상이된다.

 

Heap Generations for Garbage Collection

 자바의 Object는 Heap에서 생성되며,힙은 GC를 위해 3개의 parts로 나뉘어져있다. 각각 Young(New) generationTenured(Old) Generation and Perm Area 이다.

 Young Generation은 더 나아가, Eden Space, Survivor1과 Survivor2로 나뉜다. Object가 맨처음 생성되었을때, 그것은 Eden공간 내부에서 생성되고, 그 후 Minor Garbage Collection후 살아남은 객체는 S0으로 이동하고, 또 Minor GC가 있은후 S1으로 이동된다. 만약 Major garbage collection 전에 s0,s1 영역에 계속 살아남았다면, 이것은 Old(tenured) 영역으로 이동하게된다.

 또한 Heap의 Perm 공간에는 메서드, Classes와 String Pool 및 Class level detail 에대한 Metadata를 저장하는곳이다.

 

Major GC가 Minor GC보다 느린이유.

- 예를들어가정해보자 15개의 objects 들을 s1 영역으로 이동시키고, 다음 gc에서는 s2로 이동시킨다. 또 다음 GC는 survivor를 s1 또는 s2로 이동시킬것이다. 이렇게 되면 계속해서 살아남은 생존자들은 old generation으로 이동하게 될것이다.

- Major GC는 old generation에 free space가 없어 더이상 survive한 객체를 옮길수 없을때에 발생한다. old generation에 모든 object에 대해 gc가 수행된다. old generation은 보통 young generation보다 많은 object를 담고있기때문에, GC처리가 훨씬 오래걸린다. 

 

 

what is? 

minor gc: 모든 새로운객체는 Young Generation에 할당되는데, Young Generation이 꽉차게되면 이는 minor GC를 일으킴.
major gc: Young generation에 있는 객체에 임계값을 설정하고, 그 임계값이 넘게되면 Old gerneration으로 이동되게된다. 그리고 쌓이고 쌓이게되다보면, Old Generation영역도 GC가 발동되게 되는데 이를 Major GC라고한다. 종종 live Object도 수집대상이 되기때문에, 훨씬 더 느리다.
full gc: Perm 영역에는 클래스 및 메소드가 사용되는데 설명하는 metadata들이 포함되어있다. 사용중인 클래스를 기준으로 JVM에 의해 채워진다. 또한 java SE 클래스또한 이곳에 저장될수있다. JVM이 기존것들이 더이상 필요하지않고, 다른클래스를 위한 공간이 필요하다고 여겨질때 perm 영역가지 포함되어 gc가 일어나게되는데 이를 full gc라고한다.
String Pool:
method,classes, class level metadata

 

!!!주의: JDK 8에서는 Perm Gen이 Heap에서 삭제되고, Native 영역에 MetaData Space가 생겼다.

Java 7의 JVM 구조

<----- Java Heap ----->             <--- Native Memory --->
+------+----+----+-----+-----------+--------+--------------+
| Eden | S0 | S1 | Old | Permanent | C Heap | Thread Stack |
+------+----+----+-----+-----------+--------+--------------+
                        <--------->
                       Permanent Heap
S0: Survivor 0
S1: Survivor 1


JAVA 8의 JVM 구조

<----- Java Heap -----> <--------- Native Memory --------->
+------+----+----+-----+-----------+--------+--------------+
| Eden | S0 | S1 | Old | Metaspace | C Heap | Thread Stack |
+------+----+----+-----+-----------+--------+--------------+

(Heap 영역은 JVM에 의해 관리된 영역이며, Native 메모리는 OS 레벨에서 관리하는 영역으로 구분된다)

Perm 영역은 보통 Class의 Meta 정보나 Method의 Meta 정보, Static 변수와 상수 정보들이 저장되는 공간으로 흔히 메타데이터 저장 영역이라고도 한다. 이 영역은 Java 8 부터는 Native 영역으로 이동하여 Metaspace 영역으로 변경되었다. (다만, 기존 Perm 영역에 존재하던 Static Object는 Heap 영역으로 옮겨져서 GC의 대상이 최대한 될 수 있도록 하였다)

 

왜 Perm이 제거됐고 Metaspace 영역이 추가된 것인가?

최근 Java 8에서 JVM 메모리 구조적인 개선 사항으로 Perm 영역이 Metaspace 영역으로 전환되고 기존 Perm 영역은 사라지게 되었다. Metaspace 영역은 Heap이 아닌 Native 메모리 영역으로 취급하게 된다. (Heap 영역은 JVM에 의해 관리된 영역이며, Native 메모리는 OS 레벨에서 관리하는 영역으로 구분된다) Metaspace가 Native 메모리를 이용함으로서 개발자는 영역 확보의 상한을 크게 의식할 필요가 없어지게 되었다.

 

JAVA7과 JAVA8의 변동사항.

  JAVA 7 JAVA8
Class 메타 데이터 저장 저장
Method 메타 데이터 저장 저장
Static Object 변수, 상수 저장 Heap 영역으로 이동
메모리 튜닝 Heap, Perm 영역 튜닝 Heap 튜닝, Native 영역은 OS가 동적 조정
메모리 옵션 -XX:PermSize
-XX:MaxPermSize
-XX:MetaspaceSize
-XX:MaxMetaspaceSize

 

Perm 영역에 있던 것들은 어디로?

 - Mehtods of a class -> Native 영역으로
 - Names of the clases -> Native 역역으로
 - Constant pool information (String 포함) -> Heap 영역으로
 - Static Variable -> Heap 영역으로

 

JDK8에 관한내용 출처:https://johngrib.github.io/wiki/java8-why-permgen-removed/

 

전체출처: https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

 

Java Garbage Collection Basics

Java Overview Java is a programming language and computing platform first released by Sun Microsystems in 1995. It is the underlying technology that powers Java programs including utilities, games, and business applications. Java runs on more than 850 mill

www.oracle.com

https://stackoverflow.com/questions/24592834/why-major-garbage-collection-is-slower-than-minor

 

Why Major Garbage collection is slower than Minor?

Gone thru this link but still has confusion what actually happens in minor and major GC collection. Say i have 100 objects in younger generation out of which 85 object are unreachabe objects. Now ...

stackoverflow.com

JDK 8 로 넘어오면서 레가시 시스템의 성능저하를 MaxMetaspaceSize를 바꿔서 튜닝사례

https://brunch.co.kr/@heracul/1

 

JDK8 적용 후, 심각한 성능저하가 발생한다면?

Full GC가 제대로 되지 않는다면?! | 17년 9월 드디어 JDK9이 release되었습니다. 14년 3월에 JDK8이 출시된 후로 무려 3년6개월만입니다. 작년 Java One에서 Jigsaw프로젝트 리뷰하면서 올 연초에 나온다고 했었는데...계속 연기되었었죠 ^^; 뭐 어찌됐건 JDK9이 release된 마당에 아직도 JDK7을 쓰고 있다고 자책하면서 JDK8으로 업그레이드를 했다면, 그리

brunch.co.kr

 

제1정규화
 제1정규형의 정의는 테이블 셀에 복합적인값을 포함하지않는다 (스칼라값만을 갖음), 그러나 배열을 insert 할 수 없기때문에 제 1정규형은 자동으로만족한다.

제2정규화 (2NF)
 부분함수의 종속성 = 기본키를 구성하는 열의 일부에만 종속성을 갖음 (복합키일경우) 예를들어 table 회사별 주문에 회사id+주문번호가 키값이라 쳐보자 
 create table 회사별 주문 {
  회사id ,
  주문번호,
  주문일자,
  회사이름,
  고객기업규모,
  PRIMARY KEY(회사id,주문번호)

 위 테이블은 제2정규형을 만족하지 못함, 회사id에만 종속되는게 존재, 회사이름 및 고객기업규모 만 종속됨. 이게 왜 위험하냐면, 주문을 했을때 주문을 한 정보만으로는 고객기업규모를 알아낼수 없음, 이럴경우 NULL을 허가하거나 더미값추가? => 이런 갱신시 부정합을 갱신이상이라함.

제3정규화 (3NF)
 추이함수의 종속이 없어야한다. 다음과같은 테이블이 있다고 가정하자. 
 create table 회사 {
 회사ID,
 회사명,
 규모,
 업계코드,
 업계명,
 PRIMARY KEY (회사ID)

 2NF까진 만족하는듯 하다. 그러나 추이함수의 종속성을 갖는다. (2단계의 함수종속성)  
현재 (회사ID->업계코드->업계명) 이런식으로 종속되어있다. 이럴경우 새로운 업계를 추가할 수 있을까? 업계코드에 따른 업계명은 다른 테이블을 빼야한다.

 

 

지난글 에서는 Transaction이 무엇인지에 대해서 한번 살펴보았다.
이번에는 지난번에 설명했던 ANSI에서 정의한 Transaction의 Isolation level 4단계에대해서 알아보자.

 

격리레벨 4단계를 이해하기위해 일단 상황을 가정해보자. 
 Table USER는 id와 name column이 있다.
 그리고 동시에 실행되는 processes a, b, c 가 있다고 가정해보자 a는 트렌젝션을 시작하고, 이것은 data를 insert하는데 commit될수도 있고, 커밋이 안될 수 도 있다. b는 SELECT 쿼리를 2번 사용하는 트랜잭션이다. c는 data를 select하고 update 한다. 이 모든 프로세스는 USER Table에서 실행된다고 가정하자.

 

READ UNCOMMITTED 
* b = (select * from user);
 이 레벨은 table에 락을 걸지 않는다. 이 말인즉슨 A가 USER table에 {id:user10,name:홍길동}을 insert하는 도중에 (권한 처리를 위해  아직 uncomitted되었다.)  B가 SELECT를 한다면 이 Uncommitted 되는 {id:user10,name:홍길동}을 본다는  이야기이다.(Dirty Read) 심지어 a가 rollback 되었음에도 B의 SELECT 결과 에는 사용된다. 이는 가장 빠르긴하지만, 안전하지않다. 

 

READ COMMITTED
 * b = (select * from user where id='user10');
 이 레벨에서는 커밋된 데이터에 락을건다. 커밋된 데이터만 읽을수 있지만, c가 만약 트랜젝션중간에 update를 하고 commit을 하면, b가 1회에 select한 데이터가 2회에는 달라질수가 있다(Non repeatable read). 예를들어보자. b 트랜젝션에서 {id:user10, name:홍길동} 데이터를 1회 읽었다. 그런데 c transaction에서 user10의 이름을 '김길동'이라고 바꿨다. 다시 b에서 2회 select를 실행했을땐 {id:user10, name:홍길동} 을 읽게된다.

REPEATABLE READ
 * b = (select * from user where name='홍길동');
 이 레벨에서는 block of SQL의 lock을 건다. 만약 현재 USER TABLE에 {user:aaa,name:홍길동}, {user:bbb,name:홍길동}이 있다고 가정하자. 그런데 a 트랜잭션에서 도중에 {user:ccc, name:홍길동}을 넣고 commit 했다. 그리고나서 b가 2회째 select를 수행하게되면 1회째와 달리 총 3개의 행을 select 하게된다.(Phantom read)

 

SERIALIZABLE
 이 레벨은 전체 테이블을 잠근다(위의 예제는 b가 select를 2번 실행할동안 table 전체를 lock 한다.) 이것은 가장 안전하지만, 비효율적이고 가장 느린데이터 작업방법이다. 

 


위의 내용들 참고하여 transaction isolation level에 따른 나타날수 있는 현상을 정리하자면 다음과 같다.

  Dirty Read Non repeatable read Phantom read
READ UNCOMMITTED 
Permitted Permitted Permitted
READ COMMITTED
x Permitted Permitted
REPEATABLE READ
x x Permitted
SERIALIZABLE
x x x

 

출처
https://stackoverflow.com/questions/16162357/transaction-isolation-levels-relation-with-locks-on-table

https://stackoverflow.com/questions/11043712/what-is-the-difference-between-non-repeatable-read-and-phantom-read

+ Recent posts