본문 바로가기

자바 : 쓰레드(Thread)란?

참고 서적: 자바의 정석


1. 프로세스와 쓰레드

프로세스란 간단히 말해서 '실행 중인 프로그램'이다'.

프로그램을 실행하면 OS로부터 실행에 필요한 자원(메모리)을 할당받아 프로세스가 된다.

프로세스는 프로그램을 수행하는 데 필요한 데이터와 메모리 등의 자원 그리고 쓰레드로 구성되어 있으며

프로세스의 자원을 이용해서 실제로 작업을 수행하는 것이 바로 쓰레드다. 그래서 모든 프로세스에는 최소한 하나 이상의 쓰레드가 존재하며,

둘 이상의 쓰레드를 가진 프로세스를 멀티쓰레드 프로세스라고 한다.

멀티쓰레딩은 하나의 프로세스 내에서 여러 쓰레드가 동시에 작업을 수행하는 것이 가능하다.

실제로는 한 개의 CPU가 한 번에 단 한가지 작업만 수행할 수 있기 때문에 아주 짧은 시간 동안 여러 작업을 번갈아 가며 수행함으로써

동시에 여러 작업이 수행되는 것처럼 보이게 하는 것이다.


ex) 메신저의 경우 채팅하면서 파일을 다운로드 받거나 음성대화를 나눌 수 있는 것이 가능한 이유가 바로 멀티쓰레드로 작성되어 있기 때문이다.


- 멀티쓰레딩의 장점

  • cpu의 사용률을 향상시킨다.

  • 자원을 보다 효율적으로 사용할 수 있다.

  • 사용자에 대한 응답성이 향상된다.

  • 작업이 분리되어 코드가 간결해진다.

하지만 멀티쓰레드 프로세스는 여러 쓰레드가 같은 프로세스 내에서 자원을 공유하면서 작업을 하기 때문에 발생할 수 있는 동기화(synchronization),

교착상태(deadlock)와 같은 문제들을 고려해서 신중히 프로그래밍해야한다. (쓰레드 환경에서도 잘 작동 하는 것을 Thread safe라고 한다)

항상 우리가 사용하는 main메소드의 작업을 수행하는 것도 쓰레드다. 프로그램을 실행하면 기본적으로 하나의 쓰레드(main thread)가 실행된다. 그리고 실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다.


2. 쓰레드 구현과 실행

쓰레드를 구현하는 방법은 두가지가 있다.

  • Thread클래스를 상속

  • Runnable인터페이스를 구현

Runnable 사용을 추천한다. (Thread클래스를 상속받으면 다른 클래스를 상속받지 못하기 때문에)

Runnable을 구현하는 방법은 재사용성이 높고 코드의 일관성을 유지 할 수 있다는 장점이 있기 때문에 보다 객체지향적인 방법이라 할 수 있다.


2.1. Thread 클래스

public class ThreadExam01 {
   public static void main(String[] args){
       MyThread01 m1 = new MyThread01("*");
       MyThread01 m2 = new MyThread01("+");
       MyThread01 m3 = new MyThread01("#");
       m1.start();
       m2.start();
       m3.start();
       System.out.println("main메소드 종료.");
  }
}

class MyThread01 extends Thread{
   private String str;
   public MyThread01(String str){
       this.str = str;
  }

   @Override
   public void run() {
       for(int i = 0; i < 5; i++)
           System.out.print(str);
  }
}


2.2. Runnable 인터페이스

public class ThreadExam02 {
   public static void main(String[] args){
       Runnable r1 = new MyThread02("*");
       Runnable r2 = new MyThread02("%");
       Runnable r3 = new MyThread02("#");
       
       //Thread t1 = new Thread(new MyThread02("*"));
       Thread t1 = new Thread(r1);
       Thread t2 = new Thread(r2);
       Thread t3 = new Thread(r3);
       t1.start();
       t2.start();
       t3.start();
       System.out.println("main메소드 종료.");
  }
}

class MyThread02 implements Runnable{
   private String str;
   public MyThread02(String str){
       this.str = str;
  }

   @Override
   public void run() {
       for(int i = 0; i < 5; i++)
           System.out.print(str);
  }
}

[실행결과]
*****#####+++++main메소드 종료.

일단 둘 다 공통적으로 사용하기 위해서는

  • run() 메소드를 상속받아서 오버라이딩 해줘야한다.

  • 쓰레드 작업을 해줄 곳에 start()를 호출해준다.

    start()를 호출하면 thread를 실행 준비 -> run()을 실행한다.(템플릿 메서드 패턴 적용, 오버라이딩한 메소드 사용)


- Template Method Pattern(템플릿 메서드 패턴)

  • 상위 클래스에서 처리의 흐름을 제어하며, 하위클래스에서 처리의 내용을 구체화한다.
  • 여러 클래스에 공통되는 사항은 상위 추상 클래스에서 구현하고, 각각의 상세부분은 하위 클래스에서 구현한다.
  • 코드의 중복을 줄이고, Refactoring에 유리한 패턴으로 상속을 통한 확장 개발 방법으로써 전략 패턴(Strategy Pattern)과 함께 가장 많이 사용되는 패턴중에 하나이다.


출처: http://copynull.tistory.com/124 [Thinking Different]


2.3 run()에 Thread.sleep() 사용해서 지연

    @Override
   public void run() {
       for(int i = 0; i < 5; i++){
           System.out.print(str);
           try {
               Thread.sleep((long) (Math.random() * 1000));
          }catch(InterruptedException ie){}
      }
  }

[실행결과]
*#main메소드 종료.
%**#%*%*%#%##


이번에는 run()메소드에 Thread.sleep()을 사용해서 지연을 시켜보았다.

여러 개의 쓰레드로 작업하는 경우에는 짧은 시간동안 쓰레드들을 스케줄에 따라서 번갈아 가면서 작업을 수행해서 동시에 두 작업이 처리되는 것과 같이 느끼게 해준다.

그러나 이 전에 출력했을 때의 결과는 *****#####+++++main메소드 종료.

이렇게 순서대로 출력이 되었다. 이유는 컴퓨터의 속도가 빨라서 그런건데 Thread.sleep()을 사용해서 지연 시켜주면 번갈아가면서 출력되는 것을 확인 할 수가 있다.


* Runnable 인터페이스를 구현한 경우 인스턴스 생성방법이 다르다.

 Runnable r1 = new MyThread02("*");
Thread t1 = new Thread(r1);
t1.start();

//또는

Thread t1 = new Thread(new MyThread02("*"));
t1.start();


3. Lambda를 사용한 Runnable 구현

람다 표현식(lambda expression)은 java 8부터 도입되었는데 Runnable 인터페이스처럼 인터페이스에 메소드가 하나 있을 때 유용하게 사용 할 수 있다. ()->{ }

3.1

public class ThreadExam04 {
   public static void main(String[] args) {
       // 이름없는 클래스. new A(){ ... }
       // A를 상속받거나, 구현하는 이름없는 객체를 생성한다.
       Runnable r = new Runnable(){
           @Override
           public void run() {
               for(int i = 0; i < 100; i++){
                   System.out.println("*");
              }
          }
      };
       Thread t1 = new Thread(r);
       t1.start();
  }
}

3.2

        Thread t1 = new Thread(new Runnable(){
           @Override
           public void run() {
               for(int i = 0; i < 100; i++){
                   System.out.println("*");
              }
          }
      });
       t1.start();

3.3

        Thread t1 = new Thread(() -> {
           for (int i = 0; i < 100; i++)
               System.out.println("*");
      });
       t1.start();


'Java' 카테고리의 다른 글

자바 입출력 IO  (0) 2018.11.30
Comparator와 Comparable의 차이 + 람다표현식  (0) 2018.11.29
객체, 클래스, 인스턴스의 차이  (0) 2018.11.29
객체지향 프로그래밍의 특징  (0) 2018.11.29
자바의 특징  (0) 2018.11.29