IT_Programming/Java

쓰레드 (Thread) 사용하기

JJun ™ 2007. 1. 30. 11:11

1. 쓰레드(Thread) 사용하기

 

[ MyRun.java ]

=================================================================

 

public class MyRun implements Runnable
{
  public void run()
  {
   show();
  }

  public void show()
  {
    for(int i=0; i<100; i++)
     System.out.println("S");
  }
}

 

=================================================================

 

쓰레드를 만드는 방법에는 두가지가 있습니다.

 

1. java.lang.Runnable을 구현하는 방법

2. java.lang.Thread를 상속하는 방법

 

하지만, 여기서 1번 방법을 쓴 이유는 자바에서는 다중상속을 허용하지 않기 때문입니다.

이 Runnable 인터페이스에는 추상 메서드 run이 있으므로 Runnable을 구현하는 클래스에서

run 메서드를 구현해야 합니다.

 

[ MyThread.java ]

==================================================================

 

public class MyThread extends Thread
{
 public void run()
 {
  for(int i=0; i<100; i++)
  {
    System.out.print("T");
  }
 }
}

==================================================================

Thread 클래스를 상속하여 쓰레드를 만듭니다.

Thread 클래스는 Runnable 인터페이스를 구현하는 클래스이므로

run 메서드를 구현해야 합니다.

 

[MyRunsMain.java]

==================================================================

 

public class MyRunsMain
{
 public static void main(String[] args)
 {
  MyRuns mr1 = new MyRuns();

  Thread t1 = new Thread(mr1,"a");
  Thread t2 = new Thread(mr1,"b");
  Thread t3 = new Thread(mr1,"c");

  t1.start();
  t2.start();
  t3.start();
 }
}

 

==================================================================

Runnable을 구현한 MyRun의 객체를 생성하고, MyRun은 run메서드를 구현했습니다.

메인 메서드는 메인 쓰레드에서 실행이 됩니다. 메인 쓰레드 이외의 새로운 쓰레드를 만들려면

Runnable을 구현한 객체의 레퍼런스를 대입하여 Thread 클래스의 객체를 생성합니다.

 

MyThread는 Thread를 상속하였으므로 자신이 쓰레드입니다.

쓰레드의 start 메서드는 JVM이 쓰레드를 사용할 수 있는 상태를 만들어줍니다.

JVM은 run()을 자동으로 호출하여 준비상태의 쓰레드를 실행합니다.

즉, 개발자가 run메서드를 구현하고 쓰레드의 start()를 호출하면 JVM은 run()을 호출합니다.

메인 쓰레드, 쓰레드, t1, t2가 쓰레드 스케줄러에 의해 실행되므로 M, T, S가 섞여서 출력됩니다.

 

 

 

2. 쓰레드의 기본 메서드와 특징 확인하기

 

쓰레드를 메모리의 스택영역으로 생각하면 보다 이해하기 쉽습니다.

쓰레드는 기본적으로 메인 쓰레드(메인 메서드가 실행되는 쓰레드)에서 실행이 됩니다.

가령, 여러개의 웹 브라우저를 띄워놓으면 여러작업을 한꺼번에 진행할 수 있습니다.

여기에는 웹 브라우저를 쓰레드로 바꾸어 생각하면 멀티 쓰레드가 됩니다.

즉, 여러개의 쓰레드를 이용하여 여러작업을 한꺼번에 처리 할 수 있는 것입니다.

 

[MyRuns.java]

==================================================================

 

public class MyRuns implements Runnable
{
  public void run()
  {
   show();
  }

 

  public void show()
  {
    for(int i=0;i<100; i++)
    {
      if(((Thread.currentThread()).getName()).equals("a"))
      {
         System.out.print("[A"+i+"]");
      }
      else if(((Thread.currentThread()).getName()).equals("b"))
      {
         System.out.print("[B"+i+"]");
      }
      else if(((Thread.currentThread()).getName()).equals("c"))
      {
         System.out.print("[C"+i+"]");
      }
    }
  }
}

==================================================================

Thread.currentThread()는 현재 실행되고 있는 쓰레드의 타입를 Thread 타입으로 리턴합니다.

getName()은 쓰레드의 이름을 리턴해주며, 현재 실행되고 있는 쓰레드의 이름이 a이면,

"[A+숫자]"로 출력이 됩니다.

 

 

[MyRunsMain.java]

==================================================================

 

public class MyRunsMain
{
 public static void main(String[] args)
 {
  MyRuns mr1 = new MyRuns();

  Thread t1 = new Thread(mr1,"a");
  Thread t2 = new Thread(mr1,"b");
  Thread t3 = new Thread(mr1,"c");

  t1.start();
  t2.start();
  t3.start();
 }
}

 

==================================================================

쓰레드를 생성하고, 첫번째 아규먼트로는 run()를 구현한 객체의 레퍼런스를,

두번째 아규먼트로는 쓰레드의 이름을 대입합니다. 쓰레드의 이름을 넣지 않으면

자동으로 'Thread-숫자'가 됩니다.

Thread를 쓰레드 스케줄러에게 맡깁니다. 스케줄러가 t1, t2, t3에서 run()을 실행합니다.

쓰레드 스케줄러에 의해서 하나의 쓰레드가 출력할 동안 나머지 쓰레드들은 정지하기 때문에

출력 결과는 뒤죽박죽이 될 것입니다.

( 쓰레드는 자원을 공유하는 특징이 있어 멤버필드와 스태틱 필드를 공유합니다.

  하지만 여기서는 블록변수만 사용하기 때문에 자원공유 문제가 발생하지 않습니다. )

 

 

 

3. 쓰레드(Thread)와 자원의 공유

 

[MemberPrint.java]

===================================================================

 

public class MemberPrint implements Runnable
{
 private int i=0;

 public void run()
 {
  show();
 }

 public void show()
 {
  for(int i=0;i<100; i++)
  {
   if(((Thread.currentThread()).getName()).equals("a"))
   {
    System.out.print("[A"+i+"]");
   }
   else if(((Thread.currentThread()).getName()).equals("b"))
   {
    System.out.print("[B"+i+"]");
   }
   else if(((Thread.currentThread()).getName()).equals("c"))
   {
    System.out.print("[C"+i+"]");
   }
  }
 }
}

 

===================================================================

쓰레드는 다음과 같은 경우 자원을 공유합니다.

 

1. 여러 개의 쓰레드에서 한 객체의 멤버 필드를 사용하려고 하는 경우

2. 여러 개의 쓰레드에서 동일 타입으로 생성된 객체의 스태틱 필드를 사용하려는 경우

 

먼저, 전자의 경우를 살펴보겠습니다.

우선 멤버 필드 i를 선언합니다. 

 

[MemberPrintMain.java]

===================================================================

 

public class MemberPrintMain
{
 public static void main(String[] args)
 {
   MemberPrint mr1 = new MemberPrint();

  Thread t1 = new Thread(mr1,"a");
  Thread t2 = new Thread(mr1,"b");
  Thread t3 = new Thread(mr1,"c");

  t1.start();
  t2.start();
  t3.start();
 }
}

 

===================================================================

멤버필드 i를 갖는 MemberPrint의 객체를 생성합니다.

MemberPrint 객체의 레퍼런스를 대입하면서 쓰레드 t1, t2, t3를 생성합니다.

( 같은 자원을 여러 쓰레드가 사용하면 자원 공유 문제가 발생합니다.

  예를들어 t1쓰레드에서 a를 2까지 사용했다면 t2에서는 2부터 시작합니다.

  그리고 이와 같은 방식으로 t3는 3부터 시작하게 됩니다. )

 

쓰레드를 구별하기 위해서 이름을 대입하고 쓰레드의 이름을 얻을 때는 getName()를 이용합니다.

자원의 공유는 한 객체의 멤버 필드를 여러 쓰레드에서 사용할 때 발생합니다.

예를들어, 80만원이 든 내 계좌(멤버필드)에서 여러가지 방법(쓰레드)으로 돈을 지불 할 수 있습니다.

(자원의 공유). 인터넷 쇼핑을 하다가 인터넷 뱅킹으로 40만원을 지불하고 나머지는 직불카드로 찾아

쓸 수 있습니다. 이처럼 자원공유는 매우 편리합니다.

하지만 두 방법으로 동시에 계좌로 접근하여 인터넷 뱅킹으로 80만원을 지불하면서 직불 카드로

80만원을 찾으려 할 때 이런 문제를 막을 수 있는 조치(동기화)가 없다면 160만원이 인출되는 사고가

발생할 수 있습니다.

 

 

 

4. 쓰레드(Thread)와 자원 공유 - 동기화

 

[staticPrint.java]

=======================================================================

 

public class staticPrint implements Runnable
{
 private static int i=0;

 public void run()
 {
  show();
 }

 public void show()
 {
  for(int i=0;i<100; i++)
  {
   if(((Thread.currentThread()).getName()).equals("a"))
   {
    System.out.print("[A"+i+"]");
   }
   else if(((Thread.currentThread()).getName()).equals("b"))
   {
    System.out.print("[B"+i+"]");
   }
   else if(((Thread.currentThread()).getName()).equals("c"))
   {
    System.out.print("[C"+i+"]");
   }
  }
 }
}

 

=======================================================================

동일한 타입의 여러 객체가 스태틱 필드를 공유하는 경우를 알아보자.

우선 스태틱 필드 i를 선언합니다.

 

[staticPrintMain.java]

=======================================================================

 

public class staticPrintMain
{
 public static void main(String[] args)
 {
  staticPrint mr1 = new staticPrint();
  staticPrint mr2 = new staticPrint();
  staticPrint mr3 = new staticPrint();
  

  Thread t1 = new Thread(mr1,"a");
  Thread t2 = new Thread(mr1,"b");
  Thread t3 = new Thread(mr1,"c");

  

  t1.start();
  t2.start();
  t3.start();
 }
}

 

=======================================================================

 

[staticLockPrint.java (수정)]

=======================================================================

 

public class staticLockPrint implements Runnable
{
 private static int i=0;

 public void run()
 {
  show();
 }

 public void show()
 {
  synchronized(staticLockPrint.class)
  {
   for(int i=0;i<100; i++)
   {
    if(((Thread.currentThread()).getName()).equals("a"))
    {
     System.out.print("[A"+i+"]");
    }
    else if(((Thread.currentThread()).getName()).equals("b"))
    {
     System.out.print("[B"+i+"]");
    }
    else if(((Thread.currentThread()).getName()).equals("c"))
    {
     System.out.print("[C"+i+"]");
    }
   }
  }
 }
}

=======================================================================

동일한 타입의 staticLockPrint의 객체를 세 개 생성합니다.

(타입이 같은 여러 객체는 스태틱 영역에 있는 스태틱 필드 i를 공유할 수 있습니다.)

synchronized(staticLockPrint.class) {...}는 staticLockPrint의 스태틱 필드를 동기화합니다.

이로써 다른 쓰레드는 동기화된 구역을 동시에 공유할 수 없게 됩니다.

(스태틱 필드에 대한 동기화는 synchronized(클래스 이름.class) {...} 의 형식을 사용합니다.

멤버 필드의 경우, 메서드에 synchronized 키워드를 붙이는 동기화 방법과

synchronized(this) {...}를 사용하는 동기화 블록 방법이 있습니다. )

 

[staticPrintMain.java (수정)]

=======================================================================

 

public class staticPrintMain
{
 public static void main(String[] args)
 {
  staticLockPrint mr1 = new staticLockPrint();
  staticLockPrint mr2 = new staticLockPrint();
  staticLockPrint mr3 = new staticLockPrint();
  

  Thread t1 = new Thread(mr1,"a");
  Thread t2 = new Thread(mr1,"b");
  Thread t3 = new Thread(mr1,"c");

  

  t1.start();
  t2.start();
  t3.start();
 }
}

 

=======================================================================

 

 

[MemberLockPrint.java]

=======================================================================

 

public class MemberLockPrint implements Runnable
{
 private int i=0;

 public void run()
 {
  show();
 }

 public synchronized void show()
 {
  for(int i=0;i<100; i++)
  {
   if(((Thread.currentThread()).getName()).equals("a"))
   {
    System.out.print("[A"+i+"]");
   }
   else if(((Thread.currentThread()).getName()).equals("b"))
   {
    System.out.print("[B"+i+"]");
   }
   else if(((Thread.currentThread()).getName()).equals("c"))
   {
    System.out.print("[C"+i+"]");
   }
  }
 }
/*
 public void show()
 {
  synchronized(this)
  {
   for(int i=0;i<100; i++)
   {
    if(((Thread.currentThread()).getName()).equals("a"))
    {
     System.out.print("[A"+i+"]");
    }
    else if(((Thread.currentThread()).getName()).equals("b"))
    {
     System.out.print("[B"+i+"]");
    }
    else if(((Thread.currentThread()).getName()).equals("c"))
    {
     System.out.print("[C"+i+"]");
    }
   }
  }
 }
*/

}

 

=======================================================================

멤버 필드에 대한 동기화는 메서드에 synchronized 키워드를 붙이거나

synchronized(this){...}를 사용합니다.

 

메서드를 이용한 동기화는 public synchronized void show()와 같이

메서드에 synchronized 키워드를 붙입니다.

 

메서드가 아닐 경우에는 synchronized(this){...}를 사용합니다.

 

 

[MemberLockPrintMain.java]

=======================================================================

 

public class MemberLockPrintMain
{
 public static void main(String[] args)
 {
  MemberLockPrint mr1 = new MemberLockPrint();

  Thread t1 = new Thread(mr1,"a");
  Thread t2 = new Thread(mr1,"b");
  Thread t3 = new Thread(mr1,"c");

  t1.start();
  t2.start();
  t3.start();
 }
}

 

=======================================================================

소스 (MemberPrintMain.java)와 큰 차이가 없습니다.

MyRunsMain.java
0.0MB
MyRuns.java
0.0MB
MemberLockPrintMain.java
0.0MB
MyRunMain.java
0.0MB
MemberLockPrint.java
0.0MB
MemberPrint.java
0.0MB
staticLockPrint.java
0.0MB
MyThread.java
0.0MB
staticPrintMain.java
0.0MB
MemberPrintMain.java
0.0MB
MyRun.java
0.0MB
staticPrint.java
0.0MB

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

String과 StringBuffer의 성능에 대해서...  (0) 2007.02.05
내부 클래스  (0) 2007.02.05
JVM의 메모리구조  (0) 2007.01.29
FileWriter & FileWriter  (0) 2007.01.29
파일 클래스  (0) 2007.01.29