본문 바로가기
프로그래밍/Java-문법정리

【Java문법】스레드

by 코이킹 2022. 1. 27.
반응형

안녕하세요 코이킹입니다. 
이번 포스트에선 스레드에 대해서 정리해보려 합니다.  

 


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

댓글