본문 바로가기

Java/Java

[Java] 스레드와 Synchronized

자바 멀티스레드

프로세스 스레드 글 보러가기

https://dlwnsdud205.tistory.com/279

 

[Java] Java Heap Stack Static

프로세스 와 스레드 자바의 메모리 구조를 이해하기위해선, 우선 프로세스와 스레드의 차이를 알아야한다. 프로세스 프로세스는 운영체제에서 할당받는 하나의 공간으로 각각의 프로세스는 독

dlwnsdud205.tistory.com

프로세스란 실행중인 하나의 프로그램이다.

모든 프로세스는 하나의 메인스레드를 갖고있으며, 프로세스의 동작은 메인스레드에 의해서 이루어진다. 멀티스레드란 하나의 프로세스 내에서 여러개의 스레드를 만들어 동시에 작업을 수행하는것으로, 프로세스를 만들어 병렬처리하는것보다 효율이 좋다.

멀티프로세스 - 여러개의 프로세스를 만들어 동시에 작업 수행

멀티스레드 - 하나의 프로세스 내에서 여러개의 스레드를 만들어, 동시에 (Conccurency or Parallelism)으로 작업 수행

 

병렬성과 동시성

동시성 : 하나의 코어가 스레드를 번갈아가며 실행한다(컨텍스트 스위칭). 이 과정이 매우 빨라서, 사용자 입장에서는 병렬적으로 실행되는것으로 보인다. 

병렬성 : 스레드마다 코어가 하나씩 할당되어 병렬적으로 실행되는것을 의미한다. 

병렬성과 동시성은 컴퓨터가 사용가능한 CPU가 사용가능한 코어수에따라 결정된다.

자바 Thread 기본적인 사용법

https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html

 

Thread (Java Platform SE 7 )

Allocates a new Thread object so that it has target as its run object, has the specified name as its name, and belongs to the thread group referred to by group, and has the specified stack size. This constructor is identical to Thread(ThreadGroup,Runnable,

docs.oracle.com

자바에서 멀티스레드를 사용하기위해선, Runnable 인터페이스를 구현해야한다.

예시코드

public class Main{
    
	private int num;
	
    public static void main(String[] args){
		Thread t1 = new Thread(new ThreadTest("Hello 1"));
		Thread t2 = new Thread(new ThreadTest("Hello 2"));
		Thread t3 = new Thread(new ThreadTest("Hello 3"));
		t1.start();
		t2.start();
		t3.start();
    }
    
}

class ThreadTest implements Runnable{
	
	private String helloWorld;
	
	public ThreadTest(String helloWorld){
		this.helloWorld = helloWorld;
	}
	
	/**
	Runnable 구현 메소드이다.
	Thread를 사용하려면 Runnable인터페이스를 구현해야한다.
	*/
	@Override
	public void run(){
		try{
			System.out.println(this.toString());
			Thread.sleep(5000);
			System.out.println(this.toString());
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	@Override 
	public String toString(){
		return "hello! " + this.helloWorld;
	}
	
}

Runnable은 run()메소드 하나만 갖고있는 함수형 인터페이스다. 따라서 위 코드는 아래와 같이 변경할수있다.

 

public class Main{
    
	private int num;
	
    public static void main(String[] args){
		Thread t1 = new Thread(() ->{
			try{
				System.out.println("1");
				Thread.sleep(5000);
				System.out.println("1");
			}catch(Exception e){
				e.printStackTrace();
			}
		});
		t1.start();
    }
    
}

*주의! .start()가 아닌 .run()으로 runnable을 직접 호출하게되면, 순차적으로 동작한다.*

데몬스레드

public class Main{
    
    private int num;
	
    public static void main(String[] args){
		Thread t1 = new Thread(() ->{
			try{
				System.out.println("1");
				Thread.sleep(5000);
				System.out.println("1");
			}catch(Exception e){
				e.printStackTrace();
			}
		});
		t1.start();
		return;
    }
    
}

우선 위 코드를 실행시켜보면, 5초간격으로 1이 두번 출력되는걸 확인할수있다.

 

위에서 설명한 스레드는 모두 개별적인 스레드로, 메인스레드가 종료되더라도, 스레드가 하나라도 살아있다면, 프로세스는 종료되지않는다. 따라서, t1스레드 시작후에 바로 메인스레드를 종료해줬음에도 프로세스는 종료되지않고, 5초뒤에 1이 출력되는것이다.

아래는 메인스레드가 종료되면 같이 종료되는, 데몬스레드를 만드는법이다.

public class Main{
    
    private int num;
	
    public static void main(String[] args){
		Thread t1 = new Thread(() ->{
			try{
				System.out.println("1");
				Thread.sleep(5000);
				System.out.println("1");
			}catch(Exception e){
				e.printStackTrace();
			}
		});
		t1.start();
		t1.setDaemon(true);
		return;
    }
    
}

1이 처음 한번만 출력되고 5초가 지나기전에 프로세스가 종료되는걸 볼수있다.

동시성 처리

멀티스레드 환경에서는 스레드 세이프하게 코딩하는것이 굉장히 중요하다.

스레드세이프란 멀티스레드 환경에서, 여러스레드가 하나의 자원에 동시에 접근하더라도, 프로그램에서 항상 올바른 답을 리턴하는것을 의미한다.

 

동시성 처리를 위해선 공유자원( ex) 전역변수 )를 읽기전용으로 만드는것이 가장 좋지만, 어쩔수 없이 사용해야한다면 아래와 같은 방법을 사용할 수 있다.

- Synchronized

- Validate : Validate에 대해서는 따로 정리하도록 하겠다.

 

Synchronized

Sycnhronized는 메소드선언 혹은 블록을 만들어 사용가능하며, 객체기준으로 락을걸어 스레드의 동시접근을 막는다.

사용 예시를 보자.

 

1. 메소드에 선언

public synchronized String syncString(String s){
    this.s = s;
    return s;
}

2. 블록기준으로 락

public String syncString(String s){
    synchronized(this){
        this.s = s;
    }
    return s;
}

위 와 같이 블록기준으로 락을 걸수도 있으며, 이 때도 마찬가지로 락의 범위는 객체이다.

(블록으로 동기화를 했으나, this.s가 필드에 있는 String 변수 s를 참조하므로)

주의할점이, 락의 범위가 객체이므로, 무턱대고 사용하다가는, 공유해야하는 리소스가 같이 락에 걸릴수있다

 

+synchronized의 동기화 범위는 다음과 같다.

- 인스턴스 메소드에 synchronized : 생성된 인스턴스를 기준으로 동기화가 된다.

- 스태틱 메소드에 synchronized : 객체 기준으로 동기화가 된다.

- 블록 synchronized : 블록내부에 synchronized를 처리하나, 외부 필드를 참조할 경우 해당 필드가 포함된 인스턴스를 기준으로 동기화가 된다.

 

이럴때 사용할수있는 최적화는 다음과 같다.

public class ShareClass{
    
    private Object forLock = new Object();
    
    public String syncString(String s){
        synchronized(forLock){
            this.s = s;
        }
        return s;
    }
}

무의미한 forLock객체를 생성하여 해당 객체에 락을 걸어버린다. 

 

이 외 에도, 자바 기본 스레드풀이 아닌, executor를 이용한 커스텀 스레드풀, 스레드 상태제어등 많은 내용이 있으나, 이는 추후 포스팅 하겠다.

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

[Java] Json 기본 생성자 없이 역직렬화 하기  (0) 2023.10.24
[Java] Future.cancel() vs CompletableFuture.cancel()  (0) 2023.02.15
[Java] Java Heap Stack Static  (0) 2021.10.23
[Java] compareTo  (0) 2021.05.05
[Java] Wrapper Class  (0) 2021.05.02