2013-03-17 79 views
3

我是自学Java,我想了解多线程。我创建了一个带有作业的MyThread类,以执行循环,直到条件成立为止,然后打印关于它的信息。它包含静态字段left,它在构造函数中递增并在作业完成时递减。类Flag正在'告诉'线程什么时候开始。 不幸的是,当所有线程完成时left不等于零。我做了一些方法​​它变得更好,但它仍然不完美。 我在做什么错?在java中同步多线程

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 2013-03-17 08:08:50

+0

在最后一行,我想看到lke'1540 243385 left:0'。它是(从左):线程名称,计数器(循环中有多少次迭代)和0(剩下的线程数)。相反,我得到了'1540 243385 left:5'之类的东西。 – 2013-03-17 08:11:24

回答

3

++left--left不是原子操作。当多个线程正在尝试执行时,可能会出现两个尝试同时递减left的情况。此行为是由于您的代码在实例级同步(print是实例方法),而leftstatic(类)变量。

还要注意的是,在print()打印的值是无序的(因为print也不是一成不变的同步,然后打印在“最后”的值可能不是与调用print最后一个线程的)。

第一次更改:在执行所有线程后检查left是否确实为零。

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 

第二个变化:在类同步(addremoveprint转化为静态方法,我们也来代替调用printrun因为name没有从静态方法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

由于静态可变对象同步意外结果。

我已经测试了你的代码,几乎没有变化。我用Synchronized块与MyThread.class作为因为我需要MyThread内的静态变量的同步。

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

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

每次打印预期结果。您也可以使用AtomicInteger变量。它用于原子增量式计数器等应用程序。

1

在线程之间交换信息时不要使用普通变量。将线程视为具有自己内存的进程。要决定如何交换信息,请提出以下问题:

  1. 请问BlockingQueue是否符合我的需求? BlockingQueue是在线程之间交换信息的最常见的媒体。

  2. 如果是这样,我真的需要发送数据块,或者我只需要计数信号的数量?有几种类型的共享计数器:信号量,原子数,CountdownLatch和其他。 就你而言,AtomicInteger看起来不够。

  3. 如果不是,其他就绪类型的同步集合可以工作吗?

  4. 如果没有准备好的同步数据交换类可以满足您的需求,请自行创建。