java/java.lang

[java/lang] 8. 입출금프로그램 (feat. 다중 스레드)

jeri 2024. 6. 17. 00:01
반응형

01. 운영체제 & JWM 에 의한 애플리케이션 실행방법

  • 운영체제로부터 실행에 필요한 메모리를 할당받아 애플리케이션이 실행된다.
  • 자바는 운영체제 위에 JVM이 올라가고, JVM으로부터 메모리를 할당받아 애플리케이션이 실행된다.

하드디스크(보조기억장치)

  • 보조기억장치(비휘발성) , 단가쌈

메모리(주기억장치)

  • 주기억장치 (휘발성) , flach memory , 단가비쌈
  • 요즘 비휘발성에 단가 저렴한 메모리가 생겨남! 즉, 모든 것이 메모리에 저장되어 처리할 수 있을듯?

CPU(중앙처리장치)

  • 프로그램 실행 (아이콘 클릭!)
  • 보조기억장치에 있는 메모리가 주기억장치(ram)로 이동
  • 그것을 대신 해주는 것, 도와주는 것이 운영체제
  • 자바는 JVM이 도와줌

 

 

02. 프로세스(Process)와 스레드(Thread)

🎩프로세스(Process)

  • 메모리에 저장되어 중앙처리장치(CPU)에 의해 실행되는 명령 프로그램
  • [1개 프로세스 내 2개 스레드가 있다] = 2개의 코드 실행 흐름을 가진 프로그램

🎩스레드(Thread)

  • 프로그램에서 명령을 실행하기 위한 최소의 작업 단위 - 프로그램 흐름
  • 하나의 스레드 = 하나의 코드 실행 흐름
  • 스레드 = 프로세스를 구성하는 실행의 최소 단위
  • 스레드 = 프로세스 내부 코드의 실행 흐름

🎩멀티프로세스(MultiProcess)와 멀티스레드(MultiThread)

  • 프로세스1 에는 메모장 앱이 실행중
  • 프로세스2에는 계산기 앱이 실행중 , 프로세스3에는 계산기 앱이 실행중
  • (즉, 2개의 프로세스 실행 = 계산기 애플리케이션 2개 실행중)
  • 프로세스4에는 카카오톡 앱이 실행중

 

 

 

 

03. 입출금 프로그램 단일스레드

1. 💸 Account.java

package xyz.itwill.thread;

//은행계좌정보(잔액)를 저장하기 위한 클래스
public class Account {
	//필드
	private int balance;
	//생성자
	public Account() {	}
	public Account(int balance) {super(); this.balance = balance;}
	//Getter & Setter 메소드
	public int getBalance() {return balance;}
	public void setBalance(int balance) {this.balance = balance;}
	
	//입금 처리 메소드 - 입금자와 입금액을 전달받아 처리
	public /*synchronized*/ void deposit(String name, int amount) {
		balance += amount;
		System.out.println("[입금]" + name + "님이 "+amount+"원을 입금하여 잔액은 "+balance+"원 입니다.");
	}
   
	//출금 처리 메소드 - 출금자와 출금액을 전달받아 처리
	public /*synchronized*/ void withDraw(String name, int amount) {
		if(balance < amount) {
			System.out.println("[에러]"+name+"님, 잔액이 "+balance+"원 남아 "+amount+"원을 출금할 수 없습니다.");
			return;
		}
		balance -= amount;
		System.out.println("[출금]" + name + "님이 "+amount+"원을 출금하여 잔액은 "+balance+"원 입니다.");
	}
}

2. 🏦 AccountUser.java

package xyz.itwill.thread;

//은행 계좌 사용자 정보(은행계좌정보, 사용자명)를 저장하기 위한 클래스
public class AccountUser /*extends Thread*/ {
	//필드
	private Account account; //은행계좌정보 - 포함관계
	private String userName;
	//생성자
	public AccountUser() {	}
	public AccountUser(Account account, String userName) {
		super();
		this.account = account;
		this.userName = userName;
	}
	//Getter & Setter 메소드
	public Account getAccount() {return account;}
	public void setAccount(Account account) {this.account = account;}
	public String getUserName() {return userName;}
	public void setUserName(String userName) {this.userName = userName;}
}

3. 🎩AccountUserApp (단일스레드 입출금 프로그램)

package xyz.itwill.thread;

public class AccountUserApp {
	public static void main(String[] args) {

		//은행계좌정보를 생성하여 저장
		Account account = new Account(10000); //처음 잔액 : 10000원 (동일한 계좌)

		//단일 스레드(main스레드)를 이용하여 은행계좌 사용자를 생성하여 입금 처리
		AccountUser[] users = new AccountUser[3];
		//모든 사용자가 동일한 계좌 사용 (포함관계 성립)
		users[0] = new AccountUser(account, "홍길동"); //users[0] = new AccountUser(new Account(10000), "홍길동");과 같은효과
		users[1] = new AccountUser(account, "임꺽정");
		users[2] = new AccountUser(account, "전우치");

		//일괄처리
		for(AccountUser user:users) {
			user.getAccount().deposit(user.getUserName(), 5000);
		}

	}
}

//[입금]홍길동님이 5000원을 입금하여 잔액은 15000원 입니다.
//[입금]임꺽정님이 5000원을 입금하여 잔액은 20000원 입니다.
//[입금]전우치님이 5000원을 입금하여 잔액은 25000원 입니다.

 

 

 

 

 

04. 입출금 프로그램 다중스레드 (문제점)

1. 💸 Account.java - (변동x)

package xyz.itwill.thread;

//은행계좌정보(잔액)를 저장하기 위한 클래스
public class Account {
	//필드
	private int balance;
	//생성자
	public Account() {	}
	public Account(int balance) {super(); this.balance = balance;}
	//Getter & Setter 메소드
	public int getBalance() {return balance;}
	public void setBalance(int balance) {this.balance = balance;}
	
	//입금 처리 메소드 - 입금자와 입금액을 전달받아 처리
	public /*synchronized*/ void deposit(String name, int amount) {
		balance += amount;
		System.out.println("[입금]" + name + "님이 "+amount+"원을 입금하여 잔액은 "+balance+"원 입니다.");
	}
   
	//출금 처리 메소드 - 출금자와 출금액을 전달받아 처리
	public /*synchronized*/ void withDraw(String name, int amount) {
		if(balance < amount) {
			System.out.println("[에러]"+name+"님, 잔액이 "+balance+"원 남아 "+amount+"원을 출금할 수 없습니다.");
			return;
		}
		balance -= amount;
		System.out.println("[출금]" + name + "님이 "+amount+"원을 출금하여 잔액은 "+balance+"원 입니다.");
	}
}

2. 🏦AccountUser.java - (변동o)

package xyz.itwill.thread;

//은행 계좌 사용자 정보(은행계좌정보, 사용자명)를 저장하기 위한 클래스
public class AccountUser extends Thread {
	//필드
	private Account account; //은행계좌정보 - 포함관계
	private String userName;
	//생성자
	public AccountUser() {	}
	public AccountUser(Account account, String userName) {
		super();
		this.account = account;
		this.userName = userName;
	}
	//Getter & Setter 메소드
	public Account getAccount() {return account;}
	public void setAccount(Account account) {this.account = account;}
	public String getUserName() {return userName;}
	public void setUserName(String userName) {this.userName = userName;}
}
	@Override
	public void run() {
		//프로그램 개발자의 의해 생성된 새로운 스레드가 run() 메소드의 명령 실행
		// => 은행계좌 사용자에 의한 은행계좌의 입금 처리 메소드 호출
    	//1.
		account.deposit(userName, 5000);
    	//2.
    	//account.withDraw(username, 5000);
	}

}

3. 🎩AccountUserApp (다중스레드 입출금 프로그램)

public class AccountUserApp {
   public static void main(String[] args) {

   	//은행계좌정보를 생성하여 저장
   	Account account = new Account(10000); //처음 잔액 : 10000원 (동일한 계좌)

   	//main스레드의 역할 : new연산자와 스레드만 호출(start()메소드)하고 다음 코드 실행
   	//다중 스레드를 이용하여 은행계좌 사용자를 생성하여 입금 처리 - 입금처리가 run메소드 안에서 실행됨

   	new AccountUser(account, "홍길동").start(); //새 스레드(AccountUser스레드1) 형성되어 run메소드 호출됨
	   //=> AccountUser객체와 스레드1객체 생성 -> 스레드1이 run()메소드 호출 -> 입금처리코드 실행

   	new AccountUser(account, "임꺽정").start(); //새 스레드(AccountUser스레드2) 형성되어 run메소드 호출됨
	   //=>AccountUser객체와 스레드2객체 생성 -> 스레드2이 run()메소드 호출 -> 입금처리코드 실행

   	new AccountUser(account, "전우치").start(); //새 스레드(AccountUser스레드3) 형성되어 run메소드 호출됨
     //=>AccountUser객체와 스레드3객체 생성 -> 스레드3이 run()메소드 호출 -> 입금처리코드 실행

   	//[입금]임꺽정님이 5000원을 입금하여 잔액은 20000원 입니다.
   	//[입금]홍길동님이 5000원을 입금하여 잔액은 20000원 입니다.
   	//[입금]전우치님이 5000원을 입금하여 잔액은 25000원 입니다.
   	// => 이상한 결과값

		// => 새로운 스레드들이 자동으로 입금처리했지만, 문제는 어떤 스레드가 먼저 입금처리했을지 모름!
		// => 다시말해, 어떤 스레드가 먼저 CPU에게 전달하느냐에 따라서 결과값이 달라짐
		// => CPU는 스레드가 요청한 결과값을 요청한 스레드에게 주어야하는데, 요청하지 않은 다른 스레드에게도 무작위로 줄 수 있음
		// => 즉, 공유데이터에 대한 결과를 공유하는 스레드라면 모두 공유값을 받을 수 있음!
		// => 이것이 바로 다중스레드의 단점!!

   }
}

 

 

 

 

 

05. 입출금 프로그램 다중스레드 (해결법)

🎩 다중스레드의 문제점

  • 동일한 다수의 스레드 (= 같은 클래스로 만들어진 스레드)가 run메소드의 명령을 동시에 실행할 경우,
  • 메소드를 호출해 필드값(공유값)을 변경하면
  • 잘못된 결과를 발생할 수 있음
  • (발생하지 않을수도 있지만 발생할 가능성이 높음)

🎩 다중스레드의 문제 해결법

  • 스레드 동기화를 이용하여 스레드에 대한 메소드 호출을 제어할 수 있도록 만들기

🎩스레드 동기화(Thread Synchronized)

  • 스레드에 의해 메소드 호출 시 메소드의 명령을 모두 처리하기 전까지 다른 스레드에 호출을 방지하기 위한 기능
  • 스레드 락(Lock) 기능 제공

🎩스레드 동기화 처리의 단점

  • 실행속도가 매우 느려짐ㅠ
  • 그래서 무조건 모든 메소드들을 동기화 처리하는 것은 아님!

🎩그럼 스레드 동기화 처리 언제 사용하면 좋을까?

  • 동일한 다수의 스레드 (= 같은 클래스로 만들어진 스레드)가
  • run메소드의 명령을 동시에 실행하여 메소드를 호출해 필드값(공유값)을 변경할 때

1. 💸 Account.java - (🐻방법1에 의해 변동o)

package xyz.itwill.thread;

//은행계좌정보(잔액)를 저장하기 위한 클래스
public class Account {
	//필드
	private int balance;
	//생성자
	public Account() {	}
	public Account(int balance) {super(); this.balance = balance;}
	//Getter & Setter 메소드
	public int getBalance() {return balance;}
	public void setBalance(int balance) {this.balance = balance;}
	

	//🐻방법1에 의해 변동o
	//입금 처리 메소드 - 매개변수로 입금자와 입금액을 전달받아 처리
	public synchronized void deposit(String name, int amount) {
		balance+=amount;
		System.out.println("[입금]"+name+"님이 "+amount+"원을 입금하여 잔액은 "+balance+"원입니다.");
	}
	
	//출금 처리 메소드 - 매개변수로 출금자와 출금액을 전달받아 처리
	public void withDraw(String name, int amount) {
		if(balance<amount) {
			System.out.println("[에러]"+name+"님, 잔액이 "+balance+"원 남아 "+amount+"원을 출금할 수 없습니다.");
			return;
		}	
		balance-=amount;
		System.out.println("[출금]"+name+"님이 "+amount+"원을 출금하여 잔액은 "+balance+"원입니다.");
	}
}

2. 🏦 AccountUser.java - (🐻방법2에 의해 변동o)

package xyz.itwill.thread;

//은행계좌 사용자정보(은행계좌정보, 사용자명)를 저장하기 위한 클래스
public class AccountUser extends Thread {
	//필드
	private Account account; //은행계좌정보 - 포함관계
	private String userName;
	//생성자
	public AccountUser() {	}
	public AccountUser(Account account, String userName) {
		super();
		this.account = account;
		this.userName = userName;
	}
	//Getter & Setter 메소드
	public Account getAccount() {return account;}
	public void setAccount(Account account) {this.account = account;}
	public String getUserName() {return userName;}
	public void setUserName(String userName) {this.userName = userName;}
}
	@Override
	public void run() {
		//프로그램 개발자의 의해 생성된 새로운 스레드가 run() 메소드의 명령 실행
		// => 은행계좌 사용자에 의한 은행계좌의 입금 처리 메소드 호출
		//account.deposit(userName, 5000);	
	
		//🐻방법2에 의해 변동o
		synchronized (account) {
			account.withDraw(userName, 5000);
		}
	}
}

3. 🎩AccountUserApp (다중스레드 입출금 프로그램)

package xyz.itwill.thread;

//다중스레드 프로그램의 문제점
// => 동일한 다수의 스레드가 run() 메소드의 명령을 동시에 실행할 경우 메소드를 호출하여
//필드값(공유)을 변경하면 잘못된 처리결과 발생 가능
//해결법)스레드 동기화를 이용하여 스레드에 대한 메소드 호출 제어

//스레드 동기화(Thread Synchronize) : 스레드에 의해 메소드 호출시 메소드의 모든 명령을 처리
//하기 전까지 다른 스레드의 메소드 실행을 방지하기 위한 기능
// => 스레드를 일시 중지하여 명령이 실행되지 않도록 락(Lock)기능 제공

//스레드 동기화 처리 방법
//🐻방법1 - Account.java
// => 동기화 메소드 만들기(Synchronized Method) - 권장
// => 클래스 내 메소드에 직접 작성
// => Synchronized 키워드를 사용하여 메소드 선언
// => 동기화메소드를 이용할 때마다 스레드 락(Lock) 기능 제공
// => 동기화를 선언한 메소드만 스레드 락 기능이 제공됨
// => 단, 어떤 스레드가 먼저 접근할지는 아무도 모르지만, 먼저 접근한 스레드의 코드가 다 실행될 동안 다른 스레드 호출 차단!
// => 형식) 접근제한자 synchronized 반환형 메소드명(자료형 매개변수명,...) { }

//🐻방법2 - AccountUser.java
// => Synchronized 키워드로 블록 설정 후 메소드 호출 하기 - 비권장
// => 클래스 내 메소드를 직접 고칠 수 없다면??????
// => ex) 배포된클래스 : 상속받은 스레드 클래스에 작성 - 비권장.. 왜? - 어떤 메소드는 동기화처리해야하지만, 어떤 메소드는 동기화처리하면 안되기 때문
// => 객체로 호출되는 모든 메소드는 동기화 처리되어 실행됨
//형식) synchronized(객체) { 객체.메소드명(값,...); ... } - 객체로 호출되는 모든 메소드는 동기화 처리되어 실행

public class AccountUserApp {
	public static void main(String[] args) {

		//은행계좌정보를 생성하여 저장
		Account account=new Account(10000);//잔액 : 10000원
				
		//다중 스레드를 이용하여 은행계좌 사용자를 생성하여 입금(출금) 처리
		//🐻방법1 - Account.java 변경으로 인한 결과값
		//1. 스레드 동기화 처리한 후 결과값 (입금)
		new AccountUser(account, "홍길동").start();
		new AccountUser(account, "임꺽정").start();
		new AccountUser(account, "전우치").start();
		//[입금]홍길동님이 5000원을 입금하여 잔액은 15000원 입니다.
		//[입금]전우치님이 5000원을 입금하여 잔액은 20000원 입니다.
		//[입금]임꺽정님이 5000원을 입금하여 잔액은 25000원 입니다.
		//정상결과
		//2. 스레드 동기화 처리한 후 결과값 (출금)
		new AccountUser(account, "홍길동").start();
		new AccountUser(account, "임꺽정").start();
		new AccountUser(account, "전우치").start();
		//[출금]홍길동님이 5000원을 출금하여 잔액은 5000원 입니다.
		//[출금]전우치님이 5000원을 출금하여 잔액은 0원 입니다.
		//[에러]임꺽정님, 잔액이 0원 남아 5000원을 출금할 수 없습니다.
    //정상결과
    }

		//🐻방법2 - AccountUser.java 변경으로 인한 결과값
		//배포받은 클래스는 직접 고칠 수 없어!! - 어떻게 할까? 2번째 방법을 이용하자!
		new AccountUser(account, "홍길동").start();
		new AccountUser(account, "임꺽정").start();
		new AccountUser(account, "전우치").start();
		//[출금]홍길동님이 5000원을 출금하여 잔액은 5000원 입니다.
		//[출금]전우치님이 5000원을 출금하여 잔액은 0원 입니다.
		//[에러]임꺽정님, 잔액이 0원 남아 5000원을 출금할 수 없습니다.
    //정상결과
    }

		
	}
}

 

 

 

 

06. 배포된 클래스의 대다수는 동기화 처리 되어있다??

  • ex) StringBuffer클래스는 동기화 처리된 메소드들을 가지고 있어 다중스레드 프로그램 만들 때 안전하게 조작가능
  • 반면에 StringBuilder클래스는 비동기화 처리 메소드라 안전하지 않음
  • 대신 처리속도가 훨씬 빠름
  • 다중스레드를 사용해야 하는데, 이로 인해 잘못된 결과가 나올 수 있을 것 같다면 안전하게 StringBuffer이용하기
  • 이 밖에도 동기화처리된 메소드들 많으니 살펴보기
반응형