안녕하세요 알통몬입니다. 공감 및 댓글은 포스팅 하는데 아주아주 큰 힘이 됩니다!! 포스팅 내용이 찾아주신 분들께 도움이 되길 바라며 더 깔끔하고 좋은 포스팅을 만들어 나가겠습니다^^
|
멀티 스레드는 동시성 Concurrency or 병렬성 Parallelism 으로 실행됩니다.
동시성은 멀티 작을 위해 하나의 코어에서 멀티 스레드가 번갈아가며 실행하는 성질을 말하고,
병렬성은 멀티 작업을 위해 멀티 코어에서 개별 스레드를 동시에 실행하는 성질을 말합니다.
싱글 코어 CPU를 이용한 머리 스레드 작업은 번갈아가며 실행하는 동시성 작업입니다.
번갈아 실행하는 것이 워낙 빠르다보니 병렬성으로 보일 뿐입니다.
스레드의 개수가 코어의 수보다 많을 때,
스레드를 어떤 순서에 의해 동시성으로 실행할 것인가를 결정해야 하는데
이것을 스레드 스케줄링이라고 합니다.
스레드 스케줄링에 의해 스레드들은 아주 짧은 시간에 번갈아 가면서 그들의 run() 메서드를 조금씩 실행합니다.
자바의 스레드 스케줄링방식
-우선순위 방식: 우선순위가 높은 스레드가 실행 상태를 더 많이 가지도록 스케줄링하는 것 ,
스레드 객체에 우선 순위번호를 부여할 수 있기 때문에 개발자가 코드로 제어 가능
-순환 할당 방식: 시간 할당량을 정해서 하나의 스레드를 정해진 시간만큼 실행하고
다시 다른 스레드를 실행하는 방식, JVM에 의해 정해지기 때문에 코드로 제어 불가
우선순위 방식에서 우선순위는 1부터 10까지 부여됩니다. 숫자가 크면 우선순위도 크다.
따로 우선순위를 부여하지 않으면 모든 스레드들은 기본적으로 5의 우선순위를 할당 받습니다.
우선순위를 변경하고 싶다면 setPriority()메서드를 이용하면 됩니다.
thread.setPriority(우선순위);
매개 값으로 1~10까지의 값을 주어도되지만,
코드의 가독성을 위해 Thread 클래스의 상수를 사용할 수도 있습니다.
thread.setPriority(Thread.MAX_PRIORITY); => 10
thread.setPriority(Thread.MIN_PRIORITY); => 1
thread.setPriority(Thread.NORM_PRIORITY); => 5
우선 순위가 높을수록 실행 기회 多
동일한 계산 작업을 실행하는 스레드들이 있고, 싱글 코어에서 동시성으로 실행할 경우
우선 순위가 높은 스레드가 실행 기회를 더 많이 가지기 때문에
우선순위가 낮은 스레드보다 계산 작업을 빨리 끝냅니다.
쿼드 코어일 경우 4개의 스레드가 병렬성으로 실행될 수 있기 때문에
4개 이하의 스레드를 실행할 경우 우선순위 방식이 크케 영향을 미치지 못합니다.
아래 예제는 10개의 스레드를 생성하고 20억번의 루핑을 누가 더 빨리 끝내는 가를 실행하는 예제입니다.
예제)
public class CalcThread extends Thread {
public CalcThread(String name) {
setName(name);
}
public void run() {
for(int i=0; i<2000000000; i++) {
}
System.out.println(getName());
}
}
실행 클레스)
public class PriorityExample {
public static void main(String[] args) {
for(int i=1; i<=10; i++) {
Thread thread = new CalcThread("thread" + i);
if(i != 10) {
thread.setPriority(Thread.MIN_PRIORITY);1~9는 우선순위가 1
} else {
thread.setPriority(Thread.MAX_PRIORITY);10은 우선순위가 10
}
thread.start();
}
}
}
그렇다고 해서 꼭 Thread10이 먼저 작업을 끝내는 것은 아닙니다.
30번 정도 실행해봤는데 실행할 때마다 순서가 바뀝니다.
물론 Thread10이 계산작업을 가장 먼저 끝내는 경우가 제일 많이 발생합니다.
========================================================
공유 객체를 사용할 때 주의할 점
멀티 스레드 프로그램에서 스레드들이 객체를 공유해서 작업해야하는 경우가 있습니다.
이런 경우 스레드1이 사용하던 객체가 스레드2에 의해 상태가 변경될 수 있기 때문에
스레드1이 의도했던 것과는 다른 결과를 산출하는 경우가 생길 수 있습니다.
바로 예제로 보겠습니다.
예제)
실행 클래스
public class MainThreadExample {
public static void main(String[] args) {
Calculator calculator = new Calculator();
User1 user1 = new User1(); User1 스레드 생성
user1.setCalculator(calculator); 공유 객체 설정
user1.start(); 스레드 시작
User2 user2 = new User2(); User2 스레드 생성
user2.setCalculator(calculator); 공유 객체 설정
user2.start(); 스레드 시작
}
}
공유 객체
public class Calculator {
private int memory;
public int getMemory() {
return memory;
}
public void setMemory(int memory) { 계산기 메모리에 값을 저장하는 필드
this.memory = memory; 매개 값을 memory 필드에 저장
try {
Thread.sleep(2000); 스레드를 2초간 정지시킵니다.
} catch(InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + ": " + this.memory);
}
}
User1 스레드
public class User1 extends Thread {
private Calculator calculator;
public void setCalculator(Calculator calculator) {
this.setName("User1"); 스레드 이름을 User1로 설정
this.calculator = calculator; 공유 객체인 Calculator를 필드에 저장
}
public void run() {
calculator.setMemory(100); 공유 객체인 Calculator의 메모리에 100을 저장
}
}
User2 스레드
public class User2 extends Thread {
private Calculator calculator;
public void setCalculator(Calculator calculator) {
this.setName("User2");스레드 이름을 User2로 설정
this.calculator = calculator;공유 객체인 Calculator를 필드에 저장
}
public void run() {
calculator.setMemory(50);공유 객체인 Calculator의 메모리에 50을 저장
}
}
동기화 메서드와 동기화 블록
스레드가 사용 중인 객체를 다른 스레드가 변경할 수 없도록 하려면
스레드 작업이 끝날 때까지 객체에 잠금을 걸어서 다른 스레드가 사용할 수 없도록 해야 합니다.
멀티 스레드는 프로그램에서 단 하나의 스레드만 실행할 수 있는 코드 영역을 임계 영역이라도 합니다.
자바는 임계영역을 지정하기 위해 동기화 메서드와 동기화 블록을 제공.
스레드가 객체 내부의 동기화 메서드 또는 블록에 들어가면 즉시 객체에 잠금을 걸어
다른 스레드가 임계 영역 코드를 실행하지 못하도록 합니다.
동기화 메서드 사용 방법
public synchronized void method() {
임계 영역; // 단 하나의 스레드만 실행
}
동기화 메서드는 스레드가 동기화 메서드를 실행하는 즉시 객체에는 잠금이 일어나고
스레드가 동기화 메서드를 실행 종료하면 잠금이 풀립니다.
메서드 전체가 아니라 일부 내용만 임계 영역으로 하고 싶다면 동기화 블록을 사용하면 됩니다.
public void method() {
// 여러 스레드 실행 가능 영역
... Γ--------- 공유 객체가 자신이면 this를 넣을 수 있습니다.
synchronized(공유 객체) {
임계 영역; // 단 하나의 스레드만 실행
} // 동기화 블럭
// 여러 스레드 실행 가능 영역
...
}
동기화 블록의 외부 코드들은 여러 스레드가 동시에 실행할 수 있지만,
동기화 블록 내부 코드는 임계 영역이므로
한 번에 한 스레드만 실행할 수 있고 다른 스레드는 실행할 수 없습니다.
동기화 메서드와 동기화 블록이 여러개 있다면
스레드가 이들 중 하나를 실행할 때 다른 스레드는 해당 메서드는 물론,
다른 동기화 메서드 및 블록도 실행할 수 없습니다. 하지만 일반 메서드는 실행이 가능힙니다.
아래 예제는 이전 포스팅의 예제에서 문제였던 공유 객체인 Calculator를 수정한 것입니다.
예제)
public class Calculator {
private int memory;
public int getMemory() {
return memory;
}
public synchronized void setMemory(int memory) {
// 동기화 메모리로 만듦으로써 User1스레드가 setMemory()를 실행하는 동안
User2 스레드가 setMemory() 스레드를 실행할 수 없도록 함.
this.memory = memory;
try {
Thread.sleep(2000);
} catch(InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + ": " + this.memory);
}
}
이렇게 수정한 후 다시 main()메서드를 실행하면
이전 포스팅에서의 결과와는 다르게 User1은 100, User2는 50을 출력합니다.
User1 스레드는 Calculator 객체의 동기화 메서드인 setMemory()를 실행하는 순간 객체를 잠급니다.
메인 스레드가 User2를 실행시키지만, 동기화 메서드인setMemory()를 실행시키지 못하고
User1이 setMemory()를 모두 실행할 동안 대기해야 합니다.
User1 스레드가setMemory()메서드를 모두 실행하고 나면 User2 스레드가 setMemory()메서드를 실행합니다.
위 예제에서 Calculator 객체의 setMemory() 메서드를 동기화 메서드로 만들엇는데
아래처럼 기화 블럭으로 만들 수도 있습니다.
public void setMemory(int memory) {
synchronized(this){
this.memory = memory;
try{
Thread.sleep(2000);
}catch(InterruptedException e){
System.out.println(Thread.currentThread().getName() + ": " + this.memory + "저장");
스레드가 동기화 블럭으로 들어가면 this(Calculator 객체)를 잠그고 동기화 블럭이 실행됩니다.
동기회 블럭을 모두 실행할 때까지 다른 스레드들은 this(Calculator 객체)의
모든 동기화 메서드 or 동기화 블럭을 실행할 수 없게 됩니다.