멀티쓰레드에서는 여러 쓰레드에서 같은 프로세스 내의 자원을 공유해서 작업을 하기 때문에 자칫하면 각각의 쓰레드가 하나의 자원에 접근 제어를 하게 되어 서로의 작업에 영향을 주게 있음.

주의할 , 쓰레드가 교착상태(dead lock) 빠질 있음

 

교착상태(Dead lock)

하나의 이상의 쓰레드가 lock 상태에서 서로 lock 풀리기를 기다리는 상황

교착 상태에서는 작업이 진행되지 않고 쓰레드는 작업을 영원히 기다리는 상황이 .
그리하여 자바에서는 stop(), suspend(), resume() 같은 쓰레드의 상태를 변경하는 메서드들이 deprecated 되어짐.

만일 객체에 lock 상태에서 쓰레드가 종료되거나 정지 된다면, 객체를 사용하기 위해 대기중인 쓰레드들은 영원이 기다려야 되는 교착상태에 처하게 . 그리하여 쓰레드들 종료시킬 때에는 작업중인 객체의 lock 풀고, 작업 중에 변경했던 데이터를 작업이전의 상태로 돌려놓아야 .

 

 

package thread1;

 

public class MainClass {

 

       /**

        * @param args

        * @throws InterruptedException

        */

       public static void main(String[] args) throws InterruptedException {


              ManageThread mt = new ManageThread();

              mt.startThread();


              Thread.sleep(100);


              mt.stopThread();

       }

 

}

 

  
 

package thread1;

 

public class ManageThread {

 

       TestThread xxx = new TestThread("첫번째 Thread");

       TestThread yyy = new TestThread("두번째 Thread");

       Thread t1 = new Thread(xxx);

       Thread t2 = new Thread(yyy);

 

       public void startThread() {

              t1.start();

              t2.start();

       }

 

       public void stopThread() {

              xxx.stopRunning();

              yyy.stopRunning();

       }

 

}

 

  

 

package thread1;

 

public class TestThread implements Runnable {

      

       private boolean timeToStop = false;

       String name;

       int cnt = 0;

 

       TestThread(String name) {

              this.name = name;

       }

 

       @Override

       public void run() {

              while (timeToStop != true) {

                     System.out.println(name + ":" + cnt++);

              }

       }

 

       public void stopRunning() {

              timeToStop = true;

       }

      

}

 

이 예제를 수행해 보면 xxx yyy Thread가 각각 경쟁하면서 수행되는 것을 알 수 있다. 한 순간에는 하나의 Thread만이 수행된다. 따라서 적당한 시간을 분배받아서 Thread가 수행되다가 다른 Thread에게 우선권을 넘긴다. 그러면 우선권을 받을 Thread가 수행되는 것이다.


이런 방식을 통하여 여러 thread가 한꺼번에 수행될 수 있다. Java에서는 preemptive(선점형) 방식으로 Thread를 수행시킨다. 이 방식은 Thread에게 일률적으로 일정한 시간을 배당하는 기존의 방식(cooperative)과는 다르다. preemptive방식은 우선순위(priority)를 각 Thread에게 배정하고 우선순위가 높은 Thread CPU를 할당한다. 따라서 어떤 Thread가 높은 순위의 우선순위를 배정받은 후 그 순위가 바뀌지 않는다면 그 Thread를 계속 수행하게된다. 따라서 각 Thread의 우선순위를 변경해줌으로써 골고루 Thread가 배정되도록 해주어야 하는데 이 일은 Thread Scheduler가 담당한다(우리가 알 바 아니다).

위에서 살펴본 Thread.sleep()는 자신의 Thread를 잠깐 멈추고 자기보다 낮은 순위의 다른 Thread에게 우선권을 넘긴다.

여기서 참고로 알아둘 method가 있다. Thread.currentThread()라는 method인데 이를 호출하면 현재 수행되고 있는 Thread reference를 얻을 수 있다. 또한 Thread.currentThread().getName()하면 그 Thread의 이름도 알 수 있다. 참고하기 바란다.


만약 은행권 처럼 계좌금액에 동시에 + 하거나 - 하기도 전에 다른 스레드가와서 그 금액을 보고 돈을 판단하는 문제가 생기는 경우는 그럼 어떻게 할 것인가?

 

 

package thread2;

 

public class ThreadProblem {

 

       /**

        * @param args

        */

       public static void main(String[] args) {

              ManageThread mt = new ManageThread();

              mt.startThread();

 

       }

 

}

 

 
 

package thread2;

 

public class ManageThread {

 

       SharedData sd = new SharedData();

       TestThread xxx = new TestThread("첫번째 Thread", sd);

       TestThread yyy = new TestThread("두번째 Thread", sd);

       Thread t1 = new Thread(xxx);

       Thread t2 = new Thread(yyy);

 

       public void startThread() {

              t1.start();

              t2.start();

       }

 

}

 

  

 

package thread2;

 

public class TestThread implements Runnable {

 

       String name;

       SharedData sd;

 

       TestThread(String name, SharedData sd) {

              this.name = name;

              this.sd = sd;

       }

 

       public void run() {

              System.out.println(name + " push data : " + sd.push('a'));

              System.out.println(name + " push data : " + sd.push('b'));

              System.out.println(name + " push data : " + sd.push('c'));

              System.out.println(name + " pop data : " + sd.pop());

              System.out.println(name + " pop data : " + sd.pop());

              System.out.println(name + " pop data : " + sd.pop());

       }

 

}

 

 

 

package thread2;

 

public class SharedData {

 

       int stackPointer = 0;

       char[] stack = new char[100];

 

       public synchronized char push(char data) { // synchronized method

              stack[stackPointer] = data;

              doForALongJob();

              stackPointer = stackPointer + 1;

              return data;

       }

 

       public char pop() {

              synchronized (this) { // synchronized block

                     stackPointer = stackPointer - 1;

                     doForALongJob();

                     return stack[stackPointer];

              }

       }

 

       public void doForALongJob() {

              for (long i = 0; i < 5000000; i++) {

              }

       }

 

}

 

 

 

sychronized 2가지 방식을 보여주었다.  메소드에 선언하는거랑 메소드내부에서 묶는거랑...
위처럼 synchronized 키워드를 이용하면 간단히 동기화 할 수 있다...( 유닉스 스레드락보다 훨씬 간단하다 :) )
참고로 메소드에 거는것보다 블럭으로 만드는게 성능상으로 더 좋다. 메소드에 걸면..  더 많은 과정을 거치게 된다.

wait() and notify()

synchronized만 있으면 Thread의 모든 문제가 해결될까? 그랬으면 얼마나 좋을까? 하여간 해결해야 할 문제가 하나 더 있다.

위에서 살펴보았던 stack의 예제를 좀 더 생각해 보자. 만약 stack이 비어있는 상태에서 pop()가 수행되었다면 어떻게 될 것인가? 이 문제는 synchronized로는 도저히 해결할 수 없는 문제이다(data가 없다고 해서 pop() 호출을 그냥 없었던 일로 해서는 안 된다. pop()가 호출되었다면, stack에서 반드시 data를 하나 꺼내야 한다).

이 문제를 어떻게 해결하면 좋을까? 또 생각해보자(이 생각은 사실 wait(), notify()를 설명할 때 단골 손님으로 등장하는예이다).

여러분이 서울역에서 잠실까지 가려고 택시를 탔다고 생각하자. 잠실까지 가는 동안 5분마다 운전기사에게 "여기 잠실이에요?"라고 물을 사람은 아마 아무도 없을 것이다. 대부분 타자마자 "잠실 갑시다."라고 말한 후 잠을 자거나 딴 생각할 것이다. 잠실에 도착하면 기사가 알아서 "잠실입니다."라고 말해줄 것이다. 이건 매우 합리적인 일이다.

, 이 예를 stack에 적용해보자. 어떤 Thread stack에서 pop()을 했는데 stack이 비어 있었다. 그렇다면 하는 수 없이 이 Thread block상태(대기상태)가 된다. , stack data push될 때까지 기다리게 된다. 이것은 잠실에 도착할 때까지 손님이 잠자는 것과 같은 이치이다.

  후에 stack data가 들어오게 되면 block되어 있던 Thread에게 data가 왔다는 신호를 해준다. 이것은 운전기사가 잠자고 있는 손님에게 잠실에 도착했다는 신호를 보내는 것과 같다. 그러면 대기중인 Thread stack에서 data pop할 수 있는 상태가 되는 것이다.

Java Thread에서도 지금 설명한 방식이 사용된다. 그때 사용되는 method wait(), notify()이다.

 

출처: http://sinily.tistory.com/16

 

 

 

 

 

 

'Java > Threads' 카테고리의 다른 글

Controlling Thread Status  (0) 2013.02.07
Thread & Runnable  (0) 2013.02.07
Posted by Steven J.S Min
,