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()를 사용하였다.
'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 |
댓글