본문 바로가기
Develop/Java

[Java] static과 synchronized

by 연로그 2021. 5. 26.
반응형

흔히 static 키워드는 multi-thread 환경에서 문제가 생기고,

multi-thread 접근을 방지하기 위한 방법으로는 synchronized의 사용이 있다.

static만 쓰거나, synchronized만 쓰거나, 둘 다 쓰거나... 이런 경우가 헷갈려서 따로 정리해본다.

 


static

  • 메모리에 한 번 할당되며 프로그램 종료 시 해제
  • GC과 관여하지 않는 영역의 메모리에 저장됨
  • 객체 지향적이지 않아서 Java에서는 사용 지양을 권장
  • interface 구현에 사용할 수 없어 재사용성이 떨어짐
  • multi-thread 환경에서도 공유됨
  • 설정 파일 정보, 자주 사용하고 절대 변하지 않는 변수(이건 final static을 쓰면 좋다) 등에 사용

 

메모리에 대한 보충 설명 ▼

더보기
img: https://mangkyu.tistory.com/47

 

JVM의 메모리 영역은 크게 Static, Stack, Heap 영역으로 나눈다.

간단히 설명하기 위해 Static과 Heap 영역에 대해서만 설명한다.

 

일반적으로 우리가 생성한 Class는 Static 영역에,

new 연산을 통해 생성한 객체들은 Heap 영역에 생성된다.

 

Static 영역은 프로그램 종료 시까지 메모리가 할당된 채로 존재하고,

Heap 영역은 Garbage Collector를 통해 수시로 관리를 받는다.

 

-> static을 너무 남발하게 되면 시스템에 악영향을 준다.

 

생각해볼 문제 #1

메소드가 10000번을 호출된다면 static을 통해 미리 메모리를 할당된 채로 두는게 좋지 않나? 라는 생각을 할 수도 있다.

하지만 static을 선언하면 메모리가 계속 할당된 채로 존재하고,

static을 선언하지 않으면 10000번의 호출 후에 인스턴스가 해제되므로 메모리 상으로는 절약된다.

 

생각해볼 문제 #2

static 메소드에서 접근하기 위한 변수는 반드시 static이어야 한다.

다음의 예제를 살펴보자.

name1을 출력하는 부분을 살펴보면 " Cannot make a static reference to the non-static field name1 " 에러가 난다.

non-static 필드의 변수를 static의 참조로 사용할 수 없다는 의미인데 그 이유는 위 메모리 영역에 대한 이미지를 다시 살펴보면 알 수 있다.

name2와 print() 메소드는 static 영역에 존재하고, name1은 heap 영역에 존재한다. 

print() 메소드는 객체 생성 없이 접근 가능한데 name1은 객체가 생성된 후에야 메모리가 할당되므로 에러가 생긴다.

 


스레드 동기화 문제

  • multi-thread는 잘 사용하면 좋은 성능을 내지만, 스레드 간 동기화 문제를 해결해야 한다.
  • 동기화 문제는 보통 heap 영역에서 발생

+ synchronization(동기화): 프로세스 또는 스레드들이 수행되는 시점을 조절해 서로 알고 있는 정보를 일치시키는 것

+ java thread는 thread 안에 stack 영역을 포함하기 때문에 문제되는건 heap 영역뿐이다.

 

추가로, thread와 process의 차이를 뭔지 모른다면 개인적으로 공부하길 바란다.

하나의 프로세스에는 여러 개의 스레드가 생성되며, 

스레드는 프로세스에서 만들어 사용하고 있는 메모리를 공유한다.

 


synchronized

  • 스레드 동기화 문제를 해결해 thread-safe를 지원하는 키워드
  • 여러 스레드가 하나의 공유 자원에 접근할 때, 현재 데이터를 사용하는 스레드를 제외한 스레드들은 데이터에 접근하지 못하게 함
  • 무분별한 동기화는 프로그램의 성능 저하를 일으킴

+ race condition(경쟁 상태): 둘 이상의 프로세스 또는 스레드가 동기화 없이 접근하려는 현상

+ critical section(임계 영역): 공유되는 자원에서 문제가 발생하지 않도록 독점을 보장해주는 영역

 

사용하는 경우

  • 하나의 객체를 여러 스레드에서 동시에 사용할 경우
  • static으로 선언한 객체를 여러 스레드에서 동시에 사용할 경우

 

예제 1

public class SynchTest {
	private String mMessage; 
	private static String mMessage2;
	
	public static void main(String[] agrs) { 
		SynchTest temp1 = new SynchTest(); 
		new Thread(() -> { for (int i = 0; i < 100; i++) { temp1.callMe1("Thread1"); } }).start(); 
		new Thread(() -> { for (int i = 0; i < 100; i++) { temp1.callMe1("Thread2"); } }).start(); 
		
		SynchTest temp2 = new SynchTest();
		new Thread(() -> { for (int i = 0; i < 100; i++) { temp2.callMe2("Thread1"); } }).start(); 
		new Thread(() -> { for (int i = 0; i < 100; i++) { temp2.callMe2("Thread2"); } }).start(); 	
	} 
	
	public void callMe1(String whoCallMe) { 
		mMessage = whoCallMe; 
		try { 
			long sleep = (long) (Math.random() * 100); 
			Thread.sleep(sleep); 
		} catch (InterruptedException e) { 
			e.printStackTrace(); 
		} 
		
		if (!mMessage.equals(whoCallMe)) { 
			System.out.println("callMe1> " + whoCallMe + " | " + mMessage); 
		} 
	}
	
	public synchronized void callMe2(String whoCallMe) { 
		mMessage = whoCallMe; 
		try { 
			long sleep = (long) (Math.random() * 100); 
			Thread.sleep(sleep); 
		} catch (InterruptedException e) { 
			e.printStackTrace(); 
		} 
		
		if (!mMessage.equals(whoCallMe)) { 
			System.out.println("callMe2> " + whoCallMe + " | " + mMessage); 
		} 
	}
}

기본적인 로직은 다음과 같다.

  • parameter 값을 멤버 변수에 저장하고 랜덤하게 sleep
  • 멤버 변수와 parameter 값이 같이 않은 경우에 문자열 출력

출력한 결과의 일부

callMe1()만 로그가 찍히고 synchronized를 붙인 callMe2()에 대해서는 아무런 로그가 찍히지 않는다.

 

synchronized는 스레드가 접근 중이면 다른 스레드가 접근하지 못하도록 막는다. (lock)

synchronized 함수 자신이 포함된 객체에 lock을 걸기 때문에 객체에 포함된 다른 synchronized 접근도 불가능하다.

 

예제 2

public class SyncTest {
    private static int amount = 0;
    public synchronized void donate1() {
    	amount++;
	}
    public static synchronized void donate2() {
    	amount++;
	}
}

결론부터 말하자면 donate1()은 amount가 동기화 되어있지 않다.

amount는 static이지만 donate1()은 static이 아니라 인스턴스 객체의 메소드에 해당된다.

static 변수를 동기화 하려면 해당 변수를 사용하는 함수도 동기화 해야한다.


참고

https://mangkyu.tistory.com/47

https://stackoverflow.com/questions/7026507/why-are-static-variables-considered-evil

https://ooeunz.tistory.com/110

https://tourspace.tistory.com/54?category=788398

자바 성능 튜닝 이야기 / 이상민

 

반응형

'Develop > Java' 카테고리의 다른 글

[Jackson] JsonNode, ObjectNode, ArrayNode 차이  (4) 2021.08.24
[Java] 얕은 복사와 깊은 복사 (+Clone)  (0) 2021.06.22
[Java] Collections와 Map  (1) 2021.05.21
[MVC] 어댑터 추가  (0) 2021.05.19
[MVC] Controller 단순화  (0) 2021.05.13