-
자바 - thread(1) 기본특성 (1~4)언어/JAVA 2023. 3. 20. 12:49
1. 프로세스와 쓰레드
프로세스: 실행 중인 프로그램이다. 프로그램을 실행하면 OS로부터 필요한 자원을 할당받아 프로세스가 된다.
-프로세스는 데이터 메모리 등의 자원과 쓰레드로 구성되어 있다.
- 모든 프로세스는 하나 이상의 쓰레드가 존재하며, 둘 이상의 쓰레드를 가진 프로세스를 멀티쓰레드 프로세스라 한다.
쓰레드 : 프로세스 내의 자원을 이용해서 실제 작업을 수행 (프로세스의 실행 단위)
- 위와 같이 하나의 프로세스에 자원과 쓰레드가 있다. 쓰레드 각각은 내부에 스택과 고유한 프로그램 카운터 값을 가지고 있으며,
프로세스 내에 자원들을 공유하며 각자의 작업을 처리한다.
**이때 각 쓰레드에 스택 메모리를 할당하는 이유는 스레드마다 독립적으로 함수를 호출하고, 자원을 사용하는 등 독립적 작업을 처리하기 위한 최소한의 작업대를 주는 것과 같다
**고유의 프로그램 카운터 값을 가지는 이유는 쓰레드의 실행 순서를 파악하기 위해서 이다. 쓰레드는 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를 번갈아가면서 출력한다.
각각 싱글쓰레드와 멀티쓰레드로 작업을 했을때, 걸리는 시간을 표현한 것이다. 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