본문 바로가기
JAVA

[JAVA] 스레드의 동기화 - 1

by happenstance 2021. 8. 3.

스레드의 동기화

한 번에 하나의 스레드만 객체에 접근할 수 있도록 객체에 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