Thread / JVM

jungseo·2023년 5월 8일
0

Java

목록 보기
8/10

Process

실행 중인 어플리케이션

  • 어플리케이션을 실행하면 OS로부터 실행에 필요한 메모리를 할당받아 프로세스가 됨
  • 데이터, 자원, 스레드로 구성

Thread

데이터와 어플리케이션이 확보한 자원을 활용하여 소스 코드를 실행 -> 하나의 코드 실행 흐름

Main thread

  • 자바 어플리케이션 실행 시 main thread가 main 메서드를 실행
  • 소스 코드가 메인 스레드만 가지고 있다면 싱글 스레드 프로세스

Multi thread

  • 하나의 프로세스는 여러개의 프로세스를 가질 수 있음 -> 여러 동시 작업이 가능하다
    ex) 메신저 앱 : 사진을 업로드하며 메세지 전송

1. thread 생성

  • Runnable 인터페이스를 구현한 객체에서 run() 구현하여 생성
  • Thread 클래스를 상속받은 하위 클래스에서 run() 구현하여 생성
public class ProducingThread {
    public static void main(String[] args) {

//        Runnable task1 = new TheadTask1();
//        Thread thread1 = new Thread(task1);
//        아래의 코드로 축약 가능

//        Runnable 인터페이스 구현한 객체에서 thread 생성
        Thread thread1 = new Thread(new ThreadTask1());

//        Runnable 익명 객체에서 thread 생성
        Thread thread2 = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.print("2");
                }
            }
        });

//        Thread 클래스를 상속받은 하위 클래스에서 thread 생성
        Thread thread3 = new ThreadTask2();

//        Thread 익명 하위 객체를 통한 thread 생성
        Thread thread4 = new Thread() {
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.print("4");
                }
            }
        };

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();

        for (int i = 0; i < 100; i++) {
            System.out.print("@");
        }
    }
}

class ThreadTask1 implements Runnable {
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.print("1");
        }
    }
}
class ThreadTask2 extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.print("3");
        }
    }
}
  • 스레드가 병렬로 실행되어 "@", "1", "2", "3", "4"가 섞여서 출력

2. thread의 이름, 주소값 접근

  • 참조값.getName()
  • 참조값.setName()
  • Thread.currentThread() : 실행중인 스레드의 주소값에 접근
public class NameOfThread {
    public static void main(String[] args) {

        Thread exampleThread = new Thread() {
            public void run() {
                System.out.println("Thread running");
            }
        };
        exampleThread.start();

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

        exampleThread.setName("Hello");
        System.out.println("exampleThread.getName() = " + exampleThread.getName());

//        Thread.currentThread() : 현재 실행중인 thread의 주소값에 접근
        System.out.println(Thread.currentThread().getName());
    }
}

출력
Thread running
exampleThread.getName() = Thread-0
exampleThread.getName() = Hello
main

3. thread 동기화

  • 멀티 스레드 프로세스에서 여러 스레드가 같은 데이터를 공유할 때 필요
  • 임계 영역으로 설정된 객체가 다른 스레드에 의해 작업중이지 않을때 해당 객체에 대한 락을 가지고 임계 영역 내의 코드 실행 가능
  • 임계 영역 내의 코드를 모두 실행하면 락을 반납

임계 영역(Critical section)

하나의 스레드만 코드를 실행 할 수 있는 영역

  • 메서드 전체 지정
    반환 타입 앞에 synchronized 작성
    메서드가 호출 되었을 때 메서드를 실행할 스레드가 메서드가 포함된 객체의 락을 얻음
  • 특정 영역 지정
    synchronized (임계 영역으로 지정할 객체의 참조) {내용 작성}

블록 안 코드로 진입할때 해당 코드를 실행하고 있는 스레드가 락을 얻음

락 (Lock)

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

public class SynchronizeTest {
    public static void main(String[] args) {
//        Thread thread1 = new Thread(new ThreadTask());
//        Thread thread2 = new Thread(new ThreadTask());
//        -> thread1, 2가 각각의 ThreadTask 객체를 가지고 실행됨

        Runnable threadTask = new ThreadTask();
        Thread thread1 = new Thread(threadTask);
        Thread thread2 = new Thread(threadTask);
//        -> thread1, 2가 하나의 ThreadTask 객체를 공유

        thread1.setName("춘식");
        thread2.setName("봉남");

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

    }
}
class Account {
    private int balance = 1000;

    public int getBalance() {
        return balance;
    }

    public synchronized boolean withdraw (int money) {
        if (money <= balance) {
            try { Thread.sleep(1000); } catch (Exception error){}
            balance -= money;
            return true;
        }
        return false;
    }
}
class ThreadTask implements Runnable {
    Account account = new Account();

    public void run() {
        while (account.getBalance() > 0) {
            int money = (int) (Math.random() * 3 + 1) * 100;

            boolean denied = !account.withdraw(money);

            System.out.println(String.format("Withdraw %d₩ By %s. Balance : %d %s",
                    money, Thread.currentThread().getName(), account.getBalance(), denied ? "=> DENIED" : "")
            );
        }
    }
}

출력
Withdraw 200₩ By 춘식. Balance : 800
Withdraw 300₩ By 봉남. Balance : 500
Withdraw 200₩ By 춘식. Balance : 300
Withdraw 200₩ By 봉남. Balance : 100
Withdraw 100₩ By 봉남. Balance : 0 => DENIED
Withdraw 100₩ By 춘식. Balance : 0

임계영역 지정을 안했을때
Withdraw 300₩ By 춘식. Balance : 700
Withdraw 200₩ By 봉남. Balance : 700
Withdraw 300₩ By 봉남. Balance : 400
Withdraw 100₩ By 춘식. Balance : 300
Withdraw 300₩ By 춘식. Balance : -300
Withdraw 300₩ By 봉남. Balance : -300


4. thread 상태

  • RUNNABLE 상태에서 바로 실행되지 않고 OS의 스케줄러에 의해 선택을 받아 실행
  • 스케줄러는 OS에서 스레드나 프로세스들의 CPU 사용을 조율하여 언제 얼마나 할당할지 결정

sleep(long milliSecond)

milliSecond 동안 스레드를 멈춤

  • Thread 클래스의 메서드 (Thread.sleep(1000)로 호출 권장)
  • TIMED_WAITING 상태로 전환
  • 전달한 인자 만큼의 시간이 경과 하거나
    interrupt() 호출 시 RUNNABLE 상태로 복귀
  • interrupt() 호출 시 예외가 발생하기 때문에 try-catch문으로 sleep()를 감싸줘야함

interrupt()

일시 중지 상태인 스레드를 실행 대기 상태로 복귀

  • sleep(), wait(), join()에 의해 일시 정지 상태에 있는 스레드들을 실행 대기 상태로 복귀
  • interrupt() 호출 시 예외가 발생하여 그에 따라 정지가 풀림
public class ThreadStateTest {
    public static void main(String[] args) {
        Thread thread = new Thread() {
            public void run() {
                try {
                    while (true) Thread.sleep(1000);
                }
                catch (Exception e) {}
                System.out.println("woke up");
            }
        };
        System.out.println("thread.getState() = " + thread.getState());
//        thread.getState() = NEW
        thread.start();

        System.out.println("thread.getState() = " + thread.getState());
//        thread.getState() = RUNNABLE
        while (true) {
            if (thread.getState() == Thread.State.TIMED_WAITING) {
                System.out.println("thread.getState() = " + thread.getState());
//                thread.getState() = TIMED_WAITING
                break;
            }
        }
//        sleep 중인 thread에 예외 발생 -> catch문 실행 후 실행 대기 상태
        thread.interrupt();
//        woke up

        while (true) {
            if (thread.getState() == Thread.State.RUNNABLE) {
                System.out.println("thread.getState() = " + thread.getState());
//                thread.getState() = RUNNABLE
                break;
            }
        }
        while (true) {
            if (thread.getState() == Thread.State.TERMINATED) {
                System.out.println("thread.getState() = " + thread.getState());
//                thread.getState() = TERMINATED
                break;
            }
        }
    }
}

yield()

스케줄러에 의해 할당받은 실행시간을 다른 스레드에 양보

  • 실행 대기 상태로 바뀌며 남은 실행시간을 실행 대기열 상 우선순위가 높은 다른 스레드에 양보
public void run() {
		while (true) {
				if (example) {
						... // 무의미한 반복을 멈추고 실행 대기상태로 바뀜
				}
				else Thread.yield();
		}
}

join() join(long milliSecond)

다른 스레드의 작업이 끝날 때까지 기다림

  • 특정 스레드 작업중 일시 중지 상태로 만듦
  • 전달한 인자만큼 시간이 흐르거나
    interrupt()가 호출되거나
    join() 호출 시 지정한 다른 스레드가 작업을 마치면
    RUNNABLE 상태로 복귀
  • sleep()과 유사
    • 호출한 스레드가 일시 중지 상태
    • try-catch문으로 감싸서 사용해야함
    • interrupt()로 실행 대기 상태로 복귀 가능
  • sleep()은 Thread 클래스의 static 메서드
  • join()은 특정 스레드에 대해 동작하는 인스턴스 메서드
public class joinTest {
    public static void main(String[] args) {
        SumThread sumThread = new SumThread();

        sumThread.setTo(10);

        sumThread.start();

//        main 스레드가 sumThread의 작업이 끝날 때까지 대기
//        주석 처리시 출력 : 1부터 10까지의 합 = 0
        try { sumThread.join(); } catch (Exception e) {}

        System.out.println(String.format("1부터 %d까지의 합 = %d", sumThread.getTo(), sumThread.getSum()));
    }
}

class SumThread extends Thread {
    private long sum;
    private int to;

    public long getSum() {
        return sum;
    }
    public int getTo() {
        return to;
    }
    public void setTo(int to) {
        this.to = to;
    }

    @Override
    public void run() {
        for (int i = 0; i <= to; i++) {
            sum += i;
        }
    }
}

wait(), notify()

스레드 간 협업에 사용

  • 두 스레드가 교대로 작업을 처리해야 할 때
  1. 스레드A가 공유 객체의 자신의 작업을 완료
  2. 스레드B와 교대하기 위해 notify() 호출
  3. 스레드B가 실행 대기 상태
  4. 스레드A는 wait() 호출하여 일시 정지 상태
public class Wait_NotifyTest {
    public static void main(String[] args) {
        WorkObject sharedObject = new WorkObject();

        ThreadA threadA = new ThreadA(sharedObject);
        ThreadB threadB = new ThreadB(sharedObject);

        threadA.start();
        threadB.start();
    }
}

class WorkObject {
    public synchronized void methodA() {
        System.out.println("ThreadA의 methodA Working");
        notify();
        try { wait(); } catch (Exception e) {}
    }

    public synchronized void methodB() {
        System.out.println("ThreadB의 methodB Working");
        notify();
        try { wait(); } catch (Exception e) {}
    }
}
class ThreadA extends Thread {
    private WorkObject workObject;

    public ThreadA(WorkObject workObject) {
        this.workObject = workObject;
    }
    public void run() {
        for (int i = 0; i < 5; i++) {
            workObject.methodA();
        }
    }
}
class ThreadB extends Thread {
    private WorkObject workObject;

    public ThreadB(WorkObject workObject) {
        this.workObject = workObject;
    }
    public void run() {
        for (int i = 0; i < 5; i++) {
            workObject.methodB();
        }
    }
}

출력
ThreadA의 methodA Working
ThreadB의 methodB Working
ThreadA의 methodA Working
ThreadB의 methodB Working
ThreadA의 methodA Working
ThreadB의 methodB Working
ThreadA의 methodA Working
ThreadB의 methodB Working
ThreadA의 methodA Working
ThreadB의 methodB Working

JVM (Java Virtual Machine)

  • 자바 프로그램을 시행시키는 별도의 프로그램
  • 프로그램이 실행되기 위해서 컴퓨터의 자원을 프로그램이 할당 받아야 하는데 프로그램이 운영체제에 필요한 자원을 요청하는 방식이 운영체제마다 다름
  • 자바는 JVM을 매개해 운영체제와 소통하여 운영체제로부터 독립적으로 동작 가능

Stack Area

Last In First Out

  • 메서드 호출시 Method Frame 생성
  • 메서드 내부에서 사용하는 참조변수, 매개변수, 지역변수, 반환값, 연산시 일어나는 값들이 임시로 저장
  • 호출 순서대로 Stack에 쌓이고 메서드의 동작이 완료되면 역순으로 제거

Heap Area

JVM 작동시 영역 자동 생성

  • 객체, 인스턴스 변수, 배열이 저장
  • 실제 객체의 값이 저장
  • Heap 영역의 객체 대부분 일회성이며 메모리에 남아 있는 기간이 짧다는 전제로 설계

Young 영역

  • 새로 생성된 객체가 할당
  • 많은 객체가 생성, 제거 반복

Old 영역

  • Young 영역에서 상태를 유지하고 살아남은 객체들이 복사
  • Young 영역보다 크게 할당되고 가비지는 적게 발생

Gargage Collection

프로그램에서 더 이상 사용하지 않는 객체를 찾아 제거하여 메모리를 확보

Heap Area의 Young의 가비지 컬렉터는 Minor GC
Old의 가비지 컬렉터는 Magor GC

  • Stop The World
    : 가비지 컬렉션을 실행시키기 위해 JVM이 어플리케이션의 실행을 멈추는 작업
    가비지 컬렉션을 실행하는 스레드를 제외한 모든 스레드의 작업이 중단
  • Mark and Sweep
    Mark : 사용되는 메모리와 사용하지 않는 메모리를 식별하는 작업
    Sweep : Mark단계에서 사용되지 않는 것으로 식별된 메모리를 해제하는 작업

0개의 댓글