使用java多线程计数,似乎无法正常工作

时间:2017-04-12 14:43:53

标签: java multithreading

目的:创建4个线程,在每个线程中,for循环将1加到共享的int变量10000次。最后共享变量应该是40000.I使用两种方法来做到这一点,结果每次运行代码时都不总是40000.我想知道什么是错的。

方法1 with synchronized:

  public class bbb {    
    int i=0;
    public void add() {
        synchronized(bbb.class){     
            i++;
            bbb.class.notifyAll();
            System.out.println(i);
            try {
                bbb.class.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        Thread t1=new Thread(new Runnable() {
            bbb b=new bbb();
            @Override
            public void run() {
                // TODO Auto-generated method stub
                int i=0;
                for (; i < 10000; i++) {
                    b.add();
                }
            }
        },"A");

        Thread t2=new Thread(t1,"B");
        Thread t3=new Thread(t1,"C");
        Thread t4=new Thread(t1,"D");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

带乐观锁的方法2:

static volatile int value=0;
static void optimisticLock(int expected,int update){
    if(expected==value){    
        value=update;
        System.out.println(Thread.currentThread().getName()+" "+update);
    }else{
        optimisticLock(value, value+1);
    }
}

public static void main(String[] args) {

    Thread t1=new Thread(new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            for(int i=0;i<10000;i++){
                optimisticLock(value, value+1);
            }
        }
    },"A");
    Thread t2=new Thread(t1,"B");
    Thread t3=new Thread(t1,"C");
    Thread t4=new Thread(t1,"D");
    t1.start();
    t2.start();
    t3.start();
    t4.start();
}

2 个答案:

答案 0 :(得分:1)

您实际上并不需要wait()notify()导致问题,而您似乎感到困惑。通常,wait()notify()用于生产者/消费者问题(请参阅here),在您的代码中,您只是尝试控制对{{1}的访问权限来自您不需要intwait 的各种主题的变量。

您可以使用上面的Method1参考下面符合您目标的代码(运行4个线程并使用同步打印值),我基本上已经取出了notifywait()来电。

notify()

此外,在您的代码中,使用public void add() { synchronized(Testing.class){ i++; System.out.println(i); } } 更有意义,因为您正在实例变量synchronized(this)(来自i对象)进行同步。

您可能需要注意的另一点是,对于您的任务(跨线程使用bbb变量),您只需使用AtomicInteger API,您就可以避免显式同步< / strong>在您的代码中。

另外,我建议您重构代码,如下所示,因为int对象传递给thread1非常混乱且难以理解(另外,确保Java命名约定,类名以大写字母开头):

thread2

答案 1 :(得分:0)

[[@ javaguy的回答是现场,但有一些东西丢失了所以我以为我会为后代做这件事。 ]

首先,您应该使用AtomicInteger来完成所有手动同步。你的线程只需要做:

AtomicInteger i = new AtomicInteger();
...
for (int i = 0; i < 10000; i++) {
   i.incrementAndGet();
}

由于AtomicInteger包装volatile int字段并且具有正确处理并发更新的逻辑,因此无需同步。

  

我用两种方法来做到这一点,每次运行代码时结果并不总是40000。我想知道什么是错的。

你永远不会达到40000的原因是你的手同步有竞争条件。在4个线程中的3个线程完成10000个线程后,最后一个线程运行将调用wait(),但没有人通知它。如果您只是删除等待并通知,那么您的代码始终适用于我:

 synchronized(bbb.class) {
      // no wait/notify here
      i++;
      System.out.println(i);
 }

其他几条评论:

  • 永远不要锁定这样的课程。这意味着其他线程可以调用notify并等待该类并影响其内部操作。如果您需要static锁定,请创建一个static锁定对象:    private static final Object lockObject = new Object();
  • 请注意在线程程序中执行System.out.println(...),因为System.outPrintStream,它本身是同步的。更好的模式是从i中拍摄synchronized的快照,然后将其打印到外面。
  • 一般情况下,出于性能原因,请尝试在synchronized块内部执行 no IO。
  • 类应以大写字母开头,因此应为BbbThreadedCounter
  • 正如@javaguy所提到的,您正在将Thread传递给另一个线程的构造函数。这可行,因为ThreadRunnable,但这是一个非常糟糕的模式,非常令人困惑。你应该这样做:

    Runnable runnable = new Runnable() { ... };
    Thread t1 = new Thread(runnable, "A");
    Thread t2 = new Thread(runnable, "B");
    ...
    
  • 最好创建一个类的实例并将其传递给每个线程,然后可以锁定它的instance

    synchronized (this) {
        i++;
    }
    ...
    final Bbb bbb = new Bbb();
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            for(int i = 0; i < 10000; i++){
               bbb.add();
            }
        }
     }, "A");
    

但是,AtomicInteger和该包中的其他并发类是你的朋友。