동기화
: 공유자원을 상대로 순서대로 작업이 이루어지도록 처리하는 방법
여러개의 스레드가 공유객체를 바라보면서 문제 (임계영역에서 이상해짐) 가 생기기에 동기화를 진행한다.
=> 임계영역에 동기화 처리를 해줌으로 문제를 방지할 수 있다.
but 전체적으로 성능을 저하시키기에 필요한 영역만 사용해야 한다. (속도가 떨어짐)
- 동기화 방법
1. 메서드 자체에 동기화 설정하기
: 메소드 부분에 synchronized 사용
package kr.or.ddit.basic;
public class T15SyncThreadTest {
public static void main(String[] args) {
ShareObject sObj = new ShareObject();
WorkThread wTh1 = new WorkThread(sObj, "첫번째");
WorkThread wTh2 = new WorkThread(sObj, "두번째");
wTh1.start();
wTh2.start();
}
}
// 공유객체용 클래스
class ShareObject {
private int sum = 0;
// 동기화를 하는 방법1 : 메서드 자체에 동기화 설정하기
synchronized public void add() {
for(int i=0; i<1000000000; i++) {} // 동기화 전까지 시간벌기용
int n= sum;
n += 10;
sum = n;
System.out.println(Thread.currentThread().getName() + " 합 계 : " + sum);
}
}
// 작업용 스레드
class WorkThread extends Thread {
private ShareObject sObj;
public WorkThread(ShareObject sObj, String name) {
super(name);
this.sObj = sObj;
}
@Override
public void run() {
for(int i=1; i<=10; i++) {
sObj.add();
}
}
}
2. 동기화 블럭으로 설정하기
// mutex : Mutual exclusion Object(상호 배제: 동시접근 차단)
synchronized (mutex) {
}
package kr.or.ddit.basic;
public class T15SyncThreadTest {
public static void main(String[] args) {
ShareObject sObj = new ShareObject();
WorkThread wTh1 = new WorkThread(sObj, "첫번째");
WorkThread wTh2 = new WorkThread(sObj, "두번째");
wTh1.start();
wTh2.start();
}
}
// 공유객체용 클래스
class ShareObject {
private int sum = 0;
public void add() {
// 동기화를 하는 방법2 : 동기화 블럭으로 설정하기
// mutex : Mutual exclusion Object(상호 배제: 동시접근 차단)
synchronized (this) {
for(int i=0; i<1000000000; i++) {} // 동기화 전까지 시간벌기용
int n= sum;
n += 10;
sum = n;
System.out.println(Thread.currentThread().getName() + " 합 계 : " + sum);
}
}
}
// 작업용 스레드
class WorkThread extends Thread {
private ShareObject sObj;
public WorkThread(ShareObject sObj, String name) {
super(name);
this.sObj = sObj;
}
@Override
public void run() {
for(int i=1; i<=10; i++) {
sObj.add();
}
}
}
package kr.or.ddit.basic;
public class T15SyncThreadTest {
public static void main(String[] args) {
ShareObject sObj = new ShareObject();
WorkThread wTh1 = new WorkThread(sObj, "첫번째");
WorkThread wTh2 = new WorkThread(sObj, "두번째");
wTh1.start();
wTh2.start();
}
}
// 공유객체용 클래스
class ShareObject {
private int sum = 0;
public void add() {
for(int i=0; i<1000000000; i++) {} // 동기화 전까지 시간벌기용
int n= sum;
n += 10;
sum = n;
System.out.println(Thread.currentThread().getName() + " 합 계 : " + sum);
}
}
// 작업용 스레드
class WorkThread extends Thread {
private ShareObject sObj;
public WorkThread(ShareObject sObj, String name) {
super(name);
this.sObj = sObj;
}
@Override
public void run() {
for(int i=1; i<=10; i++) {
synchronized (sObj) {
sObj.add();
}
}
}
}
package kr.or.ddit.basic;
/**
* 은행입출금 처리 예제
*/
public class T16SyncAccountTest {
public static void main(String[] args) {
SyncAccount sAcc = new SyncAccount();
sAcc.deposit(10000);
Thread th1 = new BankThread(sAcc);
Thread th2 = new BankThread(sAcc);
th1.start();
th2.start();
}
}
// 은행의 입출금 처리를 위한 클래스
class SyncAccount {
private int balance; // 잔액
synchronized public int getBalance() {
return balance;
}
// 입금처리를 수행하기 위한 메서드
public void deposit(int money) {
this.balance += money;
}
// 출금처리를 위한 메서드(출금 성공:true, 출금 실패: false 반환함)
// 동기화 영역에서 호출하는 메서드도 동기화 처리를 해주어야 한다.
synchronized public boolean withdraw(int money) {
if(balance >= money) {
for(int i=1; i<=1000000000; i++) {} // 시간 때우기
balance -= money;
System.out.println("메서드 안에서 balance = " + getBalance());
return true;
} else {
return false;
}
}
}
// 은행 업무를 처리하기 위한 스레드
class BankThread extends Thread {
private SyncAccount sAcc;
public BankThread(SyncAccount sAcc) {
this.sAcc = sAcc;
}
@Override
public void run() {
boolean result = sAcc.withdraw(6000); // 6000원 인출
System.out.println("스레드 안에서 result = " + result
+ ", balance = " + sAcc.getBalance());
}
}
Lock Account
Lock객체 변수 선언 => 가능하면 private final로 설정한다.
- 락(Lock) 기능 제공 클래스
1. ReentrantLock
: Read 및 Write 구분 없이 사용하기 위한 락 클래스, 동기화 처리를 위해 사용
synchronized 를 이용한 동기화 처리보다 부가적인 기능이 제공된다.
ex) Fairness 설정 등 => 가장 오래 기다린 스레드가 가장 먼저 락을 획득하게 한다.
lock.lock(); : 동기화 시작
lock.unlock(); : 동기화 끝
package kr.or.ddit.basic;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class T17LockAccountTest {
/*
* 락(Lock) 기능을 제공하는 클래스
*
* - ReentrantLock : Read 및 Write 구분 없이 사용하기 위한 락 클래스로 동기화 처리를 위해 사용한다.
* synchronized 를 이용한 동기화 처리보다 부가적인 기능이 제공된다.
* ex) Fairness 설정 등 => 가장 오래 기다린 스레드가 가장 먼저 락을 획득하게 한다.
*
* - ReentrantReadWriteLock : Read 및 Write 락을 구분하여 사용가능함
* 여러 스레드가 동시에 read작업은 가능하지만, write 작업은 단지 하나의 스레만 가능함
* => Write 작업보다 Read작업 위주의 작업이 많이 발생하는 경우 사용하면 유리하다.
*/
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
LockAccount lAcc = new LockAccount(lock);
lAcc.deposit(10000);
BankThread2 bTh1 = new BankThread2(lAcc);
BankThread2 bTh2 = new BankThread2(lAcc);
bTh1.start();
bTh2.start();
}
}
// 입출금 담당을 위한 공유 객체
class LockAccount {
private int balance;
// Lock객체 변수 선언 => 가능하면 private final로 설정한다.
private final Lock lock;
public LockAccount(Lock lock) {
this.lock = lock;
}
public int getBalance() {
return balance;
}
public void deposit(int money) {
balance += money;
}
public boolean withdraw(int money) {
boolean result = false;
try {
if(balance >= money) {
for(int i=1; i<=1000000000; i++) {} // 시간 때우기
balance -= money;
System.out.println(Thread.currentThread().getName()
+ "메서드 안에서 balance = " + getBalance());
result = true;
}
} catch (Exception ex) {
ex.printStackTrace();
}
return result;
}
}
class BankThread2 extends Thread {
private LockAccount lAcc;
public BankThread2(LockAccount lAcc) {
this.lAcc = lAcc;
}
@Override
public void run() {
boolean result = lAcc.withdraw(6000);
// Thread.currentThread().getName() 대신 this.getName() 사용하여도 됨
System.out.println(Thread.currentThread().getName() + " 스레드 안에서 result = "
+ result + ", balance = " + lAcc.getBalance());
}
}
package kr.or.ddit.basic;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class T17LockAccountTest {
/*
* 락(Lock) 기능을 제공하는 클래스
*
* - ReentrantLock : Read 및 Write 구분 없이 사용하기 위한 락 클래스로 동기화 처리를 위해 사용한다.
* synchronized 를 이용한 동기화 처리보다 부가적인 기능이 제공된다.
* ex) Fairness 설정 등 => 가장 오래 기다린 스레드가 가장 먼저 락을 획득하게 한다.
*
* - ReentrantReadWriteLock : Read 및 Write 락을 구분하여 사용가능함
* 여러 스레드가 동시에 read작업은 가능하지만, write 작업은 단지 하나의 스레만 가능함
* => Write 작업보다 Read작업 위주의 작업이 많이 발생하는 경우 사용하면 유리하다.
*/
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
LockAccount lAcc = new LockAccount(lock);
lAcc.deposit(10000);
BankThread2 bTh1 = new BankThread2(lAcc);
BankThread2 bTh2 = new BankThread2(lAcc);
bTh1.start();
bTh2.start();
}
}
// 입출금 담당을 위한 공유 객체
class LockAccount {
private int balance;
// Lock객체 변수 선언 => 가능하면 private final로 설정한다.
private final Lock lock;
public LockAccount(Lock lock) {
this.lock = lock;
}
public int getBalance() {
return balance;
}
public void deposit(int money) {
balance += money;
}
public boolean withdraw(int money) {
boolean result = false;
// try ~ catch 블럭을 사용할 경우에는 unlock() 호출은 finally 블럭에서 하도록 한다.
try {
lock.lock(); // 동기화 시작
if(balance >= money) {
for(int i=1; i<=1000000000; i++) {} // 시간 때우기
balance -= money;
System.out.println(Thread.currentThread().getName()
+ "메서드 안에서 balance = " + getBalance());
result = true;
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
// lock을 걸 시 unlock을 필수적으로 선언해야함.
lock.unlock(); // 동기화 끝...
}
return result;
}
}
class BankThread2 extends Thread {
private LockAccount lAcc;
public BankThread2(LockAccount lAcc) {
this.lAcc = lAcc;
}
@Override
public void run() {
boolean result = lAcc.withdraw(6000);
// Thread.currentThread().getName() 대신 this.getName() 사용하여도 됨
System.out.println(Thread.currentThread().getName() + " 스레드 안에서 result = "
+ result + ", balance = " + lAcc.getBalance());
}
}
2. ReentrantReadWriteLock
: Read 및 Write 락을 구분하여 사용가능
여러 스레드가 동시에 read작업은 가능하지만, write 작업은 단지 하나의 스레만 가능함
=> Write 작업보다 Read작업 위주의 작업이 많이 발생하는 경우 사용하면 유리하다.
Collection 클래스 동기화 처리
Vector, Hashtable 등 예전부터 존재하던 Collection 클래스들은 내부에 동기화 처리가 되었다.
그런데 최근에 새로 구성된 Collection 클래스들은 동기화 처리가 되어 있지 않다.
그래서 동기화 처리가 필요한 경우에는 직접 동기화 처리를 해주어야 한다.
package kr.or.ddit.basic;
import java.util.ArrayList;
import java.util.List;
public class T18SynCollectionTest {
/*
* Vector, Hashtable 등 예전부터 존재하던 Collection 클래스들은 내부에 동기화 처리가 되었다.
* 그런데 최근에 새로 구성된 Collection 클래스들은 동기화 처리가 되어 있지 않다.
* 그래서 동기화 처리가 필요한 경우에는 직접 동기화 처리를 해주어야 한다.
*/
// 동기화 처리를 하지 않는 경우...
private static List<Integer> list1 = new ArrayList<Integer>();
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10000; i++) {
list1.add(i);
}
}
};
Thread[] ths = new Thread[] {
new Thread(r), new Thread(r),
new Thread(r), new Thread(r), new Thread(r)
};
for (Thread th : ths) {
th.start();
}
for (Thread th : ths) {
try {
th.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("list1의 개수 : " + list1.size());
}
}
=> 동기화 처리
package kr.or.ddit.basic;
import java.util.ArrayList;
import java.util.List;
public class T18SynCollectionTest {
/*
* Vector, Hashtable 등 예전부터 존재하던 Collection 클래스들은 내부에 동기화 처리가 되었다.
* 그런데 최근에 새로 구성된 Collection 클래스들은 동기화 처리가 되어 있지 않다.
* 그래서 동기화 처리가 필요한 경우에는 직접 동기화 처리를 해주어야 한다.
*/
// 동기화 처리를 하지 않는 경우...
private static List<Integer> list1 = new ArrayList<Integer>();
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10000; i++) {
synchronized (list1) {
list1.add(i);
}
}
}
};
Thread[] ths = new Thread[] {
new Thread(r), new Thread(r),
new Thread(r), new Thread(r), new Thread(r)
};
for (Thread th : ths) {
th.start();
}
for (Thread th : ths) {
try {
th.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("list1의 개수 : " + list1.size());
}
}
package kr.or.ddit.basic;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class T18SynCollectionTest {
/*
* Vector, Hashtable 등 예전부터 존재하던 Collection 클래스들은 내부에 동기화 처리가 되었다.
* 그런데 최근에 새로 구성된 Collection 클래스들은 동기화 처리가 되어 있지 않다.
* 그래서 동기화 처리가 필요한 경우에는 직접 동기화 처리를 해주어야 한다.
*/
// 동기화 처리를 하지 않는 경우...
private static List<Integer> list1 = new ArrayList<Integer>();
// 동기화 처리를 하지 경우...
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 = 1; i <= 10000; i++) {
list2.add(i);
}
}
};
Thread[] ths = new Thread[] {
new Thread(r), new Thread(r),
new Thread(r), new Thread(r), new Thread(r)
};
for (Thread th : ths) {
th.start();
}
for (Thread th : ths) {
try {
th.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("list2의 개수 : " + list2.size());
}
}
wait() & notify() & notifyAll()
=> 동기화 영역에서만 의미 있는 메소드
(Object 클래스에서 제공됨)
wait() (멈춤)
: 동기화 영역에서 락을 풀고 Wait-Set영역(공유객체별 존재)으로 이동시킨다.
notify() (깨움)
: 또는 notifyAll() => Wait-Set영역에 있는 스레드를 깨워서 실행될 수 있도록 한다.
(notify()는 하나, notifyAll()은 Wait-Set에 있는 전체 스레드를 깨운다.)
- 메소드 사용 전
package kr.or.ddit.basic;
public class T19WaitNotifyTest {
/*
* wait() => 동기화 영역에서 락을 풀고 Wait-Set영역(공유객체별 존재)으로 이동시킨다.
*
* notify() => 또는 notifyAll() => Wait-Set영역에 있는 스레드를 깨워서 실행될 수 있도록 한다.
* (notify()는 하나, notifyAll()은 Wait-Set에 있는 전체 스레드를 깨운다.)
*
* => wait()와 notify(), notifyAll()은 동기화 영역에서만 의미가 있는 메서드 이다.
* (Object 클래스에서 제공됨)
*/
public static void main(String[] args) {
WorkObject workObj = new WorkObject();
Thread thA = new ThreadA(workObj);
Thread thB = new ThreadB(workObj);
thA.start();
thB.start();
}
}
// 공유 객체
class WorkObject {
public synchronized void methodA() {
System.out.println("methodA()메서드에서 작업 중...");
}
public synchronized void methodB() {
System.out.println("methodB()메서드에서 작업 중...");
}
}
// workObject의 methodA()만 호출하는 스레드
class ThreadA extends Thread {
private WorkObject workObj;
public ThreadA(WorkObject workObj) {
super("ThreadA");
this.workObj = workObj;
}
@Override
public void run() {
for(int i=1; i<=10; i++) {
workObj.methodA();
}
System.out.println("ThreadA 종료");
}
}
// workObject의 methodB()만 호출하는 스레드
class ThreadB extends Thread {
private WorkObject workObj;
public ThreadB(WorkObject workObj) {
super("ThreadB");
this.workObj = workObj;
}
@Override
public void run() {
for(int i=1; i<=10; i++) {
workObj.methodB();
}
System.out.println("ThreadB 종료");
}
}
- notify(), wait() 예시
package kr.or.ddit.basic;
public class T19WaitNotifyTest {
/*
* wait() => 동기화 영역에서 락을 풀고 Wait-Set영역(공유객체별 존재)으로 이동시킨다.
*
* notify() => 또는 notifyAll() => Wait-Set영역에 있는 스레드를 깨워서 실행될 수 있도록 한다.
* (notify()는 하나, notifyAll()은 Wait-Set에 있는 전체 스레드를 깨운다.)
*
* => wait()와 notify(), notifyAll()은 동기화 영역에서만 의미가 있는 메서드 이다.
* (Object 클래스에서 제공됨)
*/
public static void main(String[] args) {
WorkObject workObj = new WorkObject();
Thread thA = new ThreadA(workObj);
Thread thB = new ThreadB(workObj);
thA.start();
thB.start();
}
}
// 공유 객체
class WorkObject {
public synchronized void methodA() {
System.out.println("methodA()메서드에서 작업 중...");
System.out.println(Thread.currentThread().getName() + " : notify() 호출");
notify();
System.out.println(Thread.currentThread().getName() + " : wait() 호출");
try {
// wait();
wait(3000); // 3초간 작동
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void methodB() {
System.out.println("methodB()메서드에서 작업 중...");
System.out.println(Thread.currentThread().getName() + " : notify() 호출");
notify();
System.out.println(Thread.currentThread().getName() + " : wait() 호출");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// workObject의 methodA()만 호출하는 스레드
class ThreadA extends Thread {
private WorkObject workObj;
public ThreadA(WorkObject workObj) {
super("ThreadA");
this.workObj = workObj;
}
@Override
public void run() {
for(int i=1; i<=10; i++) {
workObj.methodA();
}
System.out.println("ThreadA 종료");
}
}
// workObject의 methodB()만 호출하는 스레드
class ThreadB extends Thread {
private WorkObject workObj;
public ThreadB(WorkObject workObj) {
super("ThreadB");
this.workObj = workObj;
}
@Override
public void run() {
for(int i=1; i<=10; i++) {
workObj.methodB();
}
System.out.println("ThreadB 종료");
}
}
package kr.or.ddit.basic;
public class T20WaitNotifyTest {
public static void main(String[] args) {
DataBox dataBox = new DataBox();
ProducerThread pTh = new ProducerThread(dataBox);
ConsumerThread cTh = new ConsumerThread(dataBox);
pTh.start();
cTh.start();
}
}
// 공유 객체
class DataBox {
private String data;
// data가 null이 아닐 때 data값을 반환하는 메서드.
public synchronized String getData() {
System.out.println(Thread.currentThread().getName() +
" : getData() 메서드 진입...");
if(this.data == null) {
try {
System.out.println(Thread.currentThread().getName() +
" : getData() 메서드 안에서 wait() 호출");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String returnData = this.data;
System.out.println("읽어온 데이터 : " + this.data);
this.data = null; // 데이터 제거하기
System.out.println(Thread.currentThread().getName()
+ " : getData() 메서드 안에서 notify() 호출");
notify();
System.out.println(Thread.currentThread().getName()
+ " : getData() 메서드 끝...");
return returnData;
}
// data가 null일 경우에만 데이터를 세팅하는 메서드
public synchronized void setData(String data) {
System.out.println(Thread.currentThread().getName() +
" : setData() 메서드 진입...");
if(this.data != null) {
try {
System.out.println(Thread.currentThread().getName() +
" : setData() 메서드 안에서 wait() 호출");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.data = data;
System.out.println("세팅한 데이터 : " + this.data);
System.out.println(Thread.currentThread().getName() +
" : setData() 메서드 안에서 notify() 호출");
notify();
System.out.println(Thread.currentThread().getName()
+ " : setData() 메서드 끝...");
}
}
// 데이터를 세팅만 하는 스레드
class ProducerThread extends Thread {
private DataBox dataBox;
// 데이터 값을 생산하는 스레드
public ProducerThread(DataBox dataBox) {
super("ProducerThread");
this.dataBox = dataBox;
}
@Override
public void run() {
for(int i=1; i<=5; i++) {
String data = "Data-" + i;
System.out.println(
this.getName() + "가 dataBox.setData(" + data + ") 호출하려고 함.");
dataBox.setData(data);
}
}
}
// 데이터를 읽어만 오는 스레드
class ConsumerThread extends Thread {
private DataBox dataBox;
public ConsumerThread(DataBox dataBox) {
super("ConsumerThread");
this.dataBox = dataBox;
}
@Override
public void run() {
for(int i=1; i<=5; i++) {
String data = dataBox.getData();
System.out.println(this.getName() + "가 dataBox.getData() 호출 후 결과 받음 : "
+ data);
}
}
}
'자바' 카테고리의 다른 글
[Java 고급] 12장 File 객체 (0) | 2024.01.30 |
---|---|
[Java 고급] 10.5장 Test (1) | 2024.01.29 |
[Java 고급] 10장 Thread상태, Thread 예시2 (1) | 2024.01.29 |
[Java 고급] 9장 Process와 Thread 예시1 (1) | 2024.01.27 |
[Java 고급] 8장 Annotation (0) | 2024.01.27 |