并发中出现意外行为(Java)

时间:2014-07-08 07:11:56

标签: java concurrency

我写了最简单的资源来重现问题如下

package concurrency.test;

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

public class Main
{
    public static void main(String[] args)
    {
        new Main().start();
    }

    private void start()
    {
        for (int i = 0; i < 20; i++)
        {
            Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new SequencePrinter(), 1, 1, TimeUnit.SECONDS);
        }
    }

    private class SequencePrinter implements Runnable
    {
        @Override
        public void run()
        {
            System.out.println( IdGenerator.instance().nextId());
        }
    }

    private static class IdGenerator
    {
        private static IdGenerator instance;
        private final AtomicLong idSequence = new AtomicLong( 0 );

        private IdGenerator()
        {
        }

        public static IdGenerator instance ()
        {
            if ( instance == null )
            {
                instance = new IdGenerator();
            }

            return instance;
        }

        synchronized public long nextId ()
        {
            return idSequence.incrementAndGet();
        }
    }
}

我的期望:无序的唯一ID

我发现:多个1(任何其他数字是唯一的但不是&#39; 1&#39;)

看起来我还没有理解并发的一些基本概念。你能告诉我我做错了什么吗?

3 个答案:

答案 0 :(得分:4)

instance()的{​​{1}}方法不是线程安全的,因此当多个线程调用时,可能会创建此类的多个实例,每个实例都有自己的IdGenerator成员变量该方法同时进行。

您需要使idSequence方法具有线程安全性,例如将其设为instance()

答案 1 :(得分:0)

同步增量的同步点是什么?下次,始终使用static Type singlenote = new ...创建单例。

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

public class SequencePrinter implements Runnable {

    public void run() {
        System.out.println("produces " + idSequence.incrementAndGet());
    }

    public static void main(String[] args) {

        for (int i = 0; i < 200; i++)
            Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(
                new SequencePrinter(), 1, 10, TimeUnit.SECONDS);
    }

    private static final AtomicLong idSequence = new AtomicLong( 0 );

}

代码变得简单得多,但却被淘汰了。您可能想在getNext中创建实例,这就是为什么要对它进行同步?不要制造大量无用的吸气剂方法。

答案 2 :(得分:0)

这应该做到。 instance方法中的check-then-act是一种竞争条件,它必须是原子的。

if (instance == null) {
    instance = new IdGenerator();                
}

第二,idSequence是atomicLong,因此不需要同步。

private static class IdGenerator
    {
        private static IdGenerator instance;
        private final AtomicLong idSequence = new AtomicLong( 0 );
        private static Object lock = new Object();

        private IdGenerator()
        {
        }

        public static IdGenerator instance ()
        {
            synchronized (lock) {
                if (instance == null) {
                    instance = new IdGenerator();
                }
            }
            return instance;
        }

        public long nextId ()
        {
            return idSequence.incrementAndGet();
        }
    }
}