본문 바로가기
2022-2/자바

[자바] 멀티스레드, synchronized

by 철없는민물장어 2022. 12. 1.
728x90
반응형
MultiThread program 작성 방법
  • Thread class 이용
  • Runnable interface 이용

Thread class 상속받는 방법
public class DigitThread extends Thread {

	public void run() {//Thread.run 오버라이딩
		for (int count = 0; count < 10; count++) {
			System.out.println(count);
			
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

thread로 실행할 클래스를 선언한다.(Thread class 상속)

run()메소드를 오버라이딩하여 thread가 할 일을 작성한다.

 

public class MultiThreadExam01 {

	public static void main(String[] args) {

		Thread th = new DigitThread();
		Thread th2 = new DigitThread();
		Thread ath = new AlphabetThread();
		th.start();
		th2.start();
		ath.start();
		for (char ch = 'A'; ch <= 'Z'; ch++) {
			System.out.println(ch);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		
	}

}

메인 프로그램에서

Thread 클래스의 객체를 생성한다.

스레드를 시작하는 메소드인 .start()를 실행시킨다.

 


Thread.sleep(1000);

지정한 시간동안 아무일도 하지 않고 기다리는 메소드 sleep을 이용할 수 있다.

sleep메소드의 인자는 ms단위의 시간을 입력해야 하고,

sleep메소드를 사용할 때는 try-catch문으로 둘러싸야 한다.


Runnable interface 이용

 

다른 패키지에서 작성한 프로그램을 멀티스레드로 실행시킬 때

해당 클래스를 사용하기 위해서는 해당 클래스+ Thread 클래스를 상속받아야함.

하지만 자바에서는 다중상속을 지원하지 않으므로 이 때는 해당 클래스를 상속받은 후 implement Runnable 하여 멀티스레드 사용하여야함

public class SmallLetters implements Runnable {

	@Override
	public void run() {
		for (char ch = 'a'; ch <= 'z'; ch++) {
			System.out.println(ch);
		}
	}

}
public class MultiThreadExam03 {

	public static void main(String[] args) {
		
		Thread th =new Thread(new SmallLetters());
		th.start();
		char arr[] = { 'ㄱ', 'ㄴ', 'ㄷ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅅ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ' };

		for (char val : arr) {
			System.out.println(val);
		}
	}

}

Thread를 생성하고 사용해야 하는데,

Thread생성자의 인자로 "runnable을 implement했던 클래스의 객체"를 넣어준다.

 


예시

Account 클래스가 있다.

더보기

 

public class Account {

	String accountNum;
	String name;
	int balance;
	
	void deposit(int amnt) {
		this.balance += amnt;
		System.out.println(name+"님 게좌에"+amnt+"입금 후 계좌잔액: "+balance);
	}
	Account(String accountNum,String name){
		this(accountNum,name,0);
	}
	Account(String accountNum, String name, int balance){
		this.accountNum = accountNum;
		this.name = name;
		this.balance = balance;
	}
	Account(){
		this("","",0);
	}
	int withdraw(int amnt) {
		if(balance - amnt >=0) {
			this.balance -= amnt;
			System.out.println(name+"님 계좌에서 "+amnt+"인출완료");
			return amnt;
		}
		else {
			//throw new Exception("잔액 부족!!!");
			return 0;
		}
	}
	void printInfo() {
		System.out.println("*************info************");
		System.out.println("계좌번호: "+accountNum);
		System.out.println("계좌주: "+name);
		System.out.println("잔액: "+balance);
		System.out.println("*****************************");
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub

	}

}

Account 객체 accnt1, accnt2가 있고

thread 1은 accnt1에서 100을 출금해서 accnt2에 입금한다

thread 2는 accnt1과 accnt2의 잔액의 합을 출력한다.

 

thread1과 2는 모두 accnt1과 accnt2에 접근해야한다.

공유 영역을 SharedArea 클래스로 작성 해 보자.

public class SharedArea {
	Account accnt1;
	Account accnt2;
}

 

 

이후, thread 1을 TransferThread클래스로 작성한다.

public class TransferThread extends Thread {

	SharedArea sA;

	public TransferThread(SharedArea sA) {

		this.sA = sA;
	}

	@Override
	public void run() {
		for (int i = 0; i < 12; i++) {

			sA.accnt1.withdraw(100);
			System.out.println("accnt1 계좌에서 100 인출");
			sA.accnt2.deposit(100);
			System.out.println("accnt2 계좌에 100 입금");
		}
	}
}

 

thread2는 PrtThread클래스로 작성하였다.

public class PrtThread extends Thread {

	SharedArea sA;

	public PrtThread(SharedArea sA) {
		this.sA=sA;
	}
	@Override
	public void run() {

		for (int i = 0; i < 3; i++) {

			int sum = sA.accnt1.balance + sA.accnt2.balance;
			System.out.println("계좌잔액의 합= " + sum);
		}
	}
}

 

마지막으로 메인 프로그램

public class MultiThreadExam05 {

	public static void main(String[] args) {

		SharedArea obj=new SharedArea();
		obj.accnt1=new Account("111","이몽룡",2000);
		obj.accnt2=new Account("222","성춘향",1000);
		//계좌두개 생성
		
		TransferThread th1=new TransferThread(obj);
		PrtThread th2=new PrtThread(obj);
		
		th1.start();
		th2.start();
	}

}

accnt1은 2000의 잔액이 있고 accnt2는 1000의 잔액이 있다.

accnt1의 돈을 2에 옮긴다고 해서 두 계좌의 잔액의 합이 줄어들거나 늘진 않으므로

결과로는 3000만 나와야 한다.

 

하지만 실행시켜보면 2900이 나올 때가 있다.

왜 그럴까?

 

thread1에서 accnt1에서 100을 출금 한 이후에, accnt2에 100을 입금하기 직전

thread2는 잔액의 합을 계산해 출력한다.

 

thread1의 모든 동작이 완료되기 전에 thread2가 실행됐기 때문에 부정확한 값이 나왔다.

 

간단하게 해결 해 보자.

 

public class SharedArea {
	Account accnt1;
	Account accnt2;
	boolean isReady=false;
}

 우선 SharedArea에 

작업이 완료됐는지를 나타내는 isReady라는 변수를 추가해 준다.

 

그 다음, thread1의 run을 보자

	@Override
	public void run() {
		for (int i = 0; i < 12; i++) {

			sA.accnt1.withdraw(100);
			System.out.println("accnt1 계좌에서 100 인출");
			sA.accnt2.deposit(100);
			System.out.println("accnt2 계좌에 100 입금");
		}
		sA.isReady=true;
	}

모든 작업이 끝난 이후에, isReady를 true로 바꾼다.

 

마지막으로 thread2의 run을 보자

	@Override
	public void run() {
		while(sA.isReady==false);
		for (int i = 0; i < 3; i++) {

			int sum = sA.accnt1.balance + sA.accnt2.balance;
			System.out.println("계좌잔액의 합= " + sum);
		}
	}

while문을 추가하여 isReady가 false이면 무한반복 하도록 하였다.

그럼 isReady가 true로 바뀌는 순간 동작을 시작할 수 있다.

 

이 방법은 정말 비효율적이지만

동작을 이해하는 정도로만 간단하게 알아보았다.

 


synchronized를 사용 해 보자.

public class TransferThread extends Thread {

	SharedArea sA;

	public TransferThread(SharedArea sA) {

		this.sA = sA;
	}

	@Override
	public void run() {
		for (int i = 0; i < 12; i++) {
			synchronized (sA) {

				sA.accnt1.withdraw(100);
				System.out.println("accnt1 계좌에서 100 인출");
				sA.accnt2.deposit(100);
				System.out.println("accnt2 계좌에 100 입금");
			}
		}
		// sA.isReady=true;
	}
}

account1.withdraw ~ account2.deposit 동작 사이에 thread2가 진행되면 문제가 생겼었다.

 

withdraw, deposit을 포함하도록 코드를 묶어 synchronized했다.

 

public class PrtThread extends Thread {

	SharedArea sA;

	public PrtThread(SharedArea sA) {
		this.sA = sA;
	}

	@Override
	public void run() {
//		while (sA.isReady == false)
//			;
		for (int i = 0; i < 3; i++) {

			synchronized (sA) {
				int sum = sA.accnt1.balance + sA.accnt2.balance;
				System.out.println("계좌잔액의 합= " + sum);
			}
			
		}
	}
}

PtrThread도 sum계산과정을 synchrozied로 묶어 안정성을 더했다.

 


또 다른 예시

public class CalcThread extends Thread {

	SharedArea sA;

	@Override
	public void run() {
		double total = 0.0;

		for (int cnt = 1; cnt < 10000; cnt += 2) {
			if (cnt / 2 % 2 == 0)

				total += 1.0 / cnt;

			else

				total -= 1.0 / cnt;
		}
		sA.result = total * 4;
		sA.isReady = true;

		synchronized (sA) {

			sA.notify();
		}
		
	}
}
public class PrtThread extends Thread {

	SharedArea sA;

	@Override
	public void run() {
		// while(sA.isReady==false) ;
		if (sA.isReady == false) {
			try {
				synchronized (sA) {

					sA.wait();
				}
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println(sA.result);

	}
}

wait(), notify()를 사용하였다.

728x90
반응형

'2022-2 > 자바' 카테고리의 다른 글

[자바] 네트워크  (0) 2022.12.17
[자바] ArrayList<클래스> 형의 정렬방법  (0) 2022.12.12
[자바] Vector  (0) 2022.12.01
자바 Exception/Generic <T>  (0) 2022.11.24
package  (0) 2022.11.15

댓글