ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 자바 - thread(1) 기본특성 (1~4)
    언어/JAVA 2023. 3. 20. 12:49

    1. 프로세스와 쓰레드 

     

    프로세스:  실행 중인 프로그램이다. 프로그램을 실행하면 OS로부터 필요한 자원을 할당받아 프로세스가 된다.

                      -프로세스는 데이터 메모리 등의 자원과 쓰레드로 구성되어 있다.

                      - 모든 프로세스는 하나 이상의 쓰레드가 존재하며, 둘 이상의 쓰레드를 가진 프로세스를 멀티쓰레드 프로세스라 한다.

     

    쓰레드 : 프로세스 내의 자원을 이용해서 실제 작업을 수행 (프로세스의 실행 단위)

     

     

    출처:https://resilient-923.tistory.com/364

    - 위와 같이 하나의 프로세스에 자원과 쓰레드가 있다. 쓰레드 각각은 내부에 스택과 고유한 프로그램 카운터 값을 가지고 있으며, 

    프로세스 내에 자원들을 공유하며 각자의 작업을 처리한다.

     

    **이때 각 쓰레드에 스택 메모리를 할당하는 이유는 스레드마다 독립적으로 함수를 호출하고, 자원을 사용하는 등 독립적 작업을 처리하기 위한 최소한의 작업대를 주는 것과 같다

     

    **고유의 프로그램 카운터 값을 가지는 이유는 쓰레드의 실행 순서를 파악하기 위해서 이다. 쓰레드는 cpu를 할당 받았다가, 

    cup 스케쥴러에 의해 다시 실행순서가 변경됨으로 프로세스 내 쓰레드를 어디까지 실행했는지 항상 기억할 필요가 있다. 

    이를 위해 프로그램 카운터 값을 가지는 것이다.

     

    멀티태스킹과 멀티쓰레딩

     

    -멀티 태스킹: 여러 개의 프로세스가 동시에 실행됨을 의미 

     

    -멀티 쓰레딩 : 하나의 프로세스 내에서 여러 쓰레드가 동시에 작업을 수행하는 것을 의미한다.

                            -cpu의 코어가 한 번에 하나의 작업만 수행할 수 있으므로, 실제 동시 처리되는 작업의 개수는 코어의 개수와 일치

                            -하지만 쓰레드 수는 코어의 개수보다 일반적으로 많음 -> 스레드가 아주 짧은 시간 동안 여러 작업을 번갈아 가며 수행하여                              마치 동시에 수행되는 것 처럼 보임

     

    멀티쓰레딩의 장단점

     

      장점: - cpu사용률을 향상시킴

               - 자원을 보다 효율적으로 사용

               - 사용자에 대한 응답성 향상

               - 작업이 분리되어 코드 간결

     

      단점: - 동기화에 주의해야함

               - 교착상태 및 기아에 빠질 수 있으므로, 프로그램을 잘짜야함

     

    2. 쓰레드의 구현과 실행

     

    쓰레드를 구현하는 방법은

     

    1. Thread클래스를 상속받는 방법

    2. Runnerble인터페이스를 구현하는 방법 ----일반적으로 추천 Thread를 상속받으면, 다른 클래스 상속을 못받음 

     

    쓰레드를 구현하는 방법은 둘 중 어느방법을 선택하더라도, run()메서드의 몸통만 채우면 된다.

     

    class ThreadEx extends Thread{
        public void  run(){
            for(int i=0; i<5; i++){
                System.out.println(getName());
            }
        }
    }
    
    //getName()은  쓰레드의 이름을 반환하는 Thread클래스의 메서드이다. 상속받았으므로 그냥 사용 가능하다.
    class ThreadEx2 implements Runnable{
        @Override
        public void run() {
            for(int i=0; i<5; i++){
                System.out.println(Thread.currentThread().getName());
            }
        }
    }
    
    //Thread클래스를 직접 상속받은게 아님으로 getName()을 사용하기 위해서 
    //Thread의 static 메서드인 currentThread()를 불러와서 getName()을 사용한다.
    
    static Thread currentThread() < 현재 실행중인 쓰레드의 참조를 반환

    실제 인스턴스의 생성은 다음과 같다.

    public class ThreadEX1 {
        public static void main(String[] args){
            ThreadEx t1 = new ThreadEx();
    
            Runnable r = new ThreadEx2();
            Thread t2 = new Thread(r);
    		
            //Thread t2 = new Thread(new ThreadEx2()) Runnerble을 구현한 클래스를 통해 
                                                      직접 Thread인스턴스 생성 
            
            t1.start(); //쓰레드 시작
            t2.start();
        }
    
    }

    *Runneble 인터페이스에는 run()만 정의되어 있다.

    -> Thread객체에는 생성자 Thread(Runnerble r)과 인스턴스 변수로 Runnerble r을 지정해 두어서 Runnerble을 구현한 클래스의 인스턴스로 다양한 Thread객체의 메서드를 사용할 수 있도록 함  

     

    쓰레드의 실행 -start()

     

    start()를 호출하면, 일단 대기상태 -> 자신의 차례가 오면 실행 

    t1.start() t2.start() -->한 개 이상의 쓰레드가 있으면, 무엇이 먼저 실행될지 모른다. (순서대로x, 실행순서는 os스케줄러가 결정) 

     

    한번 실행이 종료된 쓰레드는 다시 실행할 수 없음 -> 한 번 더 수행해야한다면, 새로운 쓰레드를 생성해서 다시 start()를 호출해야함

    *종료된 쓰레드를 다시 호출하면, IllegalThreadStateException발생

     

    2.1 start()와 run()

     

    쓰레드를 실행시킬 때 run()이 아닌 start()를 호출하는 이유

    -main메서드에서 run()을 호출: 쓰레드를 실행시키는 것이 아니라 단순히 클래스에 선언된 메서드를 호출하는 것이다

                                                       (새로운 호출스택(작업대)의 생성이 아니라, 기존 호출스택에서 run()을 실행하는 것 )

     

    -start()의 호출: 쓰레드가 작업을 실행하는데 필요한 호출스택을 생성한 다음에 run()을 호출해서, 새로운 호출스택에 run()을 첫번째로 올려놓는다.  (마치 프로그램이 시작되면, 호출스택에 main()이 제일 첫번째로 호출되는 것과 같다.)

     

    *모든 쓰레드는 독집적인 작업을 수행하기 위해 자신만의 호출스택이 필요 -> 새로운 쓰레드를 생성하고 실행시킬 때마다 새로운 호출스택이 생성되고 쓰레드가 종료되면 소멸 (main메서드로 실행 하던 때와 같다.)

     1-main에서 start()호출

    2-start()가 새로운 쓰레드 생성하고, 쓰레드가 사용할 호출스택 생성

    3-새로운 호출스택에 run()이 호출되어, 쓰레드가 독립된 공간에서 작업 수행

    4-스케줄러가 정한 순서에 의해 번갈아 가면서 실행됨

     

     

    >한 쓰레드 내에 호출스택에서 가장 위에 있는 메서드가 현재 실행중인 메서드이고, 나머지는 대기상태이다. 

    >쓰레드가 둘 이상일 때는 호출스택의 최상위에 있는 메서드라도 대기상태에 있을 수 있다. 

    >이는 스케줄러가 정한 작업 순서와 시간이 있는데, 작업을 마치지 못한 쓰레드는 다시 자신의 차례가 돌아올 때 까지 대기상태가 되기 때문이다. 

    >또한 run()이 종료되면 호출스택이 비워지며, 쓰레드가 종료된다. 

     

    main쓰레드: main메서드의 작업을 수행하는 것도 쓰레드이다. 멀티쓰레드를 사용하기 전에는 main메서드가 종료되면 프로그램이 종료되었으나, 쓰레드가 두개 이상이라면, main이 종료되더라도 프로그램이 종료되지 않을  수  있다.

    >실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다. 

     

    public class ThreadEX1 {
        public static void main(String[] args){
           ThreadEx tx = new ThreadEx();
           tx.start(); 
        }
    }
    class ThreadEx extends Thread{
        public void  run(){
            throwException(); //예외를 발생시킴
        }
        public void throwException(){
            try{
                throw new Exception();
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
    //결과
    java.lang.Exception
    	at ThreadEx.throwException(ThreadEX1.java:15)
    	at ThreadEx.run(ThreadEX1.java:11)

    에러메세지를 보면, 호출 스택의 첫 메서드가 main이 아니라 run이다. (main은 이미 종료됨)

    한 쓰레드가 예외를 발생시켜 종료되어도 다른 쓰레드의 실행에는 영향을 미치지 않는다.

     

    public class ThreadEX1 {
        public static void main(String[] args){
           ThreadEx tx = new ThreadEx();
           tx.run();
        }
    }
    class ThreadEx extends Thread{
        public void  run(){
            throwException();
        }
        public void throwException(){
            try{
                throw new Exception();
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
    //결과
    java.lang.Exception
    	at ThreadEx.throwException(ThreadEX1.java:15)
    	at ThreadEx.run(ThreadEX1.java:11)
    	at ThreadEX1.main(ThreadEX1.java:4)

    새로운 쓰레드가 생성된 것이아니라 기존의 호출스택에서 run메서드를 호출한 결과가 나왔다.

     

    3.싱글쓰레드와 멀티쓰레드

     

    먼저 싱글쓰레드와 멀티쓰레드로 작업을 처리하는 경우에 어떤차이가 있는지 예제를 통해 알아보자 

    -싱글쓰레드

    public class ThreadEX1 {
        public static void main(String[] args){
          long startTime = System.currentTimeMillis();
    
          for(int i=0; i<300;i++){
              System.out.printf("%s",new String("a"));
          }
            System.out.print("소요시간 1=" + (System.currentTimeMillis()-startTime));
    
            for(int i=0; i<300;i++){
                System.out.printf("%s",new String("b"));
            }
            System.out.print("소요시간 2=" + (System.currentTimeMillis()-startTime));
        }
    }

    a를 모두 출력하고 난 뒤에 b를 출력한다. 

     

    -멀티쓰레드 

    public class ThreadEX1 {
        static long startTime =0;
        public static void main(String[] args) {
            Threadex th = new Threadex();
            th.start();
            startTime = System.currentTimeMillis();
            for(int i=0; i<300;i++){
                System.out.printf("%s",new String("a"));
            }
            System.out.print("소요시간 1 = "+(System.currentTimeMillis()- ThreadEX1.startTime));
    
        }
    
    }
    
    class Threadex extends Thread{
    
        @Override
        public void run(){
            for(int i=0; i<300;i++){
                System.out.printf("%s",new String("b"));
            }
            System.out.print("소요시간 2 = "+(System.currentTimeMillis()- ThreadEX1.startTime));
        }
    
    }

    같은 작업을 멀티쓰레드로 실행하면 a와 b를 번갈아가면서 출력한다.

     

    출처:https://javafactory.tistory.com/1529

     

    각각 싱글쓰레드와 멀티쓰레드로 작업을 했을때, 걸리는 시간을 표현한 것이다. cpu만을 사용하는 계산작업은 멀티쓰레드가 싱글쓰레드 보다 더 많은 시간이 걸린다. 그 이유는 다음과 같다.

     - 작업전환(context switching): 쓰레드가 번갈아가며 작업을 수행하므로, 각 쓰레드간 작업을 전환하는 시간이 걸리게 된다.

    - 대기시간: 한 쓰레드가 화면에 출력하고 있는 동안 다른 쓰레드는 출력이 끝나기를 기다려야하는 대기시간이 소요된다. 

     

    두개 이상의 쓰레드를 실행하는 성능차이는 cpu가 싱글 코어이냐 멀티 코어이냐에 따라 또 나뉜다.

     

    싱글코어: 멀티쓰레드를 사용하더라도 하나의 코어가 번갈아가면서 작업을 수행하므로, 두 작업이 겹치는 일이 없다.

    멀티코어: 동시에 두 쓰레드가 수행될 수 있으므로, 쓰레드간 작업이 겹치는 현상이 발생한다. 이때, 자원을 놓고 두 쓰레드가 경쟁을 하게 된다. 

     

    *여러개의 쓰레드가 여러 작업을 동시에 진행하는 것을 병행이라고 하고, 하나의 작업을 여러 쓰레드가 나눠서 처리하는 것을 병렬이라고 한다.

    *jvm의 쓰레드 스케줄러에 의해 어떤 쓰레드가 얼마나 실행될 것인지 결정되는 것과 마찬가지로 프로세스도 프로세스 스케줄러에 의해서 실행순서와 시간이 결정되므로, 프로세스에 할당되는 실행시간도 쓰레드처럼 일정하지 않다.

    *자바가 os독릭적이라고는 하지만 os에 종속적인 부분이 있는데, 쓰레드가 그 중 하나이다. (jvm 종류에 따라 쓰레드 스케줄러의 구현방법이 다를 수 있다.)

     

    3.1 i/o블라킹

     

    cpu를 통한 계산 작업등에서는 싱글쓰레드가 낫지만, 멀티쓰레드 또한 다양한 작업을 동시에 처리할 수 있으므로, 그 강점이 있다. 예시로 i/o블라킹을 들 수 있다. i/o블라킹: 입출력시에 작업중단을 의미한다 (ex 콘솔에 입력을 받을때, 입력이 완료될 때까지 프로그램이 기다리는 것)

    멀티 쓰레드를 이용하면, 사용자의 입력을 받는 쓰레드따로, 다른 작업을 수행하는 쓰레드를 따로 두어서 위와같은 i/o블라킹 문제를 해결할 수 있을 뿐더러 대부분의 우리가 사용하는 프로그램은 동시에 여러 작업을 수행하므로, 싱글쓰레드보다 멀티쓰레드를 사용하는 이점이 크다.

     

    public class ThreadEX1 {
        //static long startTime =0;
        public static void main(String[] args) {
    
            Threadex th = new Threadex();
            th.start();
            
            String input = JOptionPane.showInputDialog("아무 값이나 입력하세요");
            System.out.println("입력하신 값은 "+input+"입니다라");
    
        }
    
    }
    class Threadex extends Thread{
       
        @Override
        public void run(){
            for(int i=10; i>0;i--){
               System.out.println(i);
               try{
                   sleep(1000);
               }catch (Exception e){}
            }
         
        }
    }

    ->화면에 입력을 요구함과 동시에 시간초를 센다.

     

     

     

     

    참고자료:

    자바의 정석 -남궁성

    '언어 > JAVA' 카테고리의 다른 글

    자바 - Arrays 클래스와 Comparator  (0) 2023.03.23
    자바 - Thread(4) 동기화  (0) 2023.03.21
    자바 - thread(3) 실행제어 메서드 예제  (0) 2023.03.21
    자바 -thread(2) [5~8]  (0) 2023.03.20
    자바 - Generics  (0) 2023.03.16
Designed by Tistory.