본문 바로가기

아옳옳의 코딩공부/아옳옳 자바코딩공부

2021-05-03자바공부(스레드 Thread3)

반응형

지금까지 스레드의 기본적인것을 배웠는데 다음과 같은 상황이 생길수 있다. 

 

왼쪽 그림을 보자 각각의 스레드가 동시에 작업을 수행 할 경우 생기는 상황이다. 저렇게 된다면 순차적인 접근이 되지 않아 값이 달라질수 있다.   우리가 하고 싶었던 작업은 오른쪽 처럼 하나의 스레드가 작업을 마치면 다른 스레드가 작업을 하는 식으로 만들어 주고 싶었던 것이다 . 이런 상황을 해결 하는것이 동기화라고 한다  

들어가기 앞서 어떻게 되는지 예시를 먼저 보자 

class Banana implements Runnable{
	int res = 0 ; 
	@Override
	public void run() {
		sum();		
	}
	
	public void sum(){
		for(int i=0 ; i<10000 ; i++) {
			res++;
		}
	}
	
}

public class Abc {

	public static void main(String[] args) {

		Banana banana = new Banana();
		Thread t1 = new Thread(banana);
		Thread t2 = new Thread(banana);
		t1.start();
		t2.start();

		try {
			t1.join();
			t2.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println(banana.res);
	
	}

}

위 코드를 보면 각각의 스레드를 만들어서 1만씩 증가시키도록 했다 그러나 실행 결과 값을 보면 12382 이렇게 찍힌다

( 이값을 고정이 아님 항상 변경됨 )  이런 상황을 처리 하기위하여 사용하는 법이 동기화 이다 .

메소드에 synchronized 키워드를 선언한 경우에는 메소드 전체가 동기화 

블록에 synchronized 키워드를 사용한 경우에는 블록 내부 내용만 동기화 

 

여기서 잘 알아야 하는것은 메소드에 synchronized를 걸어주는것이다. 만약에 한 메소드의 코드가 90줄 정도 된다고 생각해보자 메소드 전체에 synchronized 걸려 있다보니 하나의 스레드가 90줄을 하는 동안 나머지는 멈춰 있어야 하다보니 작업 속도가 느려 질수 있다 이것을 잘 생각하자 

 

위의 코드를 동기화를 사용하여 해보도록 하겠다 

public synchronized void sum(){
		for(int i=0 ; i<10000 ; i++) {
			res++;
		}
	}
	}

위 예제 sum 부분만 저렇게 바꿔주었다 그러고 실행해 보면 정확히 20000이 찍히는 걸 볼수 있다. 

이게 메소드 동기화이였고 블록 동기화를 알아보자 

	public  void sum(){
		synchronized(this) {
		for(int i=0 ; i<10000 ; i++) {
			res++;
		}	}	}	}

이렇게 해주면 봤을때는 비슷해 보이지만 아까 말했듯이 메소드가 길어지면 길어줄수록 메소드 전체를 동기화 했을경우 기다려야 하는 시간때문에 좋은 프로그램이 아니다. 

 

더 나아가 다음과 같은 상황을 살펴 보자 신문을 만들고 본다고 가정해보자 신문을 보려면 일단 만들어야 하는데 만약 만들기 전에 신물을 보려고 했을때 상황이다. 

그것을 제어 하기위해서 아래의 메소드를 활용 해주는것이다. 

뉴스를 읽어 올려고하면 wait이 될것이고 작성하려 할때는 작성후 잠들어 있는 모든 스레드들을 깨워 주는 것이다.

 

synchronized 대체하는 ReentrantLock


 

1. 람다식을 이용한 스레드 생성 방법 

public class ThreadLambda {

	public static void main(String[] args) {


		Runnable run = ()-> {
			int n1 = 10;
			int n2 = 20; 
			String name = Thread.currentThread().getName();
			System.out.println(name + (n1+n2));
		};
		
		Thread t = new Thread(run);
		t.start();
		System.out.println("End" + Thread.currentThread().getName());

	}

}

람다식은 함수형 인터페이스로 할수 있다고 했는데 스레드 역시 함수형인터페이스로 람다로도 가능하다 

출력결과 

main
Thread-030

2. 람다식을 이용한 스레드 생성 방법 

public class ThreadLambda {

	public static void main(String[] args) {
		Runnable run = () -> {
			try {
				Thread.sleep(1000);
				ArrayList<Integer> array = new ArrayList<>();
				for (int i = 0; i < 20; i++) {
					if (i % 2 == 0) 
					array.add(i);					
					
				}
				System.out.println(array);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		};
		Runnable run2 = () -> {
			try {
				Thread.sleep(100);
				ArrayList<Integer> array2 = new ArrayList<>();
				for (int i = 0; i < 20; i++) {
					if (i % 2 == 1)
					array2.add(i);											
				}
				System.out.println(array2);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		};
		
		Thread t = new Thread(run);
		Thread t1 = new Thread(run2);
		t.start();
		t1.start();
	}
}

 

결과들을 어레이 리스트에 담아서 출력해주었다~ 

 

스레드 풀모델 

스레드의 생성과 소멸은 리소스 소모가 많은 작업이지만 스레드 풀 모델은 재활용이 가능한 모델이다. 

newCachedThreadPool 이건 예측이 어려워 잘 안쓴다고 하니 실습 패스 

 

newSingleThreadExecutor();

public class SingleThreadExecutor {

	public static void main(String[] args) {
		
		Runnable run = () -> {
			int n1 = 10;
			int n2 = 20;
			
			String name = Thread.currentThread().getName();
			System.out.println(name+ ": " +(n1+n2));
			
		};
		
		ExecutorService exr = Executors.newSingleThreadExecutor();
		exr.submit(run);//스레드 풀에 작업 전달 
		
		exr.shutdown();// 쓰레드 풀과 그 안스레드 소멸 
	}
}

결과 값 : pool-1-thread-1: 30

 

 

newFixedThreadPool();

public class FixedThreadPool {

	public static void main(String[] args) {		
		Runnable run = () -> {			
			try {
				Thread.sleep(499);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			String name = Thread.currentThread().getName();
			System.out.println(name +": "+ (5+7));			
		};
		Runnable run2 = () -> {	
			try {
				Thread.sleep(500);
				String name = Thread.currentThread().getName();
				System.out.println(name+ ": "+(10-7) );	
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
					
		};
		
		
		ExecutorService exr = Executors.newFixedThreadPool(2);
		exr.submit(run);//스레드 풀에 작업 전달 
		exr.submit(run2);
		exr.submit(() -> {
			String name = Thread.currentThread().getName();
			System.out.println(name + ": "+(5+7));		
		});
		exr.submit(() -> {
			String name = Thread.currentThread().getName();
			System.out.println(name + ": "+1);		
		});
			
		
		exr.shutdown();// 쓰레드 풀과 그 안스레드 소멸 
	}
}

결과 값 :  pool-1-thread-1: 12      pool-1-thread-2: 3        pool-1-thread-1: 12        pool-1-thread-2: 1

 

보는것처럼 2개의 스레드를 사용하며 번갈아 가며 사용하는것을 볼수 있다. 

 

 

오늘로서 자바 스레드는 다 배운거 같은데 내일 안드로이드에서 스레드 배운다고 하니 잘 기억해 두자!! 

반응형