ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 멀티 스레드의 개념(왜 멀티 스레드를 사용해야할까?)
    JAVA 2022. 4. 9. 17:35

    Thread가 뭐지?


    Thread란, 한가닥의 실이라는 뜻으로, 프로그래밍에서는 실행흐름을 뜻한다.

     

    이해를 돕기 위해 한 가지 상황을 가정해보자.

    나는 하나의 메인 클래스에서 소리와 동시에 문자를 출력하는 프로그램을 만들고 싶다.

    그래서 소리를 내는 실행문을 작성하고, 그 밑에 문자를 프린트해주는 실행문을 작성했다!

     

    야심차게 실행해보았지만, 프로그램은 소리가 난 이후에 문자를 출력해줄 뿐,

    내가 원하는 대로 동시에 실행해주지 못했다...

     

    import java.awt.Toolkit;
    
    
    // 이 예제는, single thread 하나로(즉, main thread), 2가지 
    // 쓰레드가 하나이기 때문에, 2가지 일(작업, task)를 순차적으로 할 수 없다.
    public class BeepPrintExample1 {
    	
    	
    	public static void main(String[] args) {
    		Toolkit toolkit = Toolkit.getDefaultToolkit();
    		
    		for(int i=0; i<5; i++) {					// 1st. Task
    			toolkit.beep();							// '띵' 소리 출력	
    
    			try { Thread.sleep(500); } 				// 밀리초 단위로 쓰레드의 실행흐름을 지정된 시간동안 잠시 멈춤
    			catch(Exception e) {;;}	
    		} // for
    		
    		for(int i=0; i<5; i++) {					// 2nd. Task
    			System.out.println("띵");				// 콘솔로 '띵' 문자 출력
    			
    			try { Thread.sleep(500); } 
    			catch(Exception e) {;;}
    		} // for
    		
    	} // main
    
    } // end class

     

    이 예제는 메인쓰레드 하나로 2가지 일을 동시에 수행하려고 하고 있다.

    그러나 하나의 스레드는 2가지 이상의 작업(task)를 동시에 수행할 수 없어 의도한 대로 결과가 나오지 않는다.

     

    우리는 웹 서핑을 하면서 유튜브도 보고, 문서정리도 하고, 여러개의 창을 띄워서 보기도 한다.

    이걸 가능하게 해주는 것이 바로 멀티 쓰레드(Multi-Thread)이다.

     

    웹 브라우저의 경우엔 처리할 작업이 매우 많기 때문에 멀티 쓰레드 뿐만 아니라 멀티 프로세스도 사용한다.

    (브라우저 실행 후 작업관리자에서 프로세스 목록을 보면, 하나의 웹 브라우저를 실행했음에도 불구하고 똑같은 이름의 프로세스가 여러개 뜨는 것을 볼 수 있다. = 멀티 프로세스)

     

    멀티 프로세스 : 독립적으로 프로그램들을 실행하고 여러 가지 작업 처리

    멀티 스레드 : 한 개의 프로그램을 실행하고 내부적으로 여러 가지 작업 처리

     

    라고 이해하면 된다.

     

    JVM의 스레드가 아닌 개발자가 직접 생성하는 스레드를 "User-Thread" 혹은 "Worker Thread"라고 한다.

    이 worker thread들은 따로 이름을 지정해주지 않으면 Thread-숫자로 표시된다.

     

    이름을 지정할 땐 start() 메소드 실행 전 setName("이름") 메소드를 사용하면 된다.

     

    멀티스레드는 어떻게 작성해야 할까?


    1. Runnable 인터페이스를 구현해서 만들기

    Runnable 인터페이스를 구현 후, 이를 매개변수로 갖는 Thread 객체를 생성해서 만드는 방법이다.

    Thread 클래스에는 다양한 생성자가 오버로딩 되어있는데, 이중 Runnable 타입을 매개변수로 가지는 생성자를 사용하면된다.

     

    Runnable 인터페이스는 public abstract void run() 메소드를 가진 함수적 인터페이스(Functional interface)이다.

    따라서 functional interface를 구현하는 일반적인 3가지 방법으로 구현할 수 있다.

     

    (1) 구현클래스로 구현

    import java.awt.Toolkit;
    
    import lombok.NoArgsConstructor;
    
    
    // 함수적 인터페이스인 Runnable을 구현하는 구현 클래스 선언
    @NoArgsConstructor
    public class BeepTask implements Runnable {
    
    	
    	@Override
    	public void run() {
    		System.out.println("BeepTask::run() invoked.");
    		
    		Toolkit toolkit = Toolkit.getDefaultToolkit();
    		
    		for(int i=0; i<5; i++) {
    			toolkit.beep();
    			
    			try { Thread.sleep(500); }
    			catch(Exception e) {;;}
    		} // for
    	} // run
    
    } // end class
    import java.awt.Toolkit;
    
    public class BeepPrintExample2 {
    	
    	
    	// 이전 예제의 2가지 작업(비프음 출력, 띵 문자출력)을
    	// Worker Thread를 만들어서 동시에 수행시키자
    	public static void main(String[] args) {
    
    		// 1. 작업쓰레드(Worker Thread)를 만드는 첫번째 방법
    		Runnable beepTask = new BeepTask();
    		Thread thread = new Thread(beepTask);
    		
    		// 1번째 Task
    		thread.start();
    		
    		---
    		
    		// 2번째 Task
    		for(int i=0; i<5; i++) {
    			System.out.println("띵");
    			try { Thread.sleep(500); }
    			catch(Exception e) {;;}
    		} // for

     

     

    (2) 익명구현객체로 구현

    import java.awt.Toolkit;
    
    
    public class BeepPrintExample2 {
    
    
    	public static void main(String[] args) {
    // 2. 작업쓰레드(Worker Thread)를 만드는 두번째 방법
    		Thread thread = new Thread(new Runnable() {
            
    			
    			@Override
    			public void run() {
    				System.out.println("Anonymous::run() invoked.");
    				
    				Toolkit toolkit = Toolkit.getDefaultToolkit();
    				
    				for(int i=0; i<5; i++) {
    					toolkit.beep();
    					
    					try { Thread.sleep(500); }
    					catch(Exception e) {;;}
    				} // for
    			} // run
    			
    		}); // 익명구현객체코딩기법으로 만든 익명구현객체
    		
    		thread.start();
    		
    		// 2번째 Task
    		for(int i=0; i<5; i++) {
    			System.out.println("띵");
    			try { Thread.sleep(500); }
    			catch(Exception e) {;;}
    		} // for
        } // main
    } // end class

     

     

    (3) 람다식으로 구현 ((2)에서 해당부분만 바뀐다.)

    // 3. 작업쓰레드(Worker Thread)를 만드는 세번째 방법
    Thread thread = new Thread(() -> {
        System.out.println("Lambda::run() invoked.");
    
        Toolkit toolkit = Toolkit.getDefaultToolkit();
    
        for(int i=0; i<5; i++) {
            toolkit.beep();
    
            try { Thread.sleep(500); }
            catch(Exception e) {;;}
        } // for
    }); // 람다식으로 구현(Runnable이 functional interface이기 때문에 가능) -> 가장 권장하는 방법!

     

    (2), (3)의 경우 구현클래스를 만들 필요 없이 실행 클래스 내부에서 바로 만들 수 있다.

    functional interface라면 이왕이면 람다식으로 작성하는 습관을 들이자. 훨씬 간단한 코드를 작성할 수 있다!

     

    2. Thread 클래스를 상속받아서 만들기

     

    (1) Thread 클래스를 상속받는 자식클래스 만들기

    import java.awt.Toolkit;
    
    
    // Thread 클래스를 상속받는 자식클래스를 만들고,
    // 물려받은 메소드인 run() 메소드를 재정의함으로써,
    // 새로운 쓰레드 객체를 생성
    public class BeepThread extends Thread {
    	
    	
    	@Override
    	public void run() {
    		System.out.println("BeepThread::run() invoked.");
    		
    		Toolkit toolkit = Toolkit.getDefaultToolkit();
    		
    		for(int i=0; i<5; i++) {
    			toolkit.beep();
    			
    			try { Thread.sleep(500); }
    			catch(Exception e) {;;}
    		} // for
    	} // run
    
    } // end class
    import java.awt.Toolkit;
    
    
    public class BeepPrintExample3 {
    	
    	
    	public static void main(String[] args) {
    		// how1 - 자식 쓰레드 클래스를 만들어서 쓰레드 객체 생성
    		Thread thread = new BeepThread();
    		
    		thread.start();
    		
    		for(int i=0; i<5; i++) {
    			System.out.println("띵");
    			
    			try { Thread.sleep(500); }
    			catch(Exception e) {;;}
    		} // for
    	} // main
    } // end class

     

     

    (2) 익명구현객체를 사용해서 만들기

    import java.awt.Toolkit;
    
    
    public class BeepPrintExample3 {
    	
    	
    	public static void main(String[] args) {
        
    		// how2 - 익명자식객체코딩기법을 이용해 쓰레드 객체 생성
    		Thread thread = new Thread() {
    			
    			
    			@Override
    			public void run() {
    				System.out.println("Anonymous::run() invoked.");
    				
    				Toolkit toolkit = Toolkit.getDefaultToolkit();
    				
    				for(int i=0; i<5; i++) {
    					toolkit.beep();
    					
    					try { Thread.sleep(100); }
    					catch(Exception e) {;;}
    				} // for
    			} // run
    			
    		}; // 익명자식객체 생성
    		
    		thread.setName("Shu-Thread");	// Default naming rule = Thread-N
    		thread.start();
    
    		for(int i=0; i<5; i++) {
    			System.out.println("띵");
    			
    			try { Thread.sleep(500); }
    			catch(Exception e) {;;}
    		} // for
    		
    	} // main
    
    } // end class

     

     

    Thread 클래스 또한 Runnable 인터페이스를 구현하고 있는 구현클래스이기 때문에

    여기에서는 람다식을 사용할 수 없다.

     

    한가지 주의할 점은,

    어느 방법으로든 멀티스레드를 생성하고 난 후엔 start()메소드로 실행하는 코드를 넣어줘야 한다.

    실행하지 않으면 단순 객체만 생성이 되는 것!!

     

    Thread 클래스의 start() 메소드

    Thread 클래스의 start() 메소드는 해당 클래스의 run()메소드를 호출하고,
    이 run()메소드는 Runnable 인터페이스의 run()메소드를 호출한다.
    이 때 우리가 재정의했던 메소드가 호출이 된다!

     

    이 글에 사용된 코드는 이것이 자바다 | 신용권 | 한빛미디어의 예제를 기반으로 작성하였습니다.

    댓글

Designed by Tistory.