3

我正在自学 Java,我想了解多线程。我创建MyThread了一个带有作业的类来执行循环,直到条件为真,然后打印有关它的信息。它包含静态字段left,该字段在构造函数中递增,并在作业完成时递减。类Flag是“告诉”线程何时开始。不幸的是,当所有线程都完成时left不等于零。我做了一些方法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);
    }
}
4

3 回答 3

3

由于静态变量上的对象同步导致意外结果。

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

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

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

每次它打印预期的结果。您也可以使用AtomicInteger变量。它用于诸如原子递增计数器之类的应用程序中。

于 2013-03-17T08:19:43.000 回答
2

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

另请注意,打印的值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

第二个变化:在类上同步(add,removeprint转换为静态方法;我们还必须替换对 in 的调用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
于 2013-03-17T08:11:48.187 回答
1

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

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

  2. 如果是这样,我真的需要发送数据块,还是只需要计算信号的数量?有几种类型的共享计数器:信号量、原子数、CountdownLatch 等。在您的情况下, AtomicInteger 看起来就足够了。

  3. 如果没有,其他现成类型的同步集合可以工作吗?

  4. 如果没有适合您需要的现成同步数据交换类,请创建您自己的。

于 2013-03-17T10:36:38.727 回答