简体   繁体   English

线程间共享资源,不同java版本的行为不同

[英]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).多个线程(实现 Runnable 的内部类)共享一个数据结构(上层类的实例变量)。

  • Working: took classes from Eclipse project's bin folder, ran on a Unix machine.工作:从 Eclipse 项目的 bin 文件夹中获取类,在 Unix 机器上运行。

  • NOT WORKING: directly compiled the src on Unix machine and used those class files.不工作:直接在 Unix 机器上编译 src 并使用这些类文件。 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.问题:一个线程向上述常见 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.日志显示大小在线程 1 中更新。

  • For some mystic reason, the workflow is not enetering if() ...由于一些神秘的原因,工作流程没有进入 if() ...

Same exact code runs perfectly if I directly paste the class files from Eclipse's bin folder.如果我直接粘贴 Eclipse 的 bin 文件夹中的类文件,则完全相同的代码可以完美运行。

I apologize if I missed anything obvious.如果我错过了任何明显的东西,我深表歉意。

Code:代码:

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

//Thread 1 //线程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.解决方案是在检查线程 2 中的大小之前添加 synchronized(myArrayList)。

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. LinkedBlockingQueue是一个很好的选择,它提供了内置的并发控制功能。 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);
}

Every time you modify a given shared variable inside a parallel region (a region with multiple threads running in parallel) you must ensure mutual exclusion .每次修改parallel region (具有多个并行运行的线程的区域)内的给定shared variable您必须确保mutual exclusion You can guarantee mutual exclusion in Java by using synchronized or locks , normally you use locks when you want a finer grain synchronization.您可以通过使用synchronizedlocks来保证 Java 中的mutual exclusion ,通常在需要更细粒度的同步时使用锁。

If the program only performance reads on a given shared variable there is no need for synchronized/lock the accesses to this variable.如果程序只对给定的共享变量进行性能读取,则不需要同步/锁定对该变量的访问。

Since you are new in this subject I recommend you this tutorial由于您是该主题的新手,因此我向您推荐本教程

Check the return of检查返回

newCSRequests.add((CSRequest) message);

I am guessing its possible that it didn't get added for some reason.我猜它可能由于某种原因没有被添加。 If it was a HashSet or similar, it could have been because the hashcode for multiple objects return the same value.如果它是 HashSet 或类似的,可能是因为多个对象的哈希码返回相同的值。 What is the equals implementation of the message object?消息对象的 equals 实现是什么?

You could also use你也可以使用

List list = Collections.synchronizedList(new ArrayList(...));

to ensure the arraylist is always synchronised correctly.以确保 arraylist 始终正确同步。

HTH HTH

If I got this right.. There are at least 2 threads that work with the same, shared, datastructure.如果我做对了……至少有 2 个线程使用相同的共享数据结构。 The array you mentioned.. One thread adds values to the array and the second thread "does stuff" if the size of the array > 0. There is a chance that the thread scheduler ran the second thread (that checks if the collection is > 0), before the first thread got a chance to run and add a value.你提到的数组..一个线程向数组添加值,如果数组的大小> 0,第二个线程“做事”。线程调度程序有可能运行第二个线程(检查集合是否> 0),在第一个线程有机会运行并添加值之前。 Running the classes from bin or recompiling them has nothing to do.从 bin 运行类或重新编译它们无关。 If you were to run the application over again from the bin directory, you might seen the issue again.如果您要从 bin 目录再次运行该应用程序,您可能会再次看到该问题。 How many times did you ran the app?你运行了多少次应用程序? It might not reproduce consistently but at one point you might see the issue again.它可能不会始终如一地重现,但在某一时刻您可能会再次看到该问题。

You could access the datastruce in a serial fashion, allowing only one thread at a time to access the array.您可以以串行方式访问数据结构,一次只允许一个线程访问数组。 Still that does not guarantee that the first thread will run and only then the second one will check if the size > 0.仍然不能保证第一个线程会运行,只有第二个线程才会检查大小是否> 0。

Depending on what you need to accomplish, there might be better / other ways to achieve that.根据您需要完成的工作,可能有更好的/其他方法来实现这一目标。 Not necessarily using a array to coordinate the threads..不一定使用数组来协调线程..

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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