[Java]::Remind - Thread

Gentlee's Self-Study Log·2023년 5월 18일
0

Java Reminder

목록 보기
15/19
post-thumbnail

Thread - 스레드

하나의 프로세스 내부에서 독립적으로 실행되는 하나의 작업 단위를 말하며, 세부적으로는 운영체제에 의해 관리되는 하나의 작업 혹은 태스크를 의미한다. 스레드와 태스크(혹은 작업)은 바꾸어 사용해도 무관합니다.

  1. JVM에 의해 하나의 프로세스가 발생하고 main( ) 안의 실행문 들이 하나의 스레드이다.

  2. main( ) 이외의 또 다른 스레드를 만들려면 Thread 클래스를 상속하거나 Runnable 인터페이스를 구현한다.

  3. 다중 스레드 작업 시에는 각 스레드 끼리 정보를 주고받을 수 있어 처리 과정의 오류를 줄일 수 있다.

  4. 프로세스끼리는 정보를 주고받을 수 없다.

Thread 생성

스레드를 생성할 때, 크게 두가지 방법이 있다. 첫번째는 Thread클래스를 상속받아 생성하는 방법과, Runnable 인터페이스를 상속받는 방법이 있다.

Thread 객체 상속

  • 스레드 클래스를 상속받는다.
  • 스레드로서 동작할 실행문은 run() 메소드에 구현한다.
  • run() 메소드의 실행은 객체를 생성한 후, start() 메소드를 호출한다.

Example Code - Thread 클래스 상속

class Food extends Thread{
	public void run(){
    	for(int i=1;i<=1000;i++){
        	System.out.println("음식 먹기"+i);
        }
    }
}
class Phone extends Thread{
	public void run(){
    	for(int i=1;i<=1000;i++){
        	System.out.println("핸드폰 하기"+i);
        }
    }
}
public static void main(String[] args){
	Food work1 = new Food();
    Phone work2 = new Phone();
    
    work1.start();  // Food.run() 메소드 실행
    work2.start();	// Phone.run() 메소드 실행
    
    for(int i=1;i<=1000;i++){
    	System.out.println("잠들기"+i);
    }
}

Runnable 인터페이스 상속

  • Runnable 인터페이스를 상속받는다.
  • 스레드로서 동작할 실행문은 run() 메소드에 구현한다.
  • run() 메소드를 실행하기 위해 Thread 클래스를 생성해야한다.
  • Thread 클래스를 생성할 때, 인자값으로 Runnable 인터페이스를 상속받는 객체를 전달한다.
  • run() 메소드를 실행하기 위해서 생성한 Thread 클래스의 start() 메소드를 호출한다.

Example Code - Runnable 상속

class Movie implements Runnable{
	@Override
    public void run(){
    	for(int i=1;i<=1000;i++){
        	System.out.println("영화 보기"+i);
        }
    }
}
class Think implements Runnable{
	@Override
    public void run(){
    	for(int i=1;i<=1000;i++){
        	System.out.println("생각 하기"+i);
        }
    }
}
public static void main(String[] args){
	// Thread 객체 생성
	Thread t1 = new Thread(new Movie());
    Thread t2 = new Thread(new Think());
    
    // run() 메소드 실행
    t1.start();
    t2.start();
    
    for(int i=1;i<=1000;i++){
    	System.out.println("잠들기"+i);
    }
}

Thread 설정

  • 스레드를 지정하기 위해 setName()으로 이름을 지정할 수 있다.
  • 여러 개의 스레드를 실행 시에 setPriority()로 우선순위를 매겨 실행 순서를 정할 수 있다.
    Example : 객체.setPriority(Thread.MAX_PRIORITY)

Synchronized - 동기화

동기화는 하나의 자원을 가지고 여러 개의 스레드가 동시에 사용할 때 자원의 안정성을 확보하기 위해 처리하는 기능이다.

특정객체 동기화

synchronized(객체 이름)으로 특정 객체에 대하여 동기화를 지정할 수 있다.

Example Code - 특정 객체 동기화

class Card{
	private long balance = 1000;
    
    public void draw(long amount){
    	balance -= amount;
    }
    
    public long getBalance(){
    	return balance;
    }
}
class DrawThread extends Thread{
	Card card;
    
    DrawThread(Card card){
    	this.card = card;
    }
    
    public void run(){
    	synchronized(card){
        	for(int i=0; i<10 ; i++){
            	card.draw(10);
                System.out.println(this.getName()+"출금 후 잔액
                +card.getBalance());
            }
        }
    }
}
public static void main(String[] args){
	Card card=new Card();
    DrawThread t1 = new DrawThread(card);
    DrawThread t2 = new DrawThread(card);
    
    t1.start();
    t2.start();
}

synchronized(card){실행문} 으로 작성 되면 card객체에 스레드들이 접근할 때, t1스레드가 card객체에 자원을 사용하고 있을 때, t2스레드는 t1스레드의 자원 사용 작업이 끝날때까지 대기하고 있어야 한다.

메서드 동기화

메서드 선언부에 synchronized를 선언하여 메서드 단위로도 동기화 지정이 가능하다.

Example - 메서드 동기화

class Card{
	private long balance = 1000;
    
    public synchronized void draw(long amount){
    	balance -= amount;
    }
    
    public long getBalance(){
    	return balance;
    }
}

스레드 상태

Thread 가 실행 될 때, 다른 스레드가 처리 객체에 대해 먼저 사용하고 있으면 동기화 처리 때문에 대기상태가 된다.

이렇게 실행 대기중인 상태를 Runnable 상태라고 한다.
그리고 실행 중인 상태는 Running 상태라고 한다.

스레드 제어

스레드를 제어하기 위한 여러가지 메소드들이 있다. 대표적으로 wait() 과 notify()/notifyAll()이 있다.

wait()

wait()은 말그대로 기다리게 하는 메서드이다. 스레드가 처리 객체에 대해 Running상태인 다른 스레드가 있으면 wait()메서드로 자원사용의 충돌을 막을 수 있다.

nofity()/notifyAll()

wait()으로 대기상태가 된 스레드들을 해제시키는 메소드이다. notifyAll()은 Running상태인 스레드가 처리 객체에 대해 사용을 완료하면 다른 스레드들에게 사용이 완료된것을 알리는 메서드이다.

Example Code - wait(), notify()/notifyAll()

class Pool{
	List<String> products = 
    new LinkedList<>(Arrays.asList("과자","연필","의자");
    
    public synchronized String get() throws InterruptException{
    	while(products.size == 0){
        	wait();
        }
        return products.remove(0);
    }
    
    public synchronized void add(String value){
    	products.add(value);
        notifyAll();
    }
}

get()메서드는 products 리스트에서 출력하는 메서드이지만, 아무것도 없을 때는 wait()으로 스레드가 대기상태에 있게 만들고, add()메서드는 리스트에 추가하면서 notifyAll()로 기다리고 있는 스레드들이 실행할 수 있게 알려준다.

join()

특정 스레드의 처리가 종료되었을 때, 자신의 스레드의 처리를 실행한다.
특히 어떤 기능을 실행하기 전에 전처리 되야하는 작업들을 우선 실행할 때 사용된다.

Example - join()

public static void main(String[] args){
	Phone calling = new Phone();
    calling.start();
    
    try{
    	calling.join();
    }catch(InterruptException e){
    	e.printStackTrace();
    }
    System.out.println("음식 먹기");
}

calling이라는 스레드가 실행되기 전에 "음식 먹기"이 먼저 출력되면 안되기 때문에 callng.join()메서드로 calling 스레드 먼저 실행한 후, 출력이 되게 한다.

sleep()

인자값으로 주어진 시간동안 스레드의 실행을 잠시 일시정지시킨다.

Example - sleep()

public void run(){
	for(int i=0;i<10;i++){
    	Thread.sleep(2000);
        System.out.println("실행");
    }
}

위의 run()메서드는 10번 반복하는 스레드지만, Thread.sleep(2000)메서드가 있으므로 2초에 한번씩 총 10번 반복하는 스레드가 되었다.

시간 설정 방법

  • Thread.sleep(5000) = 5초
  • TimeUnit.SECONDS.slepp(5) = 5초
  • TimeUnit.MINUTES.sleep(1) = 1분

interrupt()

sleep(), join(), wait()으로 대기중인 상태에 있는 스레드의 대기를 취소한다.

Example - interrupt()

public void run(){
	try{
      for(int i=0;i<10;i++){
          Thread.sleep(2000);
          System.out.println("실행");
      }
    }catch(InterruptException e){
    	System.out.println("interrupt로 인해 Sleep상태 종료함")
    }	
}

위의 스레드의 이름이 t1이라고 하면, t1.start()로 run()메서드를 실행시키고, t1.interrupt()로 run() 메서드 안에 Sleep상태를 취소시킨다. 이 때 InterruptException이 발생하므로 try ~ catch문으로 예외 처리를 한다.

ThreadPool

ThreadPool은 스레드를 효율적으로 관리하기 위한 기능이다.

스레드가 항상 실행 될때마다 생성-대기-종료-삭제 의 주기를 반복하다보면 비효율적인 측면이 있기 때문에 ThreadPool을 사용한다.

  • 실행할 스레드를 미리 생성하여 관리한다.
  • 실행 시 pool에서 스레드를 대여한다.
  • 실행 종료 후에 대여한 스레드를 다시 pool에 반납한다.

Example - ThreadPool

public static void main(String[] args){

	//10개의 스레드용량인 스레드풀을 생성
	ExecutorService threadPool = Executors.newFixedThreadPool(10);
    
    //execute()로 스레드 생성
    threadPool.execute(new Movie());
    threadPool.execute(new Think());
    
    //스레드풀 종료
    threadPool.shutdown();
}

Semaphore

Semaphore란 제한된 자원을 효율적으로 활용하기 위한 기술이다. 특히 실행할 수 있는 스레드 수를 제어하기 위한 카운터를 제공한다.

Example - Semaphore

class SemaphoreThread extends Thread{
	
    private Semaphore semaphore;
    
    SemaphoreThread(SemaphoreThread semaphore){
    	this.semaphore = semaphore;
    }
    
    public void run(){
    	try{
        	this.semaphore.acquire();  // 사용중인 스레드 하나 늘리기
            System.out.println(getName());
            Thread.sleep(5000);
        }catch(InterruptException e){
        	e.printStackTrace();
        }finally{
        	this.semaphore.release(); // 사용중인 스레드 하나 빼기
        }
    }
}
profile
https://github.com/SamGentlee

0개의 댓글