Is this code a thread-safe one?

早过忘川 提交于 2019-12-07 03:06:29

问题


I want to process a flow of client requests. Each request has its special type. First I need to initialize some data for that type, and after this I can start processing the requests. When the client type comes for the first time, I just initialize the corresponding data. After this all the following requests of that type are processed using that data.

I need to do this in a thread-safe manner.

Here is a code I have written. Is it thread-safe?

public class Test {

    private static Map<Integer, Object> clientTypesInitiated = new ConcurrentHashMap<Integer, Object>();

    /* to process client request we need to 
    create corresponding client type data.
    on the first signal we create that data, 
    on the second - we process the request*/

    void onClientRequestReceived(int clientTypeIndex) {
        if (clientTypesInitiated.put(clientTypeIndex, "") == null) {
            //new client type index arrived, this type was never processed
            //process data for that client type and put it into the map of types
            Object clientTypeData = createClientTypeData(clientTypeIndex);
            clientTypesInitiated.put(clientTypeIndex, clientTypeData);
        } else {
            //already existing index - we already have results and we can use them
            processClientUsingClientTypeData(clientTypesInitiated.get(clientTypeIndex));
        }
    }

    Object createClientTypeData(int clientIndex) {return new Object();}

    void processClientUsingClientTypeData(Object clientTypeData) {}
}

From one hand, ConcurrentHashMap cannot produce map.put(A,B) == null two times for the same A. From the other hand, the assignment and comparisson operation is not thread-safe.

So is this code is ok? If not, how can I fix it?

UPDATE: I have accepted the answer of Martin Serrano because his code is thread-safe and it is not prone to double initialization issue. But I would like to note, that I have not found any isssues with my version, posted as an answer below, and my version does not require synchronization.


回答1:


This code is not thread safe because

//already existing index - we already have results and we can use them
processClientUsingClientTypeData(clientTypesInitiated.get(clientTypeIndex));

has a chance of getting the "" value you temporarily insert in the put check.

This code could be made threadsafe thusly:

public class Test {

    private static Map<Integer, Object> clientTypesInitiated = new ConcurrentHashMap<Integer, Object>();

    /* to process client request we need to 
       create corresponding client type data.
       on the first signal we create that data, 
       on the second - we process the request*/

void onClientRequestReceived(int clientTypeIndex) {
    Object clientTypeData = clientTypesInitiated.get(clientTypeIndex);
    if (clientTypeData == null) {
        synchronized (clientTypesInitiated) {
          clientTypeData = clientTypesInitiated.get(clientTypeIndex);
          if (clientTypeData == null) {
              //new client type index arrived, this type was never processed
              //process data for that client type and put it into the map of types
              clientTypeData = createClientTypeData(clientTypeIndex);
              clientTypesInitiated.put(clientTypeIndex, clientTypeData);
          }
        }
    }
    processClientUsingClientTypeData(clientTypeData);
}

Object createClientTypeData(int clientIndex) {return new Object();}

void processClientUsingClientTypeData(Object clientTypeData) {}

}




回答2:


No, I don't think it is still thread-safe.

You need to wrap put operation in a synchronized block.

As per javadoc for ConcurrentHashMap

Retrieval operations (including get) generally do not block, so may overlap with update operations (including put and remove).




回答3:


you should use putIfAbsent here, semantics of this operation is similar to CAS, and it is atomic for sure. And since it it atomic - then you don't have a problem with internal assignments and comparisons.

The current code is not thread-safe.




回答4:


ConcurrentHashMap is created not to use synchronized block, although using it as a lock for synchronized block is ok, but not efficient. If you have to use ConcurrentHashMap, a correct usage is as follows;

private static Map<Integer, Object> clientTypesInitiated = new ConcurrentHashMap<Integer, Object>();

void onClientRequestReceived(int clientTypeIndex) {
    Object clientTypeData = clientTypesInitiated.get(clientTypeIndex);
    if (clientTypeData == null) {
        Object newClientTypeData = createClientTypeData(clientTypeIndex);
        clientTypeData = clientTypesInitiated.putIfAbsent(clientTypeIndex, newClientTypeData);
        // if clientTypeData is null, then put successful, otherwise, use the old one
        if (clientTypeData == null) {
            clientTypeData = newClientTypeData;
        }
    }
    processClientUsingClientTypeData(clientTypeData);
}

To be honest, the above code might create clientTypeData multiple times (because of race condition) although it does not use synchronized block. Therefore, you should measure how expensive it is to create clientTypeData and if it is so expensive that it overshadow not using synchronized block, then you don't even need to use ConcurrentHashMap. Use normal HashMap and synchronized block.




回答5:


No it's not, if you need the whole method to be thread safe declare it as synchronized, or use a synchronized block to put your critical code.




回答6:


The current code is not thread safe since a context switch can occur between the if and the next put. you need to make clientTypesInitiated as type ConcurrentHashMap and not the interface Map. And then use the putIfAbsent method




回答7:


I think the code doesn't work as intended at all. Look:

void onClientRequestReceived(int clientTypeIndex) {
     // the line below ensures the existing data is lost even if was not null
    if (clientTypesInitiated.put(clientTypeIndex, "") == null) {
        Object clientTypeData = createClientTypeData(clientTypeIndex);
        clientTypesInitiated.put(clientTypeIndex, clientTypeData);
    } else {
        processClientUsingClientTypeData(clientTypesInitiated.get(clientTypeIndex));
    }
}

I guess you rather wanted a get method call in there.

I'd suggest such an approach:

void onClientRequestReceived(int clientTypeIndex) {
    Object clientTypeData;
    synchronized (clientTypesInitiated) {
        if ((clientTypeData = clientTypesInitiated.get(clientTypeIndex)) == null) {
            clientTypeData = createClientTypeData(clientTypeIndex);
            clientTypesInitiated.put(clientTypeIndex, clientTypeData);
        } 
    }
    processClientUsingClientTypeData(clientTypeData);
}



回答8:


putIfAbsent works great, but sometimes it will require initialization repeated for several times. Synchronized block with double-checked locking probably can solve this problem.

But here is another option based on AtomicIntegerArrays:

public class Test {

    private static Map<Integer, Object> clientTypesInitiated = new ConcurrentHashMap<Integer, Object>();

    private static final int CLIENT_TYPES_NUMBER = 10;
    private static AtomicIntegerArray clientTypesInitStarted = new AtomicIntegerArray(CLIENT_TYPES_NUMBER);
    private static AtomicIntegerArray clientTypesInitFinished = new AtomicIntegerArray(CLIENT_TYPES_NUMBER);

    void onClientRequestReceived(int clientTypeIndex) {

        int isInitStarted = clientTypesInitStarted.getAndIncrement(clientTypeIndex);
        int isInitFinished = clientTypesInitFinished.get(clientTypeIndex);

        if (isInitStarted == 0) {
            //new client type index arrived, this type was never processed
            //process data for that client type and put it into the map of types
            Object clientTypeData = createClientTypeData(clientTypeIndex);
            clientTypesInitiated.put(clientTypeIndex, clientTypeData);
            clientTypesInitFinished.getAndIncrement(clientTypeIndex);
        } else if (isInitFinished > 0) {
            //already existing index - we already have results and we can use them
            processClientUsingClientTypeData(clientTypesInitiated.get(clientTypeIndex));
        }
    }

    Object createClientTypeData(int clientIndex) {
        return new Object();
    }

    void processClientUsingClientTypeData(Object clientTypeData) {
    }
}

Any opinions?



来源:https://stackoverflow.com/questions/12483487/is-this-code-a-thread-safe-one

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!