• 프로세스 : 실행중인 프로그램                                     
    • 멀티태스킹 : 다중 프로세스 실행
  • 쓰레드 : 프로세스 내에서 작업 단위     
    • 멀티쓰레딩 : 한 프로세스 내에서 다중 쓰레드 실행

쓰레드

  1. Thread 클래스를 상속
    • 자바의 단일상속에 의해 다른 클래스를 상속받을수 없음
    • 인터페이스를 사용하는 방법에 비해 유연성이 떨어짐
  2. Runnable 인터페이스 구현(추천)
    • 쓰레드간의 데이터를 공유해야 할 때 인터페이스로 구현해서 연결시킴
    • Thread와 실행할 코드(=run())를 분리 할 수 있어 가독성 증대
  • 쓰레드는 순서와 실행 시간을 모두 보장하지 않는다
  • 어플리케이션은 모든 사용자 쓰레드가 종료되면 종료된다
    • 메인 쓰레드가 종료된다고 종료되는 것이 아님
    • 어느 사용자 쓰레드 스택에 메서드 하나 없이 비게 되면 해당 쓰레드는 종료

Runnable 인터페이스

  • 추상메서드 run() 정의
public interface Runnable {
    public abstract void run();
}

Thread 클래스

public class Thread implements Runnable {
	...
	private volatile String name;
	private int priority;
	private Runnable target;

	public static final int MIN_PRIORITY = 1;
	public static final int NORM_PRIORITY = 5;
	public static final int MAX_PRIORITY = 10;

	public Thread(Runnable target) {
		this(null, target, "Thread-" + nextThreadNum(), 0);
	}

	public static native Thread currentThread();

	public synchronized void start() {
		...
		group.add(this);

		boolean started = false;
		try {
			start0();
			started = true;
		} finally {
			try {
				if (!started) {
					group.threadStartFailed(this);
				}
			} catch (Throwable ignore) {}
		}
	}

	@Override
	public void run() {
		if (target != null) {
			target.run();
		}
	}
}
  • start()
    • 쓰레드를 실행시킬 때 사용하는 인스턴스 메서드(Thread 클래스에 구현되어 있음)
    • 하나에 쓰레드에 대해 한번만 호출 가능(다시 호출하기 위해서는 new 연산자로 다시 만들어 줘야함)
    • 새로운 쓰레드를 호출스택에 생성한 다음 run()을 호출
    • 실행 대기 상태로 전환시키며 실행 중인 쓰레드가 없으면 바로 실행시킴
    • 항상 start()를 통해 쓰레드를 실행시켜야 한다
      • 이 메서드는 신규 쓰레드를 생성하고 로직을 수행 요청하는 특별한 메서드
      • start() 대신 run() 메서드를 실행시키면 신규 쓰레드가 아닌 메인 쓰레드가 실행하게 됨 (그냥 메서드 호출과 동일)
  • run()
    • 쓰레드를 실행시키는 것이 아닌 run()에 구현된 메서드를 호출하는 것.
    • 쓰레드를 실행시키는 start()에 의해 호출당함.
  • currentThread()
    • 현재 쓰레드 표기  ex) Thread[name, priority, group]

쓰레드 사용

public class HelloRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.CurrentThread().getName() + ": run()");
    }
}
HelloRunnable runnable = new HelloRunnable();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);

thread1.start();
thread2.start();

데몬 쓰레드

  • 사용자 쓰레드를 돕기위한 보조역할 쓰레드
  • setDaemon(true)로 설정함. 이 메서드는 꼭 start() 호출 전에 실행 되어야함.
  • 보조적인 역할만 하기 때문에 사용자 쓰레드가 종료되면 자동으로 종료됨.
public class DaemonThreadMain {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + ": main() start");
        DaemonThread daemonThread = new DaemonThread(); 
        daemonThread.setDaemon(true); // start() 호출 전 데몬 스레드 설정

        daemonThread.start();
        System.out.println(Thread.currentThread().getName() + ": main() end");
    }

    static class DaemonThread extends Thread {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + ": run() start");
        }
    }
}

스케줄링(실행제어)

생성 –①–>실행대기<—-②—->실행 –⑤–>소멸
                  └<-④-일시정지<-③ ┘

  1. start() : 실행대기 or 바로 실행 
  2. yield() : 다시 실행대기상태가 되고 다음 쓰레드가 실행됨
  3. suspend(), sleep(), wait(), join(), I/O block : 일시정지 상태로 전환 ex) 사용자의 입력을 기다림.
  4. resume(), notify(), interrupt(), timeout : 일시정지에서 벗어나 다시 실행대기로 전환
  5. stop() : 쓰레드 소멸

동기화(Synchronization)

  • 한 쓰레드가 진행 중인 작업을 다른 쓰레드가 간섭하지 못하도록 함.
  • lock을 갖은 하나의 쓰레드만 임계 영역 내의 코드 수행
  • synchronized로 임계 영역 설정.
  1. 동기화 메서드 : 메서드 선언부에 synchronized 설정
synchronized void withdraw() {
	if (money <= 0) {
	    return;
	}
	money -= 10000;
	System.out.println(Thread.currentThread().getName() + "출금. 잔액 : " + money);

}
  1. 동기화 블록 : synchronized 블록 지정
public void withdraw() {
   synchronized(this) {
	 if (money <= 0) {
	     return;
	 }
	 money -= 10000;
	 System.out.println(Thread.currentThread().getName() + "출금. 잔액 : " + money);
}
  • wait() : 사용 중인 lock을 강제로 반납시킴.
  • notify() : 중단 됐던 쓰레드가 lock을 다시 얻어 실행됨(재진입)

ThreadLocal

  • 각 쓰레드마다 별도의 저장소를 사용할 수 있게 해주는 클래스 (쓰레드 전용 저장소)
  • 전역변수/클래스변수를 쓰레드마다 다르게 접근할 수 있어 동시성 문제를 해결 할 수 있다
public class ThreadLocalExam {

	public static ThreadLocal<String> myThreadLocal = new ThreadLocal<>();

	public static void main(String[] args) {
		myThreadLocal.set("Hello World!");
		String myThreadLocalValue = myThreadLocal.get();
		myThreadLocal.remove();
	}
}
  • new ThreadLocal() : 생성자
  • set() : 값 저장
  • get() : 값 조회
  • remove() : ThreadLocal에 할당된 값 삭제

  • remove()를 반드시 실행하여 ThreadLocal을 비워야 한다
    • 쓰레드 풀과 같은 환경에서 ThreadLocal을 사용하고 비워주지 않으면 데이터가 남아있게 되어 재사용시 유효하지 않은 데이터를 사용할 수도 있게 된다