스레드의 동기화
한 번에 하나의 스레드만 객체에 접근할 수 있도록 객체에 lock을 걸어서 데이터의 일관성을 유지하는 것
1. 메서드에 lock을 걸고자 할 때
public synchronized void calcSum() {
}
2. 특정한 객체에 lock을 걸고자 할 때
synchronized(객체의 참조변수) {
}
🌳 실습
public class ThreadTest16 {
public static void main(String[] args) {
ShareObject sObj = new ShareObject();
// 각각의 스레드가 하는 일은 각 객체의 add() 메소드를 10번 씩 호출하는 것이다.
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;
// 동기화 하기
// 방법 1 ==> 메소드에 동기화 설정을 한다.
// public synchronized void add() {
public void add() {
// 방법 2 ==> 동기화 블럭으로 설정한다.
synchronized (this) {
int n = sum;
n += 10;
sum = n;
System.out.println(Thread.currentThread().getName() + " 합계 : " + sum);
}
}
}
// 실행 결과
// test1 합계 : 10
// test1 합계 : 20
// test1 합계 : 30
// test1 합계 : 40
// test1 합계 : 50
// test1 합계 : 60
// test1 합계 : 70
// test1 합계 : 80
// test1 합계 : 90
// test1 합계 : 100
// test2 합계 : 110
// test2 합계 : 120
// test2 합계 : 130
// test2 합계 : 140
// test2 합계 : 150
// test2 합계 : 160
// test2 합계 : 170
// test2 합계 : 180
// test2 합계 : 190
// test2 합계 : 200
/*
* 객체의 동기화가 되지 않았을 때
* test1 합계 : 20
* test2 합계 : 20
* test2 합계 : 40
* test2 합계 : 50
* test2 합계 : 60
* test2 합계 : 70
* test2 합계 : 80
* test2 합계 : 90
* test2 합계 : 100
* test2 합계 : 110
* test2 합계 : 120
* test1 합계 : 30
* test1 합계 : 130
* test1 합계 : 140
* test1 합계 : 150
* test1 합계 : 160
* test1 합계 : 170
* test1 합계 : 180
* test1 합계 : 190
* test1 합계 : 200
*/
⚡ 은행의 입출금을 스레드로 처리하는 예제
1. synchronized를 이용한 동기화 처리 예제
public class ThreadTest17 {
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) {
if (balance >= money) {
for (int i = 1; i <= 100_000_000; i++) {
} // 시간 지연용
balance -= money;
System.out.println("메소드 안에서 balance = " + balance);
return true; // true를 반환함과 동시에 lock이 풀림
}
return false;
}
public static void main(String[] args) {
ThreadTest17 account = new ThreadTest17();
account.setBalance(10_000); // 잔액을 10,000원으로 설정
// 익명 구현체로 스레드 구현
Runnable test = new Runnable() {
@Override
public void run() {
boolean result = account.withdraw(6_000); // 6,000원 출금하기
System.out.println("스레드에서 result = " + result + ", balance = " + account.getBalance());
}
};
Thread th1 = new Thread(test);
Thread th2 = new Thread(test);
th1.start(); // 6,000원 출금
th2.start(); // 6,000원 출금
}
}
// 실행 결과
// 메소드 안에서 balance = 4000
// 스레드에서 result = true, balance = 4000
// 스레드에서 result = false, balance = 4000
// 동기화를 하지 않았을 때 실행 결과 1
// 메소드 안에서 balance = 4000
// 스레드에서 result = true, balance = 4000
// 메소드 안에서 balance = -2000
// 스레드에서 result = true, balance = -2000
// 동기화를 하지 않았을 때 실행 결과 2
// 메소드 안에서 balance = -2000
// 스레드에서 result = true, balance = -2000
// 메소드 안에서 balance = -2000
// 스레드에서 result = true, balance = -2000
2. Lock 객체를 이용한 동기화 처리 예제
public class ThreadTest18 {
private int balance; // 잔액이 저장될 변수
// Lock 객체 생성
// 되도록이면 private final로 생성할 것 => Lock을 관리하는 객체가 변경되는 것을 막기 위함
private final Lock lock = new ReentrantLock();
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
// 입금 처리를 하는 메소드
public void deposit(int money) {
// Lock 객체는 lock() 메소드로 lock을 설정하고,
// unlock() 메소드로 반드시 lock을 해제한다.
lock.lock(); // lock 설정 시작
balance += money;
lock.unlock(); // lock 해제
}
// 출금 처리를 하는 메소드 (성공 : true, 실패 : false를 반환)
public boolean withdraw(int money) {
lock.lock();
// try .. catch 블럭이 사용되는 부분에서
// unlock() 메소드를 호출할 때는 finally 영역에서 호출하는 것이 안전하다.
// return 값이 있는 메소드의 lock 해제를 위한 변수 선언
boolean chk = false;
try {
if (balance >= money) {
for (int i = 1; i <= 100_000_000; i++) {
} // 시간 지연용
balance -= money;
System.out.println("메소드 안에서 balance = " + balance);
chk = true;
} else {
chk = false;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return chk;
}
public static void main(String[] args) {
ThreadTest18 account = new ThreadTest18();
account.setBalance(10_000); // 잔액을 10,000원으로 설정
// 익명 구현체로 스레드 구현
Runnable test = new Runnable() {
@Override
public void run() {
boolean result = account.withdraw(6_000); // 6,000원 출금하기
System.out.println("스레드에서 result = " + result + ", balance = " + account.getBalance());
}
};
Thread th1 = new Thread(test);
Thread th2 = new Thread(test);
th1.start(); // 6,000원 출금
th2.start(); // 6,000원 출금
}
}
// 실행 결과
// 메소드 안에서 balance = 4000
// 스레드에서 result = true, balance = 4000
// 스레드에서 result = false, balance = 4000
- Vector, Hashtable 등과 같이 예전부터 존재하던 Collection 객체들은 내부에 동기화 처리가 되어 있으나
새로 구성된 Collection들은 동기화 처리가 되어 있지 않다.
따라서, 동기화가 필요한 프로그램에서 이런 Collection들을 사용하려면 동기화 처리를 한 후에 사용해야 한다.
🍂 Vector 이용하기
public class ThreadTest19 {
private static Vector<Integer> vector = new Vector<>();
// 동기화 처리가 되어 있지 않은 List
private static List<Integer> list1 = new ArrayList<>();
public static void main(String[] args) {
// 익명 구현체 이용
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10_000; i++) {
vector.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) {
}
}
System.out.println("vecor의 개수 : " + vector.size());
}
}
// 실행 결과
// vecor의 개수 : 50000
🍂 List 이용하기 - 1 (동기화 처리 X)
public class ThreadTest19 {
private static Vector<Integer> vector = new Vector<>();
// 동기화 처리가 되어 있지 않은 List
private static List<Integer> list1 = new ArrayList<>();
public static void main(String[] args) {
// 익명 구현체 이용
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10_000; i++) {
// vector.add(i);
list1.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) {
}
}
// System.out.println("vecor의 개수 : " + vector.size());
System.out.println("list1의 개수 : " + list1.size());
}
}
// 실행 결과
// Exception in thread "Thread-0" Exception in thread "Thread-2" Exception in thread "Thread-1" Exception in thread "Thread-4" java.lang.ArrayIndexOutOfBoundsException: 163
// at java.util.ArrayList.add(ArrayList.java:459)
// at kr.or.ddit.basic.ThreadTest19$1.run(ThreadTest19.java:28)
// at java.lang.Thread.run(Thread.java:745)
// java.lang.ArrayIndexOutOfBoundsException: 109
// at java.util.ArrayList.add(ArrayList.java:459)
// at kr.or.ddit.basic.ThreadTest19$1.run(ThreadTest19.java:28)
// at java.lang.Thread.run(Thread.java:745)
// java.lang.ArrayIndexOutOfBoundsException: 110
// at java.util.ArrayList.add(ArrayList.java:459)
// at kr.or.ddit.basic.ThreadTest19$1.run(ThreadTest19.java:28)
// at java.lang.Thread.run(Thread.java:745)
// java.lang.ArrayIndexOutOfBoundsException: 163
// at java.util.ArrayList.add(ArrayList.java:459)
// at kr.or.ddit.basic.ThreadTest19$1.run(ThreadTest19.java:28)
// at java.lang.Thread.run(Thread.java:745)
// list1의 개수 : 10103
- List는 동기화 처리가 되어 있지 않은데, 배열의 크기가 커지기 전에 제어를 빼앗겨 데이터를 저장하기 때문에 오류가 남
🍂 List 이용하기 - 2 (동기화 처리 O)
public class ThreadTest19 {
private static Vector<Integer> vector = new Vector<>();
// 동기화 처리가 되어 있지 않은 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 < 10_000; i++) {
// vector.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) {
}
}
// System.out.println("vecor의 개수 : " + vector.size());
// System.out.println("list1의 개수 : " + list1.size());
System.out.println("list2의 개수 : " + list2.size());
}
}
// 실행 결과
// list2의 개수 : 50000
'JAVA' 카테고리의 다른 글
[JAVA] 입출력(I/O) - 1 (0) | 2021.08.04 |
---|---|
[JAVA] 스레드의 동기화 - 2 (0) | 2021.08.04 |
[JAVA] 멀티 스레드 - 2 (0) | 2021.07.30 |
[JAVA] 데몬 스레드 (0) | 2021.07.30 |
[JAVA] 하나의 스레드와 여러 개의 스레드가 수행되는 시간 비교하기 (0) | 2021.07.30 |