[Java] 쓰레드(Thread)란?

재영양·2022년 11월 25일
0

Study

목록 보기
12/14
  • 쓰레드 정의
  • extend thread 쓰레드 만들기
  • implements Runnable로 쓰레드 만들기

쓰레드란?


  • 메모리를 할당받아 실행 중인 프로그램
  • 프로세스 내의 명령어 블록으로 시작점과 종료점을 가진다.
  • 실행중에 멈출 수 있으며 동시에 수행 가능하다.
  • 어떠한 프로그램내에서 특히 프로세스 내에서 실행되는 흐름의 단위

쓰레드 생성


쓰레드 생성 방법

  1. Thread 클래스를 직접 상속받아 생성
  2. Runnable 인터페이스 구현해서 생성

1. Thread 클래스를 직접 상속받아 생성

  • Thread 클래스로부터 제공되는 run() 메소드를 오버라이딩해서 사용

쓰레드 생성 코드 예시

public class Sample extends Thread {
    public void run() {  // Thread 를 상속하면 run 메서드를 구현해야 한다.
        System.out.println("thread run.");
    }
    
public static void main(String[] args) {
        Sample sample = new Sample();
        sample.start();  // start()로 쓰레드를 실행한다.
    }
}

Sample 클래스Thread 클래스를 상속했다. Thread 클래스의 run 메소드를 구현하면 위 예제와 같이 sample.start() 실행시 sample 객체의 run 메소드가 수행된다.


쓰레드 동작 확인 코드 예시

public class Sample extends Thread {
    int seq;
    
	public Sample(int seq) {
        this.seq = seq;
    }
    
    public void run() {
        System.out.println(this.seq + " thread start.");  // 쓰레드 시작
        try {
            Thread.sleep(1000);  // 1초 대기한다.
        } catch (Exception e) {
        }
        System.out.println(this.seq + " thread end.");  // 쓰레드 종료 
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {  // 총 10개의 쓰레드를 생성하여 실행한다.
            Thread t = new Sample(i);
            t.start();
        }
        System.out.println("main end.");  // main 메소드 종료
    }
}
  • Thread.sleep(1000) : 시작과 종료 사이에 1초의 간격
  • Thread.sleep((int) (Math.random()*1000) : 시작과 종료 사이에 n초의 간격
  • .sleep()은 예외처리가 필요함

2. Runnable 인터페이스 구현해서 생성

  • 현재의 클래스가 이미 다른 클래스로부터 상속 받고 있다면 Runnable 인터페이스를 이용하여 스레드를 생성
  • run() 메소드만 정의되어 있다.

쓰레드 생성 코드 예시
- RunnableTest.class

// Runnable 인터페이스 상속
public class RunnableTest implements Runnable
{
    // Runnable 인터페이스의 run() 오버라이딩
    public void run()
    {
        try {  // 인터럽트 예외처리
            for (int i=0 ; i<10 ; i++) {
                // 대기시간 0.2초
                Thread.sleep(200);
                System.out.println("스레드 :" + i);
            } 
        } catch(InterruptedException e ) {
            e.printStackTrace();
        }
 
    }
}

- Thread2.class

public class Thread2 
{
    public static void main(String args[])
    {
        // Runnable 인터페이스 객체생성
        RunnableTest Obj1 = new RunnableTest();
        RunnableTest Obj2 = new RunnableTest();
        
        // Runnable 객체를 매개변수로 하여 스레드 객체 th생성
        Thread th1 = new Thread(Obj1);
        Thread th2 = new Thread(Obj2);
        
        th1.start();
        th2.start();
    }
}

쓰레드와 공유객체


  • 하나의 객체를 여러개의 쓰레드가 사용한다는 것을 의미한다.
    ex) 그네 1개와 그네를 타려는 어린이 3명: 그네=객체, 어린이=쓰레드

공유객체 생성 방법

  1. 공통으로 사용할 데이터를 클래스로 정의한다.
  2. 공통으로 사용할 클래스의 인스턴스를 만든다.
  3. 이 인스턴스를 각각의 쓰레드에 넘겨 준다.
  4. 각각의 쓰레드는 이 인스턴스의 참조값을 저장한 변수를 이용하여 공통 데이터를 사용한다.

공유객체 예시
- 공유객체 MusicBox.class

public class MusicBox { 
	// 메시지가 1초이하로 쉬면서 10번 반복출력
	public void playMusicA(){
		for(int i = 0; i < 10; i ++){
			System.out.println("신나는 음악!~");
			try {
				Thread.sleep((int)(Math.random() * 1000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}        
	}

	public void playMusicB(){
		for(int i = 0; i < 10; i ++){
			System.out.println("슬픈 음악ㅠ.ㅠ");
			try {
				Thread.sleep((int)(Math.random() * 1000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}        
	} 
        
	public void playMusicC(){
		for(int i = 0; i < 10; i ++){
			System.out.println("감미로운 음악!");
			try {
				Thread.sleep((int)(Math.random() * 1000));
			} catch (InterruptedException e) {
					e.printStackTrace();
			}
		}         
	}   
}

- MusicBox를 가지는 Thread객체 MusicPlayer

public class MusicPlayer extends Thread{
	int type;
	MusicBox musicBox;  
	// 생성자로 부터 musicBox와 정수를 하나 받아들여서 필드를 초기화
	public MusicPlayer(int type, MusicBox musicBox){
		this.type = type;
		this.musicBox = musicBox;
	}       
	// type이 무엇이냐에 따라서 musicBox가 가지고 있는 메소드가 다르게 호출
	public void run(){
		switch(type){
			case 1 : musicBox.playMusicA(); break;
			case 2 : musicBox.playMusicB(); break;
			case 3 : musicBox.playMusicC(); break;
		}
	}       
}

- MusicBox와 MusicPlayer를 이용하는 MusicBoxExam 클래스

public class MusicBoxExam1 {

	public static void main(String[] args) {
	// MusicBox 인스턴스
	MusicBox box = new MusicBox();

	MusicPlayer kim = new MusicPlayer(1, box);
	MusicPlayer lee = new MusicPlayer(2, box);
	MusicPlayer kang = new MusicPlayer(3, box);

	// MusicPlayer쓰레드를 실행합니다. 
	kim.start();
	lee.start();
	kang.start();           
	}   
}

동기화 메소드


  • 멀티쓰레드 프로그래밍에서는 동기화 작업이 필수이다.
  • 쓰레드 동기화란 공유 객체를 사용하게 되면 서로의 결과에 영향을 주기 때문에 방지하는 기법이다.
  • 메소드 앞에 synchronized 키워드를 사용하여 임계영억을 지정하며, 동시에 공유자원을 차지하지 않도록 강제한다.
  • 여러개의 Thread들이 공유객체의 메소드를 사용할 때 메소드에 synchronized가 붙어 있을 경우 먼저 호출한 메소드가 객체의 사용권(Monitoring Lock)을 얻는다.
public synchronized void playMusicA(){
	for(int i = 0; i < 10; i ++){
		System.out.println("신나는 음악!!!");
		try {
			Thread.sleep((int)(Math.random() * 1000));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}         
}

public void playMusicB(){
	for(int i = 0; i < 10; i ++){
		synchronized(this){
			System.out.println("슬픈 음악!!!");
		}
		try {
			Thread.sleep((int)(Math.random() * 1000));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}        
}
  • 메소드 앞에 synchronized 를 붙혀서 실행해 보면, 메소드 하나가 모두 실행된 후에 다음 메소드가 실행된다.
  • 해당 모니터링 락은 메소드 실행이 종료되거나, wait()와 같은 메소드를 만나기 전까지 유지된다.
  • 다른 쓰레드들은 모니터링 락을 놓을때까지 대기한다.
  • synchronized를 붙히지 않은 메소드는 다른 쓰레드들이 synchronized메소드를 실행하면서 모니터링 락을 획득했다 하더라도, 그것과 상관없이 실행된다.
  • synchronized를 메소드에 붙혀서 사용 할 경우, 메소드의 코드가 길어지면, 마지막에 대기하는 쓰레드가 너무 오래 기다리는것을 막기위해서 메소드에 synchronized를 붙이지 않고, 문제가 있을것 같은 부분만 synchronized블록을 사용한다.

쓰레드와 상태제어


쓰레드의 상태 6가지

  • 쓰레드는 실행가능상태인 Runnable과 실행상태인 Running 상태로 나뉜다.
  • 실행되는 쓰레드 안에서 Thread.sleep()이나 Object가 가지고 있는 wait()메소드가 호출이 되면 쓰레드는 Blocked 상태가 된다.
  • Thread.sleep()은 특정시간이 지나면 자신 스스로 블록상태에서 빠져나와 Runnable이나 Running상태가 된다.
  • Object가 가지고 있는 wait()메소드는 다른 쓰레드가 notify()notifyAll()메소드를 호출하기 전에는 블록상태에서 해제되지 않는다.
  • wait()메소드는 호출이 되면 모니터링 락을 놓게 된다. 그래서 대기중인 다른 메소드가 실행한다.
  • 쓰레드의 run메소드가 종료되면, 쓰레드는 종료된다. 즉 Dead 상태가 된다.
  • Thread의 yeild메소드가 호출되면 해당 쓰레드는 다른 쓰레드에게 자원을 양보하게 된다.
  • Thread가 가지고 있는 join메소드를 호출하게 되면 해당 쓰레드가 종료될 때까지 대기하게 된다.

join() 메소드

해당 쓰레드가 모든 일을 끝낼 동안 다른 쓰레드가 기다려준다.

public class JoinExam { 
	public static void main(String[] args) {
		MyThread5 thread = new MyThread5();
		// Thread 시작 
		thread.start(); 
		System.out.println("Thread가 종료될때까지 기다립니다.");
		try {
			// 해당 쓰레드가 멈출때까지 멈춤
			thread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("Thread가 종료되었습니다."); 
	}   
}

wait(), notify() 메소드

  • waitnotify는 동기화된 블록안에서 사용해야 한다.
  • wait를 만나게 되면 해당 쓰레드는 해당 객체의 모니터링 락에 대한 권한을 가지고 있다면 모니터링 락의 권한을 놓고 대기한다.

데몬 쓰레드


  • 데몬(Daemon)이란 보통 리눅스와 같은 유닉스계열의 운영체제에서 백그라운드로 동작하는 프로그램을 말한다.
  • 데몬쓰레드를 만드는 방법은 쓰레드에 데몬 설정을 하면 된다.
  • 이런 쓰레드는 자바프로그램을 만들 때 백그라운드에서 특별한 작업을 처리하게 하는 용도로 만든다.
  • 데몬쓰레드는 일반 쓰레드(main 등)가 모두 종료되면 강제적으로 종료되는 특징을 가지고 있다.
// Runnable을 구현하는 DaemonThread클래스를 작성
    public class DaemonThread implements Runnable {

        // 무한루프안에서 0.5초씩 쉬면서 데몬쓰레드가 실행중입니다를 출력하도록 run()메소드를 작성
        @Override
        public void run() {
            while (true) {
                System.out.println("데몬 쓰레드가 실행중입니다.");

                try {
                    Thread.sleep(500);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break; //Exception발생시 while 문 빠찌도록 
                }
            }
        }

        public static void main(String[] args) {
            // Runnable을 구현하는 DaemonThread를 실행하기 위하여 Thread 생성
            Thread th = new Thread(new DaemonThread());
            // 데몬쓰레드로 설정
            th.setDaemon(true);
            // 쓰레드를 실행
            th.start();

            // 메인 쓰레드가 1초뒤에 종료되도록 설정. 
            // 데몬쓰레드는 다른 쓰레드가 모두 종료되면 자동종료.
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }   
            System.out.println("메인 쓰레드가 종료됩니다. ");    
        }   
    }

0개의 댓글