简体   繁体   English

Java多线程执行被阻止

[英]Java Multithreading Execution Blocked

for learning purpose i have tried to implements a queue data-structure + Consumer/producer chain that is thread-safe, for learning purpose too i have not used notify/wait mechanism : 出于学习目的,我试图实现一个线程安全的队列数据结构+消费者/生产者链,出于学习目的,我还没有使用通知/等待机制:

SyncQueue : SyncQueue:

package syncpc;

/**
 * Created by Administrator on 01/07/2009.
 */
public class SyncQueue {

   private int val = 0;
   private boolean set = false;


   boolean isSet() {
      return set;
   }

   synchronized  public void enqueue(int val) {
      this.val = val;
      set = true;
   }

   synchronized public int dequeue()  {
      set = false;
      return val;
   }
}

Consumer : 消费者:

package syncpc;

/**
 * Created by Administrator on 01/07/2009.
 */
public class Consumer implements Runnable {
    SyncQueue queue;

    public Consumer(SyncQueue queue, String name) {
        this.queue = queue;

        new Thread(this, name).start();
    }


    public void run() {

        while(true) {
            if(queue.isSet()) {
                System.out.println(queue.dequeue());
            }

        }
    }
}

Producer : 制片人:

package syncpc;

import java.util.Random;

/**
 * Created by Administrator on 01/07/2009.
 */
public class Producer implements Runnable {
    SyncQueue queue;

    public Producer(SyncQueue queue, String name) {

        this.queue = queue;
        new Thread(this, name).start();
    }

    public void run() {
        Random r = new Random();

        while(true) {
            if(!queue.isSet()) {
                    queue.enqueue(r.nextInt() % 100);
            }
        }
    }
}

Main : 主要:

import syncpcwn.*;

/**
 * Created by Administrator on 27/07/2015.
 */
public class Program {

    public static void main(String[] args) {
        SyncQueue queue  = new SyncQueue();

        new Producer(queue, "PROCUDER");
        new Consumer(queue, "CONSUMER");
    }


}

The problem here, is that if isSet method is not synchronized , i got an ouput like that : 这里的问题是,如果isSet方法未同步,我得到了这样的输出:

97,
55

and the program just continue running without outputting any value. 程序只是继续运行而不输出任何值。 while if isSet method is synchronized the program work correctly. 如果isSet方法同步,程序正常工作。

i don't understand why, there is no deadlock, isSet method just query the set instance variable without setting it, so there is no race condition. 我不明白为什么,没有死锁, isSet方法只是查询set实例变量而不设置它,所以没有竞争条件。

set needs to be volatile : set需要volatile

private boolean volatile set = false;

This ensures that all readers see the updated value when a write completes. 这可确保所有读者在写入完成时看到更新的值 Otherwise they will end up seeing the cached value. 否则他们最终会看到缓存的值。 This is discussed in more detail in this article on concurrency, and also provides examples of different patterns that use volatile . 这篇文章将在有关并发性的文章中进行更详细的讨论,并提供使用volatile的不同模式的示例。

Now the reason that your code works with synchronized is probably best explained with an example. 现在,您的代码使用synchronized的原因可能最好用一个示例来解释。 synchronized methods can be written as follows (ie, they are equivalent to the following representation): synchronized方法可以写成如下(即,它们等效于以下表示):

public class SyncQueue {

   private int val = 0;
   private boolean set = false;


   boolean isSet() {
      synchronized(this) {
          return set;
      }
   }

   public void enqueue(int val) {
      synchronized(this) {
          this.val = val;
          set = true;
      }
   }

   public int dequeue()  {
      synchronized(this) {
          set = false;
          return val;
      }
   }
}

Here, the instance is itself used as a lock. 这里,实例本身用作锁。 This means that only thread can hold that lock. 这意味着只有线程可以保存该锁。 What this means is that any thread will always get the updated value because only one thread could be writing the value, and a thread that wants to read set won't be able to execute isSet until the other thread releases the lock on this , at which point the value of set will have been updated. 这意味着,任何线程总是会得到更新的价值,因为只有一个线程可以写入值,而要读出的线程set将无法执行isSet 直到其他线程释放上的锁this ,在而此时的值set将被更新。

If you want to understand concurrency in Java properly you should really read Java: Concurrency In Practice (I think there's a free PDF floating around somewhere as well). 如果你想要正确理解Java中的并发性,你应该真正阅读Java:实践中的并发 (我认为在某个地方也有一个免费的PDF)。 I'm still going through this book because there are still many things that I do not understand or am wrong about. 我还在阅读这本书,因为还有许多我不理解或错误的事情。


As matt forsythe commented, you will run into issues when you have multiple consumers. 正如评论所暗示的那样 ,当你有多个消费者时,你会遇到问题。 This is because they could both check isSet() and find that there is a value to dequeue, which means that they will both attempt to dequeue that same value. 这是因为他们都可以检查isSet()并发现有一个值出队,这意味着他们都会尝试将相同的值出列。 It comes down to the fact that what you really want is for the "check and dequeue if set" operation to be effectively atomic, but it is not so the way you have coded it. 它归结为这样一个事实,即你真正想要的是“检查并且如果设置的那样出列”操作是有效的原子,但它不是你编码它的方式。 This is because the same thread that initially called isSet may not necessarily be the same thread that then calls dequeue . 这是因为最初调用isSet的相同线程可能不一定是随后调用dequeue的相同线程。 So the operation as a whole is not atomic which means that you would have to synchronize the entire operation. 因此整个操作不是原子操作这意味着您必须同步整个操作。

The problem you have is visibility (or rather, the lack of it). 你遇到的问题是可见性(或者更确切地说,缺乏可见性)。

Without any instructions to the contrary, the JVM will assume that the value assigned to a variable in one thread need not be visible to the other threads. 如果没有任何相反的指示,JVM将假定分配给一个线程中的变量的值不需要对其他线程可见。 It may be made visible sometimes later (when it's convenient to do so), or maybe not ever. 它有时可以在以后(当它方便的时候)可见,或者可能永远不可见。 The rules governing what should be made visible and when are defined by the Java Memory Model and they're summed up here . 管理什么应该是可见的规则以及何时由Java内存模型定义,并且它们在这里总结。 (They may be a bit dry and scary at first, but it's absolutely crucial to understand them.) (起初它们可能有点干燥和可怕,但理解它们绝对是至关重要的。)

So even though the producer sets set to true , the consumer will continue to see it as false. 因此,即使生产者settrue ,消费者仍将继续将其视为错误。 How can you publish a new value? 你怎么能发布新的价值?

  1. Mark the field as volatile . 将该字段标记为volatile This works well for primitive values like boolean , with references you have to be a bit more careful. 这适用于像boolean这样的原始值,引用时你必须要小心一些。
  2. synchronized provides not just mutual exclusion but also guarantees that any values set in it will be visible to anyone entering a synchronized block that uses the same object. synchronized提供互斥,还保证任何进入使用同一对象的synchronized块的人都可以看到设置的任何值。 (This is why everything works if you declare the isSet() method synchronized .) (这就是为什么一切都有效,如果你声明isSet()方法synchronized 。)
  3. Using a thread-safe library class, like the Atomic* classes of java.util.concurrent 使用线程安全的库类,如java.util.concurrentAtomic*

In your case volatile is probably the best solution because you're only updating a boolean , so atomicity of the update is guaranteed by default. 在你的情况下, volatile可能是最好的解决方案,因为你只是更新一个boolean ,因此默认情况下保证更新的原子性。


As @matt forsythe pointed out, there is also a TOCTTOU issue with your code too because your threads can be interrupted by another between isSet() and enqueue()/dequeue() . 正如@matt forsythe指出的那样,你的代码也存在TOCTTOU问题,因为你的线程可能会被另一个在isSet()enqueue()/dequeue()之间中断。

I assume that when we get stuck in threading issue, the first step was to make sure that both the threads are running well. 我假设当我们陷入线程问题时,第一步是确保两个线程都运行良好。 ( i know they will as there are no locks to create deadlock) (我知道他们会因为没有锁来创建死锁)

For that you could have added a printf statement in enqueue function as well. 为此你也可以在enqueue函数中添加一个printf语句。 That would make sure that enqueue and dequeue threads are running well. 这将确保排队和出队线程运行良好。

Then second step should have been that "set" is the shared resource, so is the value toggling well enough so that code can run in desired fashion. 然后第二步应该是“set”是共享资源,因此值足够切换以便代码可以以所需的方式运行。

I think if you could reason and put the logging well enough, you can realize the issues in problem. 我想如果你能够推理并充分利用日志记录,你就可以意识到问题所在。

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

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