개발공부/JAVA
[JAVA] 멀티 스레드
jnnjnn
2024. 3. 26. 18:24
멀티 스레드
- 멀티 태스킹이란 두 가지 이상 작업을 동시에 처리하는 것을 말함
- 하나의 스레드에서 멀티 태스킹을 할 수 있도록 한 것이 멀티 스레드
- 멀티 스레드에서 하나의 스레드의 오류는 다른 스레드에도 영향을 미친다
작업 스레드 생성
-Thread 클래스를 통해서 멀티 스레드를 구현할 수 있다.
-스레드를 구현하려면 run() 메소드 내부에 스레드에서 실행될 코드를 구현해야 한다.
-Thread 구현 객체의 start() 메소드를 호출하면 재정의된 run()을 실행시킨다
1) Thread의 자식 객체 생성
- Thread를 상속받는 자식 객체를 생성한다.
- run() 메소드를 재정의하여 스레드가 실행할 코드를 작성하고 객체를 생성한다.
- Thread의 익명 자식 객체를 사용할 수도 있다.
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThread is running");
}
public static void main(String[] args) {
MyThread myThread = new MyThread(); // Thread 객체 생성
myThread.start(); // 쓰레드 실행
}
}
2) Thread 클래스로 직접 생성
- Thread 클래스로부터 작업 스레드 객체를 직접 생성하려면 Runnable 구현 객체를 매개값으로 갖는 생성자를 호출한다
- Runnable의 구현 클래스가 Thread로 Runnable에는 run() 메소드가 정의되어 있다.
- 일반적으로 Thread의 생성자에 Runnable 익명 구현 객체를 매개값으로 사용한다
- Thread의 자식 객체를 생성하는 것보다 다형성이 확보된다.
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("Anonymous Runnable is running");
});
thread.start(); // 쓰레드 실행
}
}
스레드 상태
1) join 메소드
해당 스레드의 작업이 종료될 때까지 join() 메소드
를 호출한 스레드를 대기 상태로 만든다
class SumThread extends Thread {
private long sum;
public long getSum() {
return sum;
}
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
sum += i;
}
}
}
public class C08Join {
public static void main(String[] args) throws InterruptedException {
SumThread threadA = new SumThread();
// threadA 실행
threadA.start();
// Main 스레드를 대기 상태로 만듬
threadA.join();
// threadA 종료 후 Main 스레드 실행
System.out.println(threadA.getSum());
}
}
2) sleep(long millis)
주어진 시간 동안 스레드를 일시 정지 상태로 만든다.
public class C09Sleep {
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("ThreadA 실행");
try {
// 3초 일시정지 후 실행
Thread.sleep(3000);
} catch (InterruptedException e) {
}
}
});
}
}
스레드 동기화
멀티 스레드가 하나의 객체를 공유해서 작업할 경우 스레드의 작업이 겹치면서 의도했던 것과 다른 결과가 나올 수 있다.
다음과 같은 방법으로 스레드 안전한 연산을 수행할 수 있다.
1) synchronized 키워드 사용
-synchronized 키워드는 인스턴스와 정적 메소드 어디든 붙일 수 있다.
-메소드 전체가 아닌 일부 영역을 실행할 때만 객체 잠금을 걸고 싶다면 동기화 블록을 만들고 synchronized(공유객체)를 선언한다.
-this 키워드는 메소드가 실행하는 객체를 나타낸다
public class C03SynchronizedMethod {
public static void main(String[] args) throws InterruptedException {
MyObject3 o = new MyObject3();
Thread t1 = new Thread(o);
Thread t2 = new Thread(o);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(o.getValue());
}
}
class MyObject3 implements Runnable {
private long value;
public long getValue() {
return value;
}
public synchronized void update() {
for (int i = 0; i < 30000; i++) {
value++;
}
}
@Override
public void run() {
update();
}
}
2) Atomic 클래스 사용
Atomic 클래스를 사용하면 별도의 동기화 없이 스레드 안전한 연산을 수행할 수 있다.
import java.util.concurrent.atomic.AtomicLong;
public class C04Concurrency {
public static void main(String[] args) throws InterruptedException {
MyObject4 o = new MyObject4();
Thread t1 = new Thread(o);
Thread t2 = new Thread(o);
t1.start();
t2.start();
t1.join();
t2.join();
}
}
class MyObject4 implements Runnable {
// private long value;
private AtomicLong value;
public MyObject4() {
this.value = new AtomicLong();
}
public long getValue() {
return value.longValue();
}
public void update() {
for (int i = 0; i < 30000; i++) {
// value++;
value.getAndIncrement();
}
System.out.println(Thread.currentThread().getName() + ": " + getValue());
}
@Override
public void run() {
update();
}
}
스레드 안전한 자료구조
흔히 사용하는 List의 ArrayList나 Set의 HashSet은 멀티 스레드로부터 안전하지 않다.
Vector나 Collections의 synchronizedList, synchronizedSet 등을 사용하면 스레드 안전한 연산을 수행할 수 있다.
public class C07SynchronizedList {
public static void main(String[] args) throws InterruptedException {
List<String> list = Collections.synchronizedList(new ArrayList<>());
Thread t1 = new Thread(() -> {
for (int i = 0; i < 30000; i++) {
list.add("a");
list.remove("a");
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 30000; i++) {
list.add("b");
list.remove("b");
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(list); //[]
}
}