스레드

이정연·2023년 1월 16일

자바기초

목록 보기
4/25

프로세스와 스레드

  • 프로세스 : 실행중인 애플리케이션 즉, 애플리케이션을 실행하면 운영체제로부터 실행에 필요한 만큼의 메모리를 할당받는다.

  • 프로세스는 각각 독립된 메모리 영역(Code, Data, Stack, Heap의 구조)을 할당받는다.

    프로세스 구성

    데이터, 컴퓨터 자원, 스레드

    스레드는 데이터와 애플리케이션이 확보한 자원을 활용하여 소스코드를 실행한다.

  • 스레드 : 하나의 코드 실행 흐름

1. 메인 스레드

  • 자바 애플리케이션을 실행하면 가장 먼저 실행되는 메서드인 main 이 바로 메인 스레드임
  • 메인 스레드는 main 메서드의 코드를 처음부터 끝까지 순차적으로 실행시킴
  • 코드의 끝이나 return문을 만나면 실행을 종료함

2. 멀티 스레드

  • 하나의 프로세스는 여러개의 스레드를 가질 수 있음.
  • 여러개의 스레드를 가진다는 것은 동시에 작업을 수행할 수 있음을 말함 : 멀티스레딩
  • 메신저 프로그램을 이용할때 사진을 업로드 하면서 동시에 메세지를 주고받으려면 멀티스레딩이 필요함.

스레드의 생성과 실행

1. Runnable 인터페이스를 구현한 객체에서 run()을 구현하여 스레드를 생성하고 실행하는 방법

public class ThreadExample1 {
    public static void main(String[] args) {

    }
}

// Runnable 인터페이스를 구현하는 클래스
class ThreadTask1 implements Runnable {
    public void run() {
		for (int i = 0; i < 100; i++) {
            System.out.print("#");
        }
    }
}

Runnable 인터페이스를 구현한 객체 ThreadTask1을 만들고, Runnable의 추상 메서드인 run()을 수행할 코드를 적어주었다.

위의 내용을 바탕으로 스레드를 생성 할 수 있다.

public class ThreadExample1 {
    public static void main(String[] args) {

        // Runnable 인터페이스를 구현한 객체 생성
        Runnable task1 = new ThreadTask1();

        // Runnable 구현 객체를 인자로 전달하면서 Thread 클래스를 인스턴스화하여 스레드를 생성
        Thread thread1 = new Thread(task1);

        // 위의 두 줄을 아래와 같이 한 줄로 축약할 수도 있습니다. 
        // Thread thread1 = new Thread(new ThreadTask1());
		
        thread1.start(); <----------
    }
}

스레드를 생성하고 run()메서드 내부의 코드를 실행하기 위해 start()메서드를 호출하여 스레드를 실행시켜 주었다.

2. Thread 클래스를 상속 받은 하위 클래스에서 run()을 구현하여 스레드를 생성하고 실행하는 방법

public class ThreadExample2 {
    public static void main(String[] args) {

    }
}

// Thread 클래스를 상속받는 클래스 작성
class ThreadTask2 extends Thread {
    public void run() {
		for (int i = 0; i < 100; i++) {
            System.out.print("#");
        }
    }
}

1번과 대부분 같지만 여기서는 Thread 클래스를 상속받았다. 따라서 run()메서들르 오버라이딩 해준다음 메서드 바디에 스레드가 수행할 작업 내용을 작성

이제 1번과 같이 스레드를 생성하는데, 여기서는 Thread 클래스를 직접 인스턴스화 하지 않는다.

public class ThreadExample2 {
    public static void main(String[]args) {

        // Thread 클래스를 상속받은 클래스를 인스턴스화하여 스레드를 생성
        ThreadTask2 thread2 = new ThreadTask2();
        
        thread2.start();
    }
}

1번과 동일하게 start(); 메서드를 실행시켜준다.

3. Runnable 익명 구현 객체를 활용한 스레드 생성 및 실행

public class ThreadExample1 {
    public static void main(String[] args) {
				
        // 익명 Runnable 구현 객체를 활용하여 스레드 생성
        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.print("#");
                }
            }
        });

        thread1.start();
        }
    }
}

1번의 스레드생성을 1줄로 축약한것을 참고하여 보면,

Thread thread1 = new Thread(new ThreadTask1());

Thread 인스턴스를 생성하며 동시에 익명 객체를 생성하고 run(); 메서드를 정의 하고있다.

스레드 이름

  • 메인 스레드는 main 이라는 이름을 가진다,
  • 추가적으로 생성한 스레드는 기본적으로 Thread-n 이라는 이름을 가진다.

1. 스레드 이름 조회

  • 스레드의_참조값.getName() 으로 조회가능.
public class ThreadExample3 {
    public static void main(String[] args) {

        Thread thread3 = new Thread(new Runnable() {
            public void run() {
                System.out.println("Get Thread Name");
            }
        });

        thread3.start();

        System.out.println("thread3.getName() = " + thread3.getName());
    }
}



// 출력결과
Get Thread Name
thread3.getName() = Thread-0

2. 스레드 이름 설정

  • 스레드의_참조값.setName() 으로 설정 가능
public class ThreadExample4 {
    public static void main(String[] args) {

        Thread thread4 = new Thread(new Runnable() {
            public void run() {
                System.out.println("Set And Get Thread Name");
            }
        });

        thread4.start();

        System.out.println("thread4.getName() = " + thread4.getName());

        thread4.setName("Code States"); <----------이름 설정

        System.out.println("thread4.getName() = " + thread4.getName());
    }
}

//출력결과
Set And Get Thread Name
thread4.getName() = Thread-0
thread4.getName() = Code States

스레드 동기화

  • 멀티스레드 프로세스의 경우 두 스레드가 동일한 데이터를 공유하게 되어 문제가 발생할 수 있다.
  • 두개의 작업스레드가 동일 객체를 공유하게되면 출력시 오류가 발생하게 된다.
  • 스레드 동기화를 이해하기 위해서 임계영역과 락의 개념에대한 이해가 필요하다.

예제코드

public class ThreadExample3 {
    public static void main(String[] args) {

        Runnable threadTask3 = new ThreadTask3(); <----------동일한 클래스로 이름이 다른 2개의 스레드를 생성하고있다.
        Thread thread3_1 = new Thread(threadTask3);
        Thread thread3_2 = new Thread(threadTask3);

        thread3_1.setName("김코딩");
        thread3_2.setName("박자바");

        thread3_1.start();
        thread3_2.start();
    }
}

class Account {
}



class ThreadTask3 implements Runnable {
	Account account = new Account(); <-------위의 Account 클래스의 인스턴스를 생성하여 이용
	public void run() {  <--------메서드
    ...
    }
}

스레드 동기화 flow

  • 임계영역으로 설정된 객체가 있다.
  • 현재 이 객체는 다른 스레드에 의해 작업이 이루지지 않고있다.
  • 이때 임의의 스레드 A는 해당 객체에 대한 락을 획득하여 임계영역 내의 코드를 실행할 수 있다.
  • 스레드 A가 임계영역 내의 코들르 실행중일 때에는 다른 스레드들은 락이 없으므로 해당 객체의 코드를 실행할 수 없다.
  • A가 락을 반납하면 다른 스레드들 중 하나가 락을 획득하여 임계 영역내의 코드를 실행 할 수 있게 된다.

임계영역

  • 오로지 하나의 스레드만 코드를 실행할 수 있는 코드 영역을 의미함
  • 임계영역의 설정은 synchronized 키워드를 사용한다.

  • 임계영역을 포함하고 있는 객체에 접근할 수 있는 권한

1. 메서드 전체를 임계영역으로 지정

  • 메서드 반환타입 좌측에 synchronized 키워드를 작성하면 메서드 전체를 임계영역으로 설정할 수 있다.
  • 이경우 메서드를 실행할 스레드는 메서드가 포함된 객체의 락을 얻는다.
class Account {
	...
	public synchronized boolean withdraw(int money) { <----------------메서드에 삽입
	    if (balance >= money) {
	        try { Thread.sleep(1000); } catch (Exception error) {}
	        balance -= money;
	        return true;
	    }
	    return false;
	}
}

위의 경우에 withdraw()가 호출되면 withdraw()를 실행하는 스레드는 withdraw()가 포함된 객체의 락을 얻는다.

2. 특정한 영역을 임계영억으로 지정

  • 임계 영역으로 설정한 블럭의 코드로 코드실행흐름이 진입할 때 해당 코드를 실행하고 있는 스레드가 this에 해당하는 객체의 락을 얻는다.
class Account {
	...
	public boolean withdraw(int money) {
			synchronized (this) { <---------------------여기가다르다, 영역에 삽입
			    if (balance >= money) {
			        try { Thread.sleep(1000); } catch (Exception error) {}
			        balance -= money;
			        return true;
			    }
			    return false;
			}
	}
}

스레드의 상태와 실행제어

  • 엄밀히 말하면 start()는 스레드를 실행시키는 메서드는 아니다.
  • start()는 스레드를 실행 대기 상태로 만들어주며, 대기하다가 운영체제가 적절한 때에 스레드를 실행시킨다.
  • 즉, 스레드의 상태를 바꿀 수 있는 메서드가 존재한다.

스레드 실행제어 메서드

1. sleep

  • milliSecond 동안 스레드를 잠시 멈춥니다.
  • sleep()은 Thread의 클래스 메서드이기 때문에 sleep을 호출할 때에는 Thread.sleep(1000);과 같이 클래스를 통해서 호출한다.
  • sleep()을 호출하면 코드를 실행한 스레드의 상태가 실행 상태에서 일시정지(TIMED_WAITING) 상태로 전환된다.
  • interrupt()를 호출한 경우엔 실행대기 상태로 복귀한다.
try { Thread.sleep(1000); } catch (Exception error) {}

interrupt()를 사용하여 스레드를 실행 대기 상태로 복귀시키는 것을 대비하여 sleep() 는 반드시 try … catch문을 이용해야 함.
왜냐하면 interrupt()가 호출되면 기본적으로 예외가 발생하기 때문임.

2. interrupt

  • sleep(), wait(), join()에 의해 일시 정지 상태에 있는 스레드들을 실행 대기 상태로 복귀시킨다.
  • 위 3가지 상태에서 [멈춰있는 스레드.interrupt()] 를 호출시키면, 예외가 발생하면서 일시정지가 풀리게 된다.
profile
반갑습니다.

0개의 댓글