1. 스레드
- 다중 스레딩 : 하나의 프로그램이 동시에 여러 가지 작업을 할 수 있도록 하는 것
- 스레드 : 각각의 작업, 동일한 데이터를 공유함
- 프로세스 : 자신만의 데이터를 가짐
- 프로그램을 보다 빠르게 실행하기 위해 멀티 스레딩 사용
2. 멀티 스레딩의 문제점
- 여러 스레드들이 같은 데이터를 공유하게 되면 '동기화' 문제 발생
3. 스레드 생성과 실행
Thread t = new Thread();
t.start();
3-1) 스레드 생성: Thread 클래스 상속하는 방법
- Thread 클래스를 상속받은 후에 run() 메소드 재정의
- run 메소드 안에 작업 기술
- Thread 객체 생성하고 start() 호출해서 스레드 시작
class MyThread extends Thread {
public void run() {
for (int i = 0; i <= 10; i++)
System.out.print(i + " ");
}
}
public class MyThreadTest {
public static void main(String args[]) {
Thread t = new MyThread();
t.start();
}
}
// 0 1 2 3 4 5 6 7 8 9 10
3-2) 스레드 생성: Runnable 인터페이스 구현하는 방법
- Runnable 인터페이스를 구현한 클래스 작성
- run() 메소드 작성
- Thread 객체 생성하고 Runnable 객체 인수로 전달
- start() 호출해서 스레드 시작
class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i <= 10; i++)
System.out.print(i + " ");
}
}
public class MyRunnableTest {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
// 0 1 2 3 4 5 6 7 8 9 10
- 스레드 2개 예제
class MyThread extends Thread {
public void run() {
for (int i = 0; i < 10; i ++) {
System.out.print(i + " ");
}
}
}
class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 30; i ++) {
System.out.print("[" + i + "]");
}
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
System.out.println("main start");
MyThread mt = new MyThread();
mt.start();
MyThread mt2 = new MyThread();
mt2.start();
Thread t = new Thread(new MyRunnable()); // runnable -바로 스타트 불가.
t.start();
// 모든 작업이 끝났을 때 종료 메세지를 출력하고 싶은 경우
mt.join();
mt2.join();
t.join(); // 끝날 때까지 기다리기
System.out.println("\nmain end");
}
}
/**
main start
0 1 2 3 4 0 5 6 7 8 9 1 2 3 4 5 6 7 8 9 [0][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]
main end
**/
- 람다식 이용한 스레드 작성
public class LambdaTest {
public static void main(String[] args) {
Runnable task = () -> {
for (int i = 0; i <= 10; i++)
System.out.print(i + " ");
};
new Thread(task).start();
}
}
// 0 1 2 3 4 5 6 7 8 9 10
4. 스레드 상태
- New : Thread 클래스의 인스턴스는 생성되었지만, start() 메소드를 호출하기 전
- Runnable : start() 메소드가 호출되어 실행 가능한 상태, 하지만 아직 스케줄러가 선택하지 않았으므로 실행 상태는 아님
- Running : 스레드 스케줄러가 스레드를 선택하면, 실행 중인 상태가 됨
- Blocking : 스레드가 아직 살아있지만, 여러 이유로 현재 실행할 수 없는 상태
- Terminated : 스레드가 종료된 상태, run() 메소드가 종료되면 스레드도 종료됨
- 실행 가능 상태 : 스레드가 스케줄링 큐에 넣어지고, 스케줄러에 의해 우선순위에 따라 실행
- 실행 중지 상태
- 스레드나 다른 스레드가 suspend()를 호출하는 경우
- 스레드가 wait() 호출하는 경우
- 스레드가 sleep() 호출하는 경우
- 스레드가 입출력 작업을 하기 위해 대기하는 경우
5. 스레드 스케줄링
- 대부분 스레드 스케줄러는 선점형 스케줄링과 타임 슬라이싱을 사용해 스레드 스케줄링
- 어떤 스케줄링을 선택하느냐는 JVM에 의해 결정됨
6. 스레드 우선순위
- 1 ~ 10 사이의 숫자료 표시됨
- 기본 우선순위 : NORM_PRIORITY(5)
- MIN_PRIORITY(1)
- MAX_PRIORITY(10)
- void setPriority(int newPriority) : 현재 스레드의 우선 순위 변경
- getPriority() : 현재 스레드의 우선 순위 반환
7. sleep()
- 지정된 시간동안 스레드 재움
- 스레드가 수면 상태로 있는 동안 인터럽트되면 InterruptedException 발생
- 4초 간격으로 메시지 출력
public class SleepTest {
public static void main(String[] args) throws InterruptedException {
String messages[] = {"Hello.",
"My name is A.",
"I'm majoring in computer science.",
"I'm taking a JAVA class."};
for (int i = 0; i < messages.length; i++) {
Thread.sleep(4000);
System.out.println(messages[i]);
}
}
}
/**
Hello.
My name is A.
I'm majoring in computer science.
I'm taking a JAVA class.
**/
8. join()
- 스레드가 종료될 때까지 기다리는 메소드
- 특정 스레드가 작업을 완료할 때까지 현재 스레드의 실행을 중지하고 기다리는 것
9. 인터럽트와 yield()
- 인터럽트 : 하나의 스레드가 실행하고 있는 작업을 중지하도록 하는 메커니즘 -> 대기 상태나 수면 상태가 됨
- yield() : CPU를 다른 스레드에게 양보하는 메소드
10. 자바 스레드 풀
- 스레드 풀 : 미리 초기화된 스레드들이 모여 있는 곳
- 스레드 풀의 동일한 스레드를 사용하여 N개의 작업을 쉽게 실행할 수 있음
- 스레드의 개수보다 작업의 개수가 더 많은 경우, 작업은 FIFO 큐에서 기다려야 함
- Java5 부터 자바 API는 Executor 프레임워크 제공
-> 개발자는 Runnable 객체를 구현하고 ThreadPoolExecutor로 보내기만 하면 됨
class MyTask implements Runnable {
private String name;
public MyTask(String name) {
this.name = name;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override
public void run() {
try {
System.out.println("실행중 : " + name);
Thread.sleep(long)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadPoolTest {
public static void main(String[] args) {
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
for(int i = 0; i <= 5; i++) {
MyTask task = new MyTask("작업 " + i);
System.out.println("작업 생성 : " + task.getName());
executor.execute(task);
}
executor.shutdown();
}
}
11. 스레드 사용시 주의해야 할 점
- 동일한 데이터를 공유하기 때문에 매우 효율적으로 작업할 수 있지만, 2가지의 문제가 발생할 수 있음
- 문제 예제
class Printer {
void print(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
Thread.sleep(100);
}
}
}
class MyThread1 extends Thread {
Printer prn;
int[] myarr = {10, 20, 30, 40};
MyThread1(Printer prn) { this.prn = prn; }
public void run() { prn.print(myarr); }
}
class MyThread2 extends Thread {
Printer prn;
int[] myarr = {1, 2, 3, 4};
MyThread2(Printer prn) { this.prn = prn; }
public void run() { prn.print(myarr); }
}
public class TestSynchro {
pubilc static void main(String args[]) {
Printer obj = new Printer();
MyThread1 t1 = new MyThread1(obj);
MyThread2 t2 = new MyThread2(obj);
t1.start();
t2.start();
}
}
// 1 10 20 2 30 3 4 40
// 순서는 계속 바뀔 수 있음, 둘이 섞여서 엉망으로 출력됨
11-1) 동기화 (Synchronization)
- 한 번에 하나의 스레드만이 공유 데이터를 접근할 수 있도록 제어하는 것이 필요
- 자원에 한 번에 하나의 스레드만이 접근할 수 있고, 하나의 스레드 작업이 끝나면 다음 스레드가 사용할 수 있도록 하여 해결
- 자바에서의 동기화 방법
- 동기화 메소드
- 동기화 블록
- 정적 동기화
- 락(lock) 또는 모니터(monitor) 사용
// 메소드 앞에 synchronized 키워드 붙이기
class Printer {
synchronized void print(int[] arr) {
....
}
}
// 10 20 30 40 1 2 3 4
// 부분 코드만 동기화 -> synchronized 블록으로 설정
class Printer {
void print(int[] arr) throws Exception {
synchronized(this) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
Thread.sleep(100);
}
}
}
}
12. 교착 상태, 기아 상태
- 동일한 자원을 접근하려고 동기화를 기다리면서 대기하는 스레드들이 많아지면 JVM이 느려지거나 일시 중단 되기도 함
- 문제 예제
public class DeadLockTest {
public static void main(String[] args) {
final String res1 = "Gold";
final String res2 = "Silver";
Thread t1 = new Thread(() -> {
synchronized(res1) {
System.out.println("Thread 1 : 자원 1 획득");
try { Thread.sleep(100); } catch (Exception e) {}
synchronized(res2) {
systme.out.println("Thread 1 : 자원 2 획득");
}
}});
Thread t2 = new Thread(() -> {
synchronized(res2) {
System.out.println("Thread 2 : 자원 2 획득");
try { Thread.sleep(100); } catch (Exception e) {}
synchronized(res1) {
System.out.println("Thread 2 : 자원 1 획득");
}
}});
t1.start();
t2.start();
}
}
// Thread 1 : 자원 1 획득
// Thread 2 : 자원 2 획득
12-1) 방법1 : 잠금 순서 변경
Thread t2 = new Thread(() -> {
synchronized(res1) {
System.out.println("Thread 2 : 자원 1 획득");
try { Thread.sleep(100); } catch (Exception e) {}
synchronized(res2) {
System.out.println("Thread 2 : 자원 2 획득");
}
}});
/**
Thread 1 : 자원 1 획득
Thread 1 : 자원 2 획득
Thread 2 : 자원 1 획득
Thread 2 : 자원 2 획득
**/
12-2) 방법2: 스레드 간의 조정
13. wait(), notify()
- 이벤트가 발생하면 알리는 방법
- 생산자/소비자 문제에 적용
// buffer 클래스
class Buffer {
private int data;
private boolean empty = true;
public synchronized int get() {
while (empty) {
try {
wait();
} catch (InterruptedException e) {
}
}
empty = true;
notifyAll();
return data;
}
public synchronized void put(int data) {
while (!empty) {
try {
wait();
} catch (InterruptedException e) {
}
}
empty = false;
this.data = data;
notifyAll();
}
}
// 생산자
class Producer implements Runnable {
private Buffer buffer;
public Producer(Buffer buffer) {
this.buffer = buffer;
}
public void run() {
for (int i = 0; i < 10; i++) {
buffer.put(i);
System.out.println("생산자: " + i + "번 케익을 생산하였습니다.");
try {
Thread.sleep((int) (Math.random() * 100));
} catch (InterruptedException e) {
}
}
}
}
// 소비자
class Consumer implements Runnable {
private Buffer buffer;
public Consumer(Buffer drop) {
this.buffer = drop;
}
public void run() {
for (int i = 0; i < 10; i++) {
int data = buffer.get();
System.out.println("소비자: " + data + "번 케익을 소비하였습니다.");
try {
Thread.sleep((int) (Math.random() * 100));
} catch (InterruptedException e) {
}
}
}
}
public class ProducerConsumerTest {
public static void main(String[] args) {
Buffer buffer = new Buffer();
(new Thread(new Producer(buffer))).start();
(new Thread(new Consumer(buffer))).start();
}
}
/**
생산자: 0번 케익을 생산하였습니다.
소비자: 0번 케익을 소비하였습니다.
생산자: 1번 케익을 생산하였습니다.
소비자: 1번 케익을 소비하였습니다.
...
생산자: 9번 케익을 생산하였습니다.
소비자: 9번 케익을 소비하였습니다.
**/
'Software > JAVA' 카테고리의 다른 글
[Baekjoon] 1085.직사각형에서 탈출 (0) | 2023.04.03 |
---|---|
[JAVA] Day13. 파일 입출력 (0) | 2023.01.18 |
[JAVA] Day12. 제네릭과 컬렉션 (0) | 2023.01.11 |
[JAVA] Day11. 자바 그래픽 (5) | 2023.01.10 |
[JAVA] Day10. 스윙 컴포넌트 (0) | 2023.01.09 |