2013-03-17 6 views
3

저는 스스로 학습하는 자바이고 멀티 스레딩을 이해하고 싶습니다. 나는 MyThread 클래스를 작성하여 조건이 참이 될 때까지 루프를 수행 한 다음 그에 대한 정보를 인쇄합니다. 여기에는 생성자에서 증가되고 작업이 완료 될 때 감소하는 정적 필드 left이 포함됩니다. 클래스 Flag은 시작 시점을 '알리는'것입니다. 불행히도 모든 스레드가 완료되면 left은 0이 아닙니다. 나는 몇 가지 방법을 만들었습니다. synchronized 그리고 더 나아졌지만 여전히 완벽하지는 않습니다. 내가 뭘 잘못하고 있니?자바에서 동기화 멀티 스레딩

import java.util.Random; 

class Flag 
{ 
    private boolean ok = false; 
    synchronized boolean ok() 
    { 
     return ok; 
    } 
    synchronized void setOk(boolean ok) 
    { 
     this.ok = ok; 
    } 
} 

class MyThread extends Thread 
{ 
    static int left = 0; 

    synchronized void add() 
    { 
     left++; 
    } 

    synchronized int remove() 
    { 
     return --left; 
    } 

    String name; 
    Flag flag; 
    Random rnd = new Random(); 
    public MyThread(String name, Flag flag) 
    { 
     this.name = name; 
     this.flag = flag; 
     add(); 
    } 

    public void run() 
    { 
     while(!flag.ok()); 

     double rnd; 
     long count = 0; 
     do{ 
      count++; 
      rnd = Math.random(); 
     } while(rnd > 0.00001); 
     print(count); 
    } 

    synchronized void print(long count) 
    { 
     System.out.printf("%s %10d left: %3d%n", name, count, remove()); 
    } 
} 

public class Test 
{ 
    public static void main(String... args) throws Exception 
    { 
     Flag flag = new Flag(); 
     for(int i=0; i<2000; i++){ 
      new MyThread(String.format("%04d",i),flag).start(); 
     } 

     flag.setOk(true); 
    } 
} 
+0

예상되는 출력은 무엇입니까? – Arpit

+0

마지막 줄에는 lke'1540 243385 left : 0'을보고 싶습니다. 그것은 (왼쪽에서) 쓰레드 이름, 카운터 (루프에 얼마나 많은 반복이 있었는지)와 0 (남은 쓰레드 수)입니다. 대신 '1540 243385 left : 5'와 같은 것을 얻습니다. –

답변

3

++left--left 원자 및 작업이 아니다. 여러 스레드가 실행하려고하면 두 사람이 동시에 left을 감소 시키려고 할 수 있습니다. 이 동작은 leftstatic (클래스) 변수 인 동안 인스턴스 수준에서 동기화하는 코드 (print은 인스턴스 메서드 임) 때문입니다. print() 인쇄 값이 순서 붙일 또한

주 (print 정적 동기화되지 않기 때문에 후 인쇄 "마지막"값은 print을 호출 마지막 스레드의를하지 않을 수도 있습니다).

첫 번째 변경 : 모든 스레드가 실행 된 후 left이 실제로 0인지 확인하십시오.

public static void main(String... args) throws Exception 
    { 
     java.util.List<Thread> threads = new java.util.LinkedList<Thread>(); 
     Flag flag = new Flag(); 

     for(int i=0; i<20; i++){ 
      Thread thread=new MyThread(String.format("%04d",i),flag); 
      threads.add(thread); 
      thread.start(); 
     } 

     flag.setOk(true);    
     for (Thread thread:threads) thread.join(); 
     System.out.println(MyThread.left); 
    } 

출력 :

0003  9527 left: 19 
0000  56748 left: 18 
0006  11428 left: 17 
0016  181845 left: 2 
0010  95287 left: 3 
0017  137911 left: 4 
0018  432172 left: 5 
0019  280280 left: 6 
0013  421170 left: 7 
0012  135830 left: 8 
0015  104375 left: 9 
0014  207409 left: 10 
0001  16157 left: 11 
0004  160136 left: 12 
0008  31673 left: 13 
0002  14589 left: 14 
0005  23692 left: 15 
0009  83419 left: 16 
0011  231135 left: 0 
0007  202603 left: 1 
0 

두 번째 변화 : 클래스에 동기화 (add, removeprint는 정적 메서드로 변환된다 name 더를하기 때문에 우리는 runprint에 대한 호출을 대체 할 수도 없다 정적 메서드 print에서 더 이상 볼 수 있음).

synchronized static void add() 
    { 
     left++; 
    } 

    synchronized static int remove() 
    { 
     return --left; 
    } 

    synchronized static void print(long count, String name) 
    { 
     System.out.printf("%s %10d left: %3d%n", name, count, remove()); 
    } 

    public void run() 
    { 
     ... 
     print(count,name); 
    } 

출력 : 정적 변수 동기화 객체에 의한

0012  10207 left: 19 
0006  121343 left: 18 
0000  16236 left: 17 
0008  81429 left: 16 
0010  20250 left: 15 
0002  14687 left: 14 
0015  11051 left: 13 
0017  23602 left: 12 
0019  19651 left: 11 
0005  180155 left: 10 
0014  126578 left: 9 
0003  41790 left: 8 
0016  98362 left: 7 
0001  96047 left: 6 
0004  334071 left: 5 
0009  46827 left: 4 
0018  102826 left: 3 
0013  71625 left: 2 
0007  267208 left: 1 
0011  188743 left: 0 
0 
3

예기치 않은 결과.

약간의 변경으로 코드를 테스트했습니다. MyThread 안에 정적 변수의 동기화가 필요하기 때문에 Synchronized 블록을 MyThread.class와 같이 사용했습니다.

void add() { 
    synchronized (MyThread.class) { 
     left++; 
    } 
} 

int remove() { 
    synchronized (MyThread.class) { 
     return --left; 
    } 
} 

예상되는 결과가 인쇄 될 때마다. 변수 AtomicInteger을 사용할 수도 있습니다. 원자 적으로 증가 된 카운터와 같은 응용 프로그램에서 사용됩니다.

1

스레드간에 정보를 교환 할 때 일반 변수를 사용하지 마십시오. 스레드를 자체 메모리가있는 프로세스로 처리하십시오. 정보 교환 방법을 결정하려면 다음 질문을하십시오.

  1. BlockingQueue는 저의 필요 사항을 충족합니까? BlockingQueue는 스레드간에 정보를 교환하는 가장 일반적인 미디어입니다.

  2. 만약 그렇다면 정말로 데이터 덩어리를 보내야합니까, 아니면 신호 수를 계산해야합니까?공유 카운터에는 세마포, 원자 숫자, CountdownLatch 등 여러 유형이 있습니다. 귀하의 경우 AtomicInteger가 충분합니다.

  3. 그렇지 않은 경우 다른 유형의 동기화 된 컬렉션이 작동 할 수 있습니까?

  4. 필요에 맞는 준비 동기화 된 데이터 교환 클래스가없는 경우 나만의 것을 만들 수 있습니다.