
thread.join() : 해당 thread가 끝날 때까지 대기하라는 의미를 가진다.
public class ThreadJoinTestB {
public static void main(String[] args) {
//스레드 생성
Thread t = new Thread() {
public void run() {
try {
//2초간 멈춤
Thread.sleep(2000);
System.out.println("MyThread 종료");
//3초간 멈춤
Thread.sleep(3000);
}catch (InterruptedException e) { }
}
};
//스레드 시작
t.start();
try {
//join()메소드 실행
t.join();
}catch (InterruptedException e) {
e.printStackTrace();
}
//main 메소드 종료 메세지
System.out.println("main() 종료");
}
}
→ 위 코드에서 join() 가 실행되면 t스레드가 종료될 때까지 main스레드가 기다린다.
여기서 기다린다는 의미는 main이 join을 만나면 NOT RUNNABLE 상태로 되는 것을 말한다.
→ join()은 수행한 스레드가 영향을 받는 것이지 t 가 영향을 받는 것이 아니다.
→ InterruptedException : RUN에서 NOT RUNNABLE로 갈 때 항상 같이 따라 온다.
class ThreadEx13_1 extends Thread{
public void run() {
for(int i = 0; i<300; i++) {
System.out.print("-");
}
}//run()
}
class ThreadEx13_2 extends Thread {
public void run() {
for(int i = 0; i<300; i++) {
System.out.print("l");
}
}//run()
}
public class ThreadEx13 {
static long startTime = 0;
public static void main(String[] args) {
ThreadEx13_1 th1 = new ThreadEx13_1();
ThreadEx13_2 th2 = new ThreadEx13_2();
th1.start();
th2.start();
startTime = System.currentTimeMillis();
try {
th1.join();
}catch (InterruptedException e) {}
try {
th2.join();
}catch (InterruptedException e) {}
// th1, th2 이 끝나고 수행하는데 언제 끝나는지 알기 위해 join을 쓴다.
System.out.print("소요시간 : " + (System.currentTimeMillis() -ThreadEx13.startTime));
}//main
}
→ 위 코드에서 try 안에 두 개의 스레드를 한번에 넣게 되면 하나가 예외가 걸리면 나머지 하나가 수행을 하지 않기 때문에 서로 영향을 주지 않기 위해서 예외 처리를 따로 해준다.
synchronized : 특정 스레드가 들어가면 문이 잠기고 다른 스레드가 들어오지 못하게 해준다.
class UnsafeAccount {
private int balance;
public int getBalance() {
return balance;
}
public void deposit(int val) {
balance += val;
}
//잔액 500원일때
public void withdraw(int val) {
if(balance >= val) {
try {
Thread.sleep(500);
}catch (InterruptedException e) {
}
balance -= val;
}
System.out.println("name : " + Thread.currentThread().getName() + ", balance = " + this.getBalance());
}
}
public class UnsafeAccountSample {
public static void main(String[] args) {
//공유 자원 : 객체는 하나인데 여러 스레드가 사용하는 것
final UnsafeAccount account = new UnsafeAccount();
account.deposit(5000);
Runnable withdrawRun = new Runnable() {
@Override
public void run() {
for(int i=0; i<10; i++) {
account.withdraw(500);
}
}
};
Thread t1 = new Thread(withdrawRun);
Thread t2 = new Thread(withdrawRun);
t1.start();
t2.start();
}
}

→ 위 코드는 synchronized를 사용하지 않았을 때 생기는 문제를 알아볼 수 있는 코드이다.
(동기화가 깨짐)
→ 위의 결과처럼 -500이라는 음수가 나오게 된다.
음수가 되는 경우의 수는 상당히 많은데 그 중에서 synchronized와 연관된 이유를 말하자면, 코드를 내려오면서 실행될 때 갑자기 t1과 t2의 순서가 교대가 될 수도 있다.
(이때, 교대가 된다는 게 한 줄 실행하고 난 다음이라고 생각하면 안된다. 위에서 보면 balance의 값을 알아보는 도중에 교대가 일어날 수 있기 때문이다.)
임계 영역(critcal section) : 공유자원에서 정보의 불일치가 일어날 수 있는 공간을 말한다.
(공유자원이란 1개의 객체를 여러 thread가 사용하는 것을 말한다.)
즉, 어떤 하나에 같이 접근할 때 생기는 문제를 말한다.
(정보의 불일치 = 동기화가 깨짐)
→ 위 UnsafeAccount 클래스에서 balance에 접근할 때, 임계 영역이 생기는 것이다.
예를 들어 화장실을 공유 자원이라고 할 때, 볼일을 보는데 다른 사람이 들어와서 같이 사용하는 문제가 생기는 것을 말한다.
이럴 때, 문제를 해결하기 위해 화장실 각 칸마다 문이 달려있고, 다른 사람이 들어오지 못하도록 한다.
이를 synchronized라고 생각하면 된다.
→ t1이 일을 다 하고 나가면 그때 t2가 들어올 수 있다.
⇒ MUTEX(Mutual Exclusion) - 상호배제 라고 한다.
→ 자바의 모든 객체는 lock을 가지고 태어난다. 이때, 락은 객체마다 하나씩만 가지고 태어난다.
→ 동기화된 메소드는 락을 가지고 상호배제를 하게 된다.
→ 락의 주체는 this이며, 락을 가졌다라는 말은 모니터를 취득했다라는 말과 동일한 말이다.
class SyncAccount {
private int balance;
public synchronized int getBalance() {
return balance;
}
public synchronized void deposit(int val) {
balance += val;
}
public synchronized void withdraw(int val) {
if(balance >= val) {
try {
Thread.sleep(500);
}catch (InterruptedException e) {
}
balance -= val;
}
System.out.println("name : " + Thread.currentThread().getName() + ", balance = " + this.getBalance());
}
}
public class SyncAccountSample {
public static void main(String[] args) {
final SyncAccount account = new SyncAccount();
account.deposit(5000);
Runnable withdrawRun = new Runnable() {
@Override
public void run() {
for(int i=0; i<10; i++) {
account.withdraw(500);
}
}
};
Thread t1 = new Thread(withdrawRun);
Thread t2 = new Thread(withdrawRun);
t1.start();
t2.start();
}
}
→ 위의 코드를 보면 첫번째 스레드가 account의 락을 가지게 되면, synchronized 메소드에 들어갈 수 있다.
synchronized 메소드에는 lock을 가진 스레드만 들어올 수 있으므로 두번째 스레드는 첫번째 스레드가 락을 돌려줄 때까지 기다리다가 돌려주면 그때 취득 후 들어갈 수 있다.
이런 방식으로 동기화가 깨지는 현상을 막을 수 있다.
독립성을 보장해준다

→ 위 그림을 보면 더 이해하기 쉬울 것이다.
하다가 걸린다고 해도 다음을 수행하는 것이 아니라 기다리다가 수행을 한다.
1) 블록을 설정하면 범위(구간) 설정이 가능하기 때문에, 병목현상을 최소화할 수 있다.
2) lock 검사 대상을 설정할 수 있다.(서로 다른 멤버변수에 대한 연산)
예를 들어서
methodA1( ), methodA2( ) 가 있고
methodB1( ), methodB2( ) 가 있을 때
각각에 synchronized 를 붙이면 methodA1( ) 가 일을 하면 나머지는 일을 하지 못하고 기다려야 한다.
즉, 시간이 오래 걸리므로 일의 효율이 떨어진다.
이를 해결하기 위해
methodA1( ), methodA2( ) 에 synchronized block 을 걸고,
methodB1( ), methodB2( ) 에 synchronized block 을 걸면

전체적으로 A, B 가 별도로 관리가 가능하다.
즉, methodA1( ) 이 수행되면 methodA2( )는 수행되지 않지만
methodB1( ), methodB2( ) 에는 영향을 주지 않는다.
class Some {
private Object key = new Object();
public void methodA() {
//lock 검사대상 객체 지정
Thread temp = Thread.currentThread();
synchronized (this) {
for (int i = 0; i < 100; i++) {
try {
System.out.println(temp.getName() +
" -> doing methodA"
);
Thread.sleep(10);
}catch (Exception e) { }
}
}
}
public void methodB() {
Thread temp = Thread.currentThread();
synchronized (key) {
for (int i = 0; i < 100; i++) {
try {
System.out.println(temp.getName() +
" -> doing methodB"
);
Thread.sleep(10);
}catch (Exception e) { }
}
}
}
}
public class ThreadTest{
public static void main(String[] args) {
final Some s = new Some();
Thread t1 = new Thread() {
@Override
public void run() {
s.methodA();
}
};
Thread t2 = new Thread() {
@Override
public void run() {
s.methodB();
}
};
t1.start();
t2.start();
}
}
class Human1 {
private String name;
private int age;
//같은 lock을 가지고 있어서 t1이 블로킹 되어 있어도 t2가 되지 않음
//둘에게 영향을 준다.(이름을 바꾸지 않으면 나이도 안된다. 나이를 바꾸지 않는 이상 이름도 안된다.)
public synchronized void setName(String name) {
try {
this.name = name;
System.out.println("name changed");
Thread.sleep(3000);
}catch (Exception e) {}
}
public synchronized void setAge(int age) {
try {
this.age = age;
System.out.println("age changed");
Thread.sleep(3000);
}catch (Exception e) {}
}
}
public class SynchronizedMethodCase {
public static void main(String[] args) {
final Human1 h = new Human1();
Thread t1 = new Thread() {
@Override
public void run() {
h.setName("춘식");
}
};
t1.setName("first");
Thread t2 = new Thread() {
@Override
public void run() {
h.setAge(10);
}
};
t2.setName("second");
t1.start();
t2.start();
}
}
class KeyA{
}
class KeyB{
}
class Human {
private String name;
private int age;
private KeyA keyA = new KeyA();
private KeyB keyB = new KeyB();
//이름과 나이가 서로 영향을 받지 않음
public void setName(String name) {
synchronized (keyA) {
try {
this.name = name;
System.out.println("name changed");
Thread.sleep(3000);
}catch (Exception e) {}
}
}
public void setAge(int age) {
synchronized (keyB) {
try {
this.age = age;
System.out.println("age changed");
Thread.sleep(3000);
}catch (Exception e) {}
}
}
}
public class SynchronizedBlockCase {
public static void main(String[] args) {
final Human h = new Human();
Thread t1 = new Thread() {
@Override
public void run() {
h.setName("춘식");
}
};
t1.setName("first");
Thread t2 = new Thread() {
@Override
public void run() {
h.setAge(10);
}
};
t2.setName("second");
t1.start();
t2.start();
}
}