스레드에서 객체를 공통으로 사용하는 예제
원주율을 계산하는 스레드와 계산이 완료되면 계산된 원주율을 출력하는 스레드가 있다
원주율을 저장하는 객체가 필요하다 이 객체를 두 스레드에서 공통으로 사용한다
public class ThreadTest14 {
public static void main(String[] args) {
// 공통으로 사용할 객체를 생성한다
ShareData data = new ShareData();
// 스레드 객체를 생성하고 공통으로 사용할 객체를 각각의 스레드에 주입한다
CalcPIThread cal = new CalcPIThread(data);
PrintPIThread print = new PrintPIThread();
print.setData(data);
cal.start();
print.start();
}
}
// 원주율을 계산하는 스레드
class CalcPIThread extends Thread {
private ShareData data;
public CalcPIThread(ShareData data) {
this.data = data;
}
@Override
public void run() {
/*
* 원주율 = (1/1 - 1/3 + 1/5 - 1/7 + 1/9 ... ) * 4
* 1 -3 5 -7 9
* (2의 몫) 0 1 2 3 4
*/
double sum = 0.0;
for (int i = 1; i < 1_000_000_000; i += 2) {
if ((i / 2) % 2 != 0) {
sum -= (1.0 / i);
} else { // 2로 나눈 몫이 짝수일 때
sum += (1.0 / i);
}
}
data.result = sum * 4;
data.isOk = true;
}
}
// 계산된 원주율을 출력하는 스레드
class PrintPIThread extends Thread {
private ShareData data;
// Setter
public void setData(ShareData data) {
this.data = data;
}
@Override
public void run() {
while (true) {
if (data.isOk) {
break;
} else {
Thread.yield();
}
}
System.out.println();
System.out.println("결과 : " + data.result);
System.out.println("PI : " + Math.PI);
}
}
// 원주율을 관리하는 클래스 (공통으로 사용할 클래스)
class ShareData {
public double result; // 계산된 원주율이 저장될 변수
public boolean isOk = false; // 계산이 완료되었는지 여부를 나타내는 변수
}
동기화 synchronized
멀티스레드 환경에서 반드시 스레드간 동기화 문제를 해결해야함
(예시로 atm기에서 돈을 동시에 인출할 때 인출하는 과정 속에 A라는 사람이 인출을 시도하고 나서 B 사람이 인출을 시도한다면 A 인출 중간에 들어오면서 A가 인출한 결과가 적용되지 않아 잔액보다 더한 돈이 인출될 수 있음
> 이에 대한 예시는 2번째에 존재)
자바에서는 synchronized 키워드를 제공해 스레드 간 동기화를 시켜 data의 thread-safe를 가능케함
- 메서드에서 사용하는 경우
public synchronized void 메소드이름(){ } - 객체 변수에 사용하는 경우(block문)
private Object 객체이름 = new Object();
public void 메소드 이름(){ synchronized (객체이름) { }}
public class ThreadTest15 {
public static void main(String[] args) {
ShareObject sObj = new ShareObject();
TestThread th1 = new TestThread("Test1", sObj);
TestThread th2 = new TestThread("Test2", sObj);
th1.start();
th2.start();
}
}
class TestThread extends Thread{
private ShareObject sObj;
// 생성자
public TestThread(String name, ShareObject sObj) {
super(name); // 스레드의 name 설정
this.sObj = sObj;
}
@Override
public void run() {
for(int i=1; i<=10; i++) {
sObj.add();
}
}
}
// 공통 클래스
class ShareObject{
private int sum = 0;
// 동기화하기
// lock을 걸어 실행하는 동안 다른 스레드가 사용하지 못하게함
// 단순히 합계에서 10더해주는 메서드
// 다른 스레드에게 넘어갈 수 있는 환경을 제공하기 위해 단계를 나눠서 사용
// public synchronized void add() { // 방법 1 : 메서드에 동기화 설정하기
public void add() {
synchronized (this) { // 방법 2 : 동기화 블럭을 이용하여 설정하기
// 동기화로 처리할 내용을 넣어주기
int n = sum;
n+= 10;
sum = n;
System.out.println(Thread.currentThread().getName() + " 합계 : "+sum);
}
}
}
은행의 입출금을 스레드로 처리하는 예제
public class ThreadTest16 {
private int balance; // 잔액이 저장될 변수
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
// 입금을 처리하는 메서드
public void deposit(int money) {
balance += money;
}
// 출금을 처리하는 메서드 (반환값 => 출금 성공 :true, 출금 실패 : false)
// public synchronized boolean withdraw(int money) { // 동기화 처리 유무
public boolean withdraw(int money) {
synchronized (this) {
if (balance < money) {
return false;
} else {
for (int i = 1; i <= 1000000000; i++) { // 브레이크 걸기(시간 소비)
int k = i + 1;
}
balance -= money;
System.out.println("메서드 안에서 balance : " + balance);
return true;
}
}
}
public static void main(String[] args) {
// 공통 객체 생성
ThreadTest16 account = new ThreadTest16();
account.setBalance(10000); // 잔액을 10000원으로 설정
// 익명 구현체로 스레드 구현
Runnable runTest = new Runnable() {
@Override
public void run() {
boolean result = account.withdraw(6000); // 6000원 출금하기
System.out.println(
Thread.currentThread().getName() + "에서 result = " + result + ", 잔액 = " + account.balance);
}
};
Thread th1 = new Thread(runTest);
Thread th2 = new Thread(runTest);
th1.start();
th2.start();
// 동기화 처리하지 않은 결과
// 메서드 안에서 balance : -2000
// 메서드 안에서 balance : -2000
// Thread-0에서 result = true, 잔액 = -2000
// Thread-1에서 result = true, 잔액 = -2000
// 동기화 처리한 결과
// 메서드 안에서 balance : 4000
// Thread-0에서 result = true, 잔액 = 4000
// Thread-1에서 result = false, 잔액 = 4000
}
}
List 동기화 처리
private static List<타입> 리스트이름 = Collections.synchronizedList(new ArrayList<타입>());
Vector, Hashtable등과 같이 자바의 초창기부터 존재하던 Collection객체들은
내부에 동기화 처리가 되어있다
그런데 새로 구성된 Collection들은 동기화 처리가 되어 있지 않다.
그래서, 동기화가 필요한 프로그램에서 이런 Collection들을 사용하려면
동기화 처리를 한 후에 사용해야 한다.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
public class ThreadTest17 {
private static Vector<Integer> vec = new Vector<Integer>();
// 동기화 처리가 되지 않은 List
private static List<Integer> list1 = new ArrayList<>();
// 동기화 처리를 한 List
private static List<Integer> list2 =
Collections.synchronizedList(new ArrayList<Integer>());
public static void main(String[] args) {
// 익명 구현체로 스레드 구현
Runnable r = new Runnable() {
@Override
public void run() {
for(int i=0; i<10000; i++) {
// vec.add(i);
// list1.add(i);
list2.add(i);
}
}
};
Thread[] thArr = new Thread[] {
new Thread(r), new Thread(r), new Thread(r), new Thread(r), new Thread(r)
};
for(Thread th : thArr) {
th.start();
}
for(Thread th : thArr) {
try {
th.join();
} catch (InterruptedException e) {
// TODO: handle exception
}
}
// System.out.println("vec의 개수 : "+vec.size()); // vec의 개수 : 50000
// System.out.println("list의 개수 : "+list1.size()); // list의 개수 : 10162
// Exception in thread "Thread-4" Exception in thread "Thread-2" Exception in thread "Thread-3" Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: 22
// 기존의 데이터가 덮어씌어지면서 발생하는 현상
System.out.println("list의 개수 : "+list2.size()); // list의 개수 : 50000
}
}
동기화 - wait(), notify(), notifyAll()
wait() : 객체의 lock을 풀고 해당 객체의 스레드를 waiting pool에 넣는다
notify() : waiting pool에서 대기중인 스레드 중의 하나를 깨운다
notifyAll() : waiting pool에서 대기중인 모든 스레드를 깨운다.
public class ThreadTest18 {
public static void main(String[] args) {
WorkObject wObj = new WorkObject();
WorkThread01 wth1 = new WorkThread01(wObj);
WorkThread02 wth2 = new WorkThread02(wObj);
wth1.start();
wth2.start();
}
}
// WorkObject의 method01()메서드만 호출하는 스레드
class WorkThread01 extends Thread {
private WorkObject wObj;
public WorkThread01(WorkObject wObj) {
super();
this.wObj = wObj;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
wObj.method01();
}
synchronized (wObj) {
wObj.notify(); // 동기화 영역이 아님
}
}
}
// WorkObject의 method02()메서드만 호출하는 스레드
class WorkThread02 extends Thread {
private WorkObject wObj;
public WorkThread02(WorkObject wObj) {
super();
this.wObj = wObj;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
wObj.method02();
}
synchronized (wObj) {
wObj.notify(); // 동기화 영역이 아님
}
}
}
// 공통으로 사용할 객체
class WorkObject {
public synchronized void method01() {
System.out.println("method01()메서드 실행 중...");
notify();
try {
wait();
} catch (InterruptedException e) {
// TODO: handle exception
}
}
public synchronized void method02() {
System.out.println("method02()메서드 실행 중...");
notify();
try {
wait();
} catch (InterruptedException e) {
// TODO: handle exception
}
}
}
public class ThreadTest19 {
public static void main(String[] args) {
DataBox dataBox = new DataBox();
ProducerThread th1 = new ProducerThread(dataBox);
ConsumerThread th2 = new ConsumerThread(dataBox);
th1.start();
th2.start();
}
}
// 데이터를 공통으로 사용하는 클래스
class DataBox {
private String value;
// 저장된 데이터를 가져가는 메서드
// 추가적인 기능 포함
// ==> value 변수의 값이 null이면 value변수에 문자열이 채워질 때까지 기다리고,
// value변수에 값이 있으면 해당 문자열을 반환한다.
// 반환 후에는 value변수값을 null로 만든다.
public synchronized String getValue() {
if (value == null) {
try {
wait();
} catch (InterruptedException e) {
// TODO: handle exception
}
}
// value변수에 데이터가 있을 때의 처리 내용
String temp = value;
System.out.println("스레드가 읽은 데이터 : " + temp);
value = null;
notify();
return temp;
}
// 데이터를 저장하는 메서드
// ==> value변수에 값이 있으면 value변수가 null이 될 때까지 기다림
// value변수값이 null이 되면 새로운 데이터를 저장한다.
public synchronized void setValue(String value) {
if (this.value != null) {
try {
wait();
} catch (InterruptedException e) {
// TODO: handle exception
}
}
// this.value변수에 새로운 데이터를 저장
notify();
this.value = value;
}
}
// 데이터를 꺼내서 사용하는 스레드
class ConsumerThread extends Thread {
private DataBox dataBox;
public ConsumerThread(DataBox dataBox) {
super();
this.dataBox = dataBox;
}
@Override
public void run() {
for (int i = 0; i < 4; i++) {
System.out.println(dataBox.getValue());
}
}
}
// 데이터를 넣는 스레드
class ProducerThread extends Thread {
private DataBox dataBox;
public ProducerThread(DataBox dataBox) {
super();
this.dataBox = dataBox;
}
@Override
public void run() {
String nameArr[] = { "홍길동", "이순신", "강감찬", "이몽룡" };
for (String str : nameArr) {
String value = str;
dataBox.setValue(value);
}
}
}
'JAVA > HIGH JAVA' 카테고리의 다른 글
5/3 Homework 사진 파일 복사 (0) | 2024.05.03 |
---|---|
[JAVA] 입출력( I/O ) - File객체 (0) | 2024.05.02 |
4/30 Homework - 경마 프로그램 (0) | 2024.04.30 |
[JAVA] 스레드 - 데몬스레드/상태/yield/종료 (0) | 2024.04.30 |
4/29 Homework - 가위바위보 게임 (0) | 2024.04.29 |