简体   繁体   中英

Unexpected behavior in concurrency (Java)

I have written the simplest source to reproduce the problem as follows

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();
        }
    }
}

What I expect: Unique ids in no order

What I have found: Multiple 1s (any other number is unique but not '1')

It looks like I have not understood some basic concepts of concurrency. Can you tell me what am I doing wrong?

The instance() method of your class IdGenerator is not thread-safe, so multiple instances of this class might be created, each with their own idSequence member variable, when multiple threads call the method concurrently.

You'll need to make the instance() method thread-safe, for example by making it synchronized .

What is the point of synchronizing synchronized increment? Next time, always, use static Type singlenote = new ... to create singletones.

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 );

}

The code becomes much simpler and buts eliminated. Might be you wanted to create instance in the getNext and that is why you sunchronized it? Do not create tons of useless getter methods.

This should do it. The check-then-act in the instance method is a race condition, which needs to be atomic.

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

Secondly, the idSequence is an atomicLong, so it doesn't need to be synchronized.

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();
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM