Notice
Recent Posts
Recent Comments
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
10-11 00:15
Archives
Today
Total
관리 메뉴

Developer_Neo

[코드스테이츠 백엔드 2기(40기) SEB BE] 19일차 본문

코드스테이츠

[코드스테이츠 백엔드 2기(40기) SEB BE] 19일차

_Neo_ 2022. 7. 19. 14:49
반응형

오늘 나의 학습 목표는 무엇인가요?

- 스레드가 무엇인지 설명하고 상태를 이해해 동기화와 실행, 제어를 해보자
- JVM의 내부 구조를 살펴보자

Thread(쓰레드)

https://gmlwjd9405.github.io/2018/09/14/process-vs-thread.html


[운영체제적 측면]

Thread가 나오게 된 배경

1.  process를 많이 사용하면 할수록 시스템이 무거워진다

2.  process 사용 시 시스템상 성능저하가 생길 수 있다

 

2번의 원인

1. process 생성시간이 오래걸린다.
     why?  process는 thread가 나오기 전에 code data heap stack영역에 해당하는 것 이외의 것들인 많은 용량을 차지하는 것들을 가지고 있어야했다. 즉, 똑같은 프로그램을 돌리게 되는 경우에도 code를 똑같이 복사해서 각각 가지고 있어야하기 때문이다.
2. Context Switching Overhead가 크다
 
3. Synchronization Overhead인 동기화 부하가 크다.
 
    why? 동기화시 context switching 자주 일어나기 때문에 동기화 부하도 크다.
 

멀티 프로세스의 특징을 유지하면서 단점을 어느 정도 극복하기 위해서 등장한 것이 바로, '쓰레드(Thread)'이다

 

프로세스의 실행단위이자 CPU 실행의 기본단위는 쓰레드입니다.


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

 

하나의 프로세스는 여러개의 쓰레드를 가질 수 있응며 이것을 멀티 쓰레드 프로세스라고 한다.

 

멀티 스레딩 : 여러 스레드가 동시에 작업을 수행할 수 있음


 

작업 스레드 생성과 실행

 

- 자바는 객체지향 언어이므로 모든 자바 코드는 클래스 안에 작성됩니다. 따라서 스레드가 수행할 코드도 클래스 내부에 작성해주어야 하며, run()이라는 메서드 내에 스레드가 처리할 작업을 작성하도록 규정되어져 있습니다

 

run() 메서드Runnable 인터페이스와 Thread 클래스에 정의하는데 2가지 방법이 있다.

 

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

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

    }
}

// Runnable 인터페이스를 구현하는 클래스
class ThreadTask1 implements Runnable {
    public void run() {

    }
}


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

    }
}

class ThreadTask1 implements Runnable {

    // run() 메서드 바디에 스레드가 수행할 작업 내용 작성
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.print("#");
        }
    }
}


//---------------------------------------------3--------------------------------------------
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());
        
         // 작업 스레드를 실행시켜, run() 내부의 코드를 처리하도록 합니다. 
        thread1.start(); //레드를 생성하는 것만으로는 run() 내부의 코드가 실행되지않기에 내부 코드 실행을 위해서라면 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("#");
        }
    }
}
출력결과
@@@@@@@@@@@######@@@@@############################ @#########@@@@@@@@@@@@@@@@############@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@##@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@###########################################

이렇게 나오는 이유는 main쓰레드와 thread1의 쓰레드중 무엇이 먼저 실행되는지 정해져 있지 않기에 랜덤하게 점유를 하면서 출력이 되는 모습니다.

즉, 메인 스레드와 작업 스레드가 동시에 병렬로 실행되면서 각각 main 메서드와 run() 메서드의 코드를 실행시켰기 때문에 두 가지 문자가 섞여서 출력된 것입니다

 

 

 

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

 

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

    }
}

// Thread 클래스를 상속받는 클래스 작성
class ThreadTask2 extends Thread {
    public void run() {

    }
}


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

    }
}

class ThreadTask2 extends Thread {
	
    // run() 메서드 바디에 스레드가 수행할 작업 내용 작성
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.print("#");
        }
    }
}


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

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

        // 작업 스레드를 실행시켜, run() 내부의 코드를 처리하도록 합니다. 
        thread2.start();

        // 반복문 추가
        for (int i = 0; i < 100; i++) {
            System.out.print("@");
        }
    }
}

class ThreadTask2 extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.print("#");
        }
    }
}

스레드의 이름

 

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());
    }
}

 

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());
    }
}

 

3. 스레드 인스턴스의 주소값 얻기 - Thread.currentThread().getName()

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

        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });

        thread1.start();
        System.out.println(Thread.currentThread().getName());
    }
}

 


스레드의 동기화

- 싱글 스레드 프로세스는 데이터에 단 하나의 스레드만 접근하므로 데이터 공유에 대해 생각을 하지 않아도 되지만 멀티 스레드 프로세스의 경우, 두 스레드가 동일한 데이터를 공유하게 되어 문제가 발생할 수 있습니다.

프로세스의 경우 따로 따로 데이터를 다 가지기에 문제가 생기지 않았지만 하나의 프로세스당 여러 쓰레드가 있을때 쓰레드들은 Heap, Code,Data 영역을 공유하기 때문에 생기는 문제이다.

 

스레드를 일시 정지시키는 메서드  - Thread.sleep(1000);

   -> 어떤 스레드가 일시 정지되면, 대기열에서 기다리고 있던 다른 스레드가 실행 됨

    -> Thread.sleep()은 반드시 try … catch문의 try 블럭 내에 작성해주어야 함

class Account {

    // 잔액을 나타내는 변수
    private int balance = 1000;

    public int getBalance() {
        return balance;
    }
		
    // 인출 성공 시 true, 실패 시 false 반환
    public boolean withdraw(int money) {

        // 인출 가능 여부 판단 : 잔액이 인출하고자 하는 금액보다 같거나 많아야 합니다.
        if (balance >= money) {

            // if문의 실행부에 진입하자마자 해당 스레드를 일시 정지 시키고, 
            // 다른 스레드에게 제어권을 강제로 넘깁니다.
            // 일부러 문제 상황을 발생시키기 위해 추가한 코드입니다.
            try { Thread.sleep(1000); } catch (Exception error) {}

            // 잔액에서 인출금을 깎아 새로운 잔액을 기록합니다.
            balance -= money;

            return true;
        }
        return false;
    }
}

 

 

임계 영역(Critical section)과 락(Lock)

entry section
critical section
exit section

위와 같이 이루어져 있을 때 임계 영역은 오로지 하나의 스레드만 코드를 실행할 수 있는 코드 영역을 의미하며, 락은 임계 영역을 포함하고 있는 객체에 접근할 수 있는 권한을 의미합니다.

락은 entry section에서 이루어져야 합니다. 왜냐하면 공유자원에 대해 한 쓰레드만 접근하게 할 것이기 때문이다.

 

특정 코드 구간을 임계 영역으로 설정할 때에는 synchronized라는 키워드를 사용합니다. 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;
	}
}

 

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

- synchronized 키워드와 함께 소괄호(()) 안에 해당 영역이 포함된 객체의 참조를 넣고, 중괄호({})로 블럭을 열어, 블럭 내에 코드를 작성

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;
			}
	}
}

 

스레드의 상태와 실행 제어 메서드

5가지를 가지고 있다

1. new

2. ready

3. waiting

4. running

5. terminated

 

sleep(long milliSecond) : milliSecond 동안 스레드를 잠시 멈춥니다.

-> 이거를 호출한 경우에는 이미 running상태였는데 sleep이라는 wait를 하게 된 것으로 waiting상태로 가게 된다. 이때 위에서 설정한 시간이 다 되면 ready상태로 돌아가거나 interrupt를 호출하는 경우 ready 상태로 돌아가게 된다.

- 주의 점:  sleep()을 사용할 때에는 아래처럼 try … catch 문으로 sleep()을 감싸주어야 합니다.

 

interrupt() : 일시 중지 상태인 스레드를 실행 대기 상태로 복귀시킵니다.

- sleep(), wait(), join()에 의해 일시 정지 상태에 있는 스레드들을 실행 대기 상태로 복귀시킨다.

 

public class ThreadExample5 {
    public static void main(String[] args) {
        Thread thread1 = new Thread() {
            public void run() {
                try {
                    while (true) Thread.sleep(1000);
                }
                catch (Exception e) {}
                System.out.println("Woke Up!!!");
            }
        };

        System.out.println("thread1.getState() = " + thread1.getState());
        
        thread1.start();

        System.out.println("thread1.getState() = " + thread1.getState());

        while (true) {
            if (thread1.getState() == Thread.State.TIMED_WAITING) {
                System.out.println("thread1.getState() = " + thread1.getState());
                break;
            }
        }

        thread1.interrupt();//-----------------------------------------------

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

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

 

yield() : 다른 스레드에게 실행을 양보합니다.

- 다른 스레드에게 자신의 실행 시간을 양보합니다. 예를 들어, 운영 체제의 스케줄러에 의해 3초를 할당 받은 스레드 A가 1초 동안 작업을 수행하다가 yield()를 호출하면 남은 실행 시간 2초는 다음 스레드에게 양보한다.

public void run() {
		while (true) {
				if (example) {
						...
				}
				else Thread.yield();
		}
}

 

join() : 다른 스레드의 작업이 끝날 때까지 기다립니다.

- 특정 스레드가 작업하는 동안에 자신을 일시 중지 상태로 만드는 상태 제어 메서드

 

public class ThreadExample {
    public static void main(String[] args) {
        SumThread sumThread = new SumThread();

        sumThread.setTo(10);

        sumThread.start();

        // 메인 스레드가 sumThread의 작업이 끝날 때까지 기다립니다. 
        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;
    }

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

 

  • sleep()은 Thread 클래스의 static 메서드입니다. 반면, join()은 특정 스레드에 대해 동작하는 인스턴스 메서드입니다.
    • 예 : Thread.sleep(1000);
    • 예 : thread1.join();

 

wait(), notify() : 스레드 간 협업에 사용

스레드A와 스레드B가 공유 객체를 두고 협업하는 상황을 가정해봅시다. 스레드간 협업은 아래의 플로우로 진행됩니다.

먼저, 스레드A가 공유 객체에 자신의 작업을 완료합니다. 이 때, 스레드B와 교대하기 위해 notify()를 호출합니다. notify()가 호출되면 스레드B가 실행 대기 상태가 되며, 곧 실행됩니다. 이어서 스레드A는 wait()을 호출하며 자기 자신을 일시 정지 상태로 만듭니다.

이후 스레드B가 작업을 완료하면 notify()를 호출하여 작업을 중단하고 있던 스레드A를 다시 실행 대기 상태로 복귀시킨 후, wait()을 호출하여 자기 자신의 상태를 일시 정지 상태로 전환합니다.

 

public class ThreadExample5 {
    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(InterruptedException e) {}
    }

    public synchronized void methodB() {
        System.out.println("ThreadB의 methodB Working");
        notify();
        try { wait(); } catch(InterruptedException e) {}
    }
}

class ThreadA extends Thread {
    private WorkObject workObject;

    public ThreadA(WorkObject workObject) {
        this.workObject = workObject;
    }

    public void run() {
        for(int i = 0; i < 10; 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 < 10; i++) {
            workObject.methodB();
        }
    }
}

 

 

 

 

 

 


오늘 학습 내용 중 새롭게 배운 내용은 무엇인가요?
       -  

오늘 새롭게 학습한 내용을 다른 사람에게 설명할 수 있나요?
      - 네

오늘 학습한 내용 중 아직 이해되지 않은 불확실한 내용은 무엇인가요?
     -  없습니다.

이해되지 않은, 불확실한 내용을 보완하기 위해서 나는 무엇을 할 수 있을까요?
     -  구글링을 해본다.

나의 오늘 학습 만족도는 몇 점인가요?
     - 90점 (아는 내용들이라고 설렁설렁 넘어간 것들이 있는 것같다.)
반응형
Comments