Search code examples
javamultithreadingparallel-processingthread-safetyjavac

Sharing a resource among Threads, different behavior in different java versions


This is the first time I've encountered something like below.

  • Multiple Threads (Inner classes implementing Runnable) sharing a Data Structure (instance variable of the upper class).

  • Working: took classes from Eclipse project's bin folder, ran on a Unix machine.

  • NOT WORKING: directly compiled the src on Unix machine and used those class files. Code compiles and then runs with no errors/warnings, but one thread is not able to access shared resource properly.

  • PROBLEM: One thread adds elements to the above common DS. Second thread does the following...

      while(true){
       if(myArrayList.size() > 0){
       //do stuff
       }
    

    }

  • The Log shows that the size is updated in Thread 1.

  • For some mystic reason, the workflow is not enetering if() ...

Same exact code runs perfectly if I directly paste the class files from Eclipse's bin folder.

I apologize if I missed anything obvious.

Code:

ArrayList<CSRequest> newCSRequests = new ArrayList<CSRequest>();

//Thread 1

private class ListeningSocketThread implements Runnable {
    ServerSocket listeningSocket;

    public void run() {
        try {
            LogUtil.log("Initiating...");
            init(); // creates socket
            processIncomongMessages();
            listeningSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void processIncomongMessages() throws IOException {     
        while (true) {
            try {
                processMessage(listeningSocket.accept());
            } catch (ClassNotFoundException e) {                    
                e.printStackTrace();
            }
        }
    }

    private void processMessage(Socket s) throws IOException, ClassNotFoundException {
        // read message
        ObjectInputStream ois = new ObjectInputStream(s.getInputStream());
        Object message = ois.readObject();
        LogUtil.log("adding...: before size: " + newCSRequests.size());
        synchronized (newCSRequests) {
                newCSRequests.add((CSRequest) message);
        }
        LogUtil.log("adding...: after size: " + newCSRequests.size()); // YES, THE SIZE IS UPDATED TO > 0
        //closing....
        
    }
    
........

}

//Thread 2
private class CSRequestResponder implements Runnable {

        public void run() {
            LogUtil.log("Initiating..."); // REACHES..
            while (true) {
//              LogUtil.log("inside while..."); // IF NOT COMMENTED, FLOODS THE CONSOLE WITH THIS MSG...
                if (newCSRequests.size() > 0) { // DOES NOT PASS
                    LogUtil.log("inside if size > 0..."); // NEVER REACHES....
                    try {
                        handleNewCSRequests();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
....
}

UPDATE

Solution was to add synchronized(myArrayList) before I check the size in the Thread 2.


Solution

  • To access a shared structure in a multi-threaded environment, you should use implicit or explicit locking to ensure safe publication and access among threads. Using the code above, it should look like this:

    while(true){
        synchronized (myArrayList) {
            if(myArrayList.size() > 0){
                //do stuff
            }
        }
        //sleep(...) // outside the lock!
    }
    

    Note: This pattern looks much like a producer-consumer and is better implemented using a queue. LinkedBlockingQueue is a good option for that and provides built-in concurrency control capabilities. It's a good structure for safe publishing of data among threads. Using a concurrent data structure lets you get rid of the synchronized block:

    Queue queue = new LinkedBlockingQueue(...)
    ...
    while(true){
            Data data = queue.take(); // this will wait until there's data in the queue
            doStuff(data);
    }