안녕하세요 코이킹입니다.
이번 포스트에선 스레드에 대해서 정리해보려 합니다.
1. 프로세스와 스레드?
프로세스(Process) : 실행중인 프로그램
스레드(Thread) : 프로세스 내에서 실행되는 흐름의 단위
멀티 스레드(Multi-Thread) : 프로세스 안에 여러 개의 스레드가 존재하는것
2. Java에서의 스레드의 사용 방법
1) 멀티 스레드 사용안함
package thread.ex01;
public class ThreadMain01 {
public static void main(String[] args) {
Calculator calculator = new Calculator(0L, 1000000000L);
execute(calculator);
}
public static void execute(Calculator calculator) {
long startTime = System.currentTimeMillis();
System.out.println("StartTime : "+ startTime);
calculator.calc();
long result = (long) calculator.getSum();
long endTime = System.currentTimeMillis();
System.out.println("EndTime : "+ endTime);
System.out.printf("Operating Time : [%.3f] Sec, Result : [%d] \n", (endTime - startTime) / 1000.0, result);
}
}
class Calculator {
private long start;
private long end;
private long sum;
public Calculator(long start, long end) {
setInit(start, end);
}
public void calc() {
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
this.sum = sum;
}
public long getSum() {
return this.sum;
}
public void setInit(long start, long end) {
this.start = start;
this.end = end;
this.sum = 0;
}
}
// 실행결과
//StartTime : 1643158486533
//EndTime : 1643158487070
//Operating Time : [0.537] Sec, Result : [500000000500000000]
- 지정한 시작과 끝의 수까지 계속해서 더하는 코드입니다.
2) Thread 클래스 사용
package thread.ex01;
import java.util.ArrayList;
public class ThreadMain02 {
public static void main(String[] args) {
// 동시성 문제해결
System.out.println("########## Solved concurrency issue. ##########");
execute(5, 1000000000L, true);
System.out.printf("\n\n");
// 값이 틀림
System.out.println("########## Not Solved concurrency issue. ##########");
execute(5, 1000000000L);
}
public static void execute(int worker, long input) {
execute(worker, input, false);
}
public static void execute(int worker, long input, boolean isJoin) {
ArrayList<Calculator2> list = new ArrayList<Calculator2>();
long startTime = System.currentTimeMillis();
System.out.println("StartTime : "+ startTime);
long start = 0;
long end = input / worker;
for (int i=1; i <= worker; i++) {
if (input < end) end = input;
Calculator2 calc = new Calculator2(start , end, i);
calc.start();
list.add(calc);
if (end == input) break;
start = end + 1;
end = end * 2;
}
long result = 0;
for (int i=0; i < list.size(); i++) {
Calculator2 calc = list.get(i);
try {
if (isJoin) {
calc.join();
}
result += calc.getSum();
} catch (Exception e) {}
}
long endTime = System.currentTimeMillis();
System.out.println("EndTime : "+ endTime);
System.out.printf("Operating Time : [%.3f] Sec, Result : [%d] \n", (endTime - startTime) / 1000.0, result);
list.clear();
list = null;
}
}
class Calculator2 extends Thread {
private long start;
private long end;
private long sum;
private int num;
public Calculator2(long start, long end, int num) {
setInit(start, end, num);
}
public void calc() {
System.out.printf("Thread [%d] Before, Num [%d], End Num [%d], sum() : [%d] \n", num, start, end, start);
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
System.out.printf("Thread [%d] After, Num [%d], End Num [%d], sum() : [%d] \n", num, start, end, sum);
this.sum = sum;
}
public long getSum() {
return this.sum;
}
public void setInit(long start, long end, int num) {
this.start = start;
this.end = end;
this.sum = 0;
this.num = num;
}
@Override
public void run() {
calc();
}
}
// 실행결과
//########## Solved concurrency issue. ##########
//StartTime : 1643159110620
//Thread [4] Before, Num [800000001], End Num [1000000000], sum() : [800000001]
//Thread [1] Before, Num [0], End Num [200000000], sum() : [0]
//Thread [3] Before, Num [400000001], End Num [800000000], sum() : [400000001]
//Thread [2] Before, Num [200000001], End Num [400000000], sum() : [200000001]
//Thread [1] After, Num [0], End Num [200000000], sum() : [20000000100000000]
//Thread [4] After, Num [800000001], End Num [1000000000], sum() : [180000000100000000]
//Thread [2] After, Num [200000001], End Num [400000000], sum() : [60000000100000000]
//Thread [3] After, Num [400000001], End Num [800000000], sum() : [240000000200000000]
//EndTime : 1643159110874
//Operating Time : [0.254] Sec, Result : [500000000500000000]
//
//
//########## Not Solved concurrency issue. ##########
//StartTime : 1643159110876
//EndTime : 1643159110876
//Operating Time : [0.000] Sec, Result : [0]
//Thread [4] Before, Num [800000001], End Num [1000000000], sum() : [800000001]
//Thread [3] Before, Num [400000001], End Num [800000000], sum() : [400000001]
//Thread [2] Before, Num [200000001], End Num [400000000], sum() : [200000001]
//Thread [1] Before, Num [0], End Num [200000000], sum() : [0]
//Thread [1] After, Num [0], End Num [200000000], sum() : [20000000100000000]
//Thread [4] After, Num [800000001], End Num [1000000000], sum() : [180000000100000000]
//Thread [2] After, Num [200000001], End Num [400000000], sum() : [60000000100000000]
//Thread [3] After, Num [400000001], End Num [800000000], sum() : [240000000200000000]
- 단일 스레드의 코드를 멀티스레드로 구현해보았습니다.
실행 결과를 보면 단일 스레드의 프로그램보다 처리속도가 빠른 것을 알 수 있습니다.
(1) 멀티 스레드를 사용할 경우의 장・단점
장점 :
- 단일 스레드로 처리하는 것보다, 멀티스레드를 사용하여 병렬로 처리하면 더 빠르게 처리할 수 있다. (상황에 따라 다르겠지만..)
- 어느 하나의 스레드가 중단되더라도, 다른 스레드가 중단되지 않으면 프로그램으로서 기능을 계속할 수 있다.
단점
- 프로그램의 성능이 저하될 수 있다.
(예시 1) 임계 영역 문제를 해결하기 위해 불필요한 곳까지 동기화를 하게 될 경우
(예시 2) 너무 많은 스레드를 생성할 경우, 콘텍스트 스위칭이나 스레드의 인스턴스를 생성하는데 자원을 많이 소모하는 경우
- 비동기적인 문제가 발생할 수 있다. (예시) 복수의 스레드에서 작업한 결과물을 하나로 합쳐야 하는 경우
-> Thread클래스의 Join() 메서드를 사용하여 해결한다.
- 임계 영역 문제가 발생할 수 있다. (예시) 복수의 스레드가 하나의 변수에 동시에 접근할 경우
-> synchronized 키워드(메서드에 키워드를 정의하거나 중괄호로 동기화 영역을 지정)를 사용하여 동기화를 통해 해결한다.
※ 비동기적 문제와 임계 영역 해결
package thread.ex01;
import java.util.ArrayList;
public class ThreadMain04 {
public static int sharedNum = 0;
public static void main(String[] args) {
SyncData sd = new SyncData(0);
ArrayList<SyncTest> list = new ArrayList<SyncTest>();
for (int i=1; i<=3; i++) {
SyncTest sync = new SyncTest(i, sd);
sync.start();
list.add(sync);
}
for (SyncTest sync : list) {
try {
sync.join();
} catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println("Test1-Result : "+sd.balance);
System.out.printf("\n\n");
ArrayList<SyncTest2> list2 = new ArrayList<SyncTest2>();
for (int i=1; i<=3; i++) {
SyncTest2 sync2 = new SyncTest2(i);
sync2.start();
list2.add(sync2);
}
for (SyncTest2 sync2 : list2) {
try {
sync2.join();
} catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println("Test2-Result : "+sharedNum);
}
}
class SyncData {
Integer balance;
public SyncData(int balance) {
this.balance = balance;
}
}
class SyncTest extends Thread{
SyncData syncData;
int number;
public SyncTest(int number, SyncData syncData) {
this.number = number;
this.syncData = syncData;
}
@Override
public void run() {
sum();
}
public void sum() {
System.out.println("sum() Thread ["+number+"] Before : "+syncData.balance);
for (int i=1; i<=3; i++) {
synchronized (this) {
syncData.balance += i;
System.out.println("sum() Thread ["+number+"] After : "+syncData.balance);
}
}
}
}
class SyncTest2 extends Thread {
int number;
public SyncTest2(int number) {
this.number = number;
}
@Override
public void run() {
sum();
}
public synchronized void sum() {
System.out.println("sum() Thread ["+number+"] Before : "+ThreadMain04.sharedNum);
for (int i=1; i<=3; i++) {
ThreadMain04.sharedNum += i;
System.out.println("sum() Thread ["+number+"] After : "+ThreadMain04.sharedNum);
}
}
}
3) Runnable 인터페이스 사용
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
public class ThreadMain03 {
public static void main(String[] args) throws InterruptedException {
PrintTime pt = new PrintTime();
Thread t = new Thread(pt);
t.start();
for (int i=0; i<20; i++) {
System.out.println("test"+i);
Thread.sleep(700);
}
}
}
class PrintTime implements Runnable {
static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
@Override
public void run() {
for (int i=0; i<10; i++) {
System.out.println(sdf.format(new Date()));
try {
Thread.sleep(300);
} catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
Thread 클래스로 스레드를 사용할 경우 상속을 사용할 수 없다는 단점이 있습니다.
Thread 클래스가 아닌 부모를 상속하고 싶은 경우 Runnable인터페이스를 사용하면 됩니다.
※ 사실 Thread 클래스는 Runnable인터페이스를 상속한 클래스!!
4) Callable 인터페이스 사용
package thread.ex01;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class ThreadMain05 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
GetTime gt = new GetTime();
System.out.println("########## Callable Test (1) ##########");
FutureTask<ArrayList<String>> futureTask = new FutureTask<ArrayList<String>>(gt);
new Thread(futureTask).start();
for (int i=0; i<5; i++) {
System.out.println("test1 wating Count : "+i);
Thread.sleep(500);
}
System.out.println(futureTask.get());
System.out.println("########## Callable Test (2) ##########");
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<ArrayList<String>> future = executor.submit(gt);
for (int i=0; i<5; i++) {
System.out.println("test2 wating Count : "+i);
Thread.sleep(300);
}
System.out.println(future.get());
}
}
class GetTime implements Callable<ArrayList<String>> {
static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS");
@Override
public ArrayList<String> call() throws Exception {
ArrayList<String> list = new ArrayList<String>();
for (int i=0; i<5; i++) {
String time = sdf.format(new Date());
System.out.println(time);
list.add(time);
try {
Thread.sleep(300);
} catch (InterruptedException e) { e.printStackTrace(); }
}
return list;
}
}
Runnable인터페이스의 run() 메서드는 리턴 값을 가질 수 없고 예외처리를 붙일 수 없습니다.
스레드의 각 작업의 리턴 값이 필요하고 예외처리를 해줘야 하는 프로그램을 구현해야 할 경우 Callable인터페이스를 사용하면 됩니다.
5) Thread Pool 사용
package thread.ex01;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadMain06 {
public static void main(String[] args) throws InterruptedException{
// 정해진 수만큼 스레드 생성
int workers = 4;
ExecutorService executorService = Executors.newFixedThreadPool(workers);
for (int i=1; i<=10; i++) {
ThreadPoolTest threadPoolTest = new ThreadPoolTest(i);
executorService.submit(threadPoolTest);
}
if (executorService.awaitTermination(5, TimeUnit.SECONDS)) {
System.out.println("All Tasks over");
}
executorService.shutdown();
}
}
class ThreadPoolTest implements Runnable {
int number;
@Override
public void run() {
counting();
}
public ThreadPoolTest(int number) {
this.number = number;
}
public void counting() {
for (int i=0; i<5; i++) {
System.out.printf("Thread [%d], Count [%d] \n", number, i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
스레드풀은 미리 정해진 수의 스레드를 생성해두고 사용하게 할 수 있도록 해주는 것입니다.
멀티스레드의 단점 중에 너무 많은 스레드를 생성할 경우 프로그램의 성능이 저하될 수 있다는 단점을 해결하기 위해 사용할 수 있습니다.
※ 예제 코드
https://github.com/leeyoungseung/algorithmBasic/tree/master/algorithm/src/thread/ex01
※ qiita에 투고한 URL
https://qiita.com/Koiking-L/items/d1535823e505b8d99419
'프로그래밍 > Java-문법정리' 카테고리의 다른 글
【OOP】06_추상화 (0) | 2022.01.23 |
---|---|
【OOP】05_다형성 (0) | 2022.01.20 |
【OOP】04_상속 (0) | 2022.01.18 |
【OOP】03_캡슐화 (0) | 2022.01.16 |
【OOP】02_멤버변수·메서드·생성자 (0) | 2022.01.15 |
댓글