Java多线程执行被阻止

Java多线程执行被阻止,java,multithreading,deadlock,race-condition,Java,Multithreading,Deadlock,Race Condition,出于学习目的,我尝试实现线程安全的队列数据结构+消费者/生产者链,出于学习目的,我也没有使用通知/等待机制: 同步队列: package syncpc; /** * Created by Administrator on 01/07/2009. */ public class SyncQueue { private int val = 0; private boolean set = false; boolean isSet() { return set

出于学习目的,我尝试实现线程安全的队列数据结构+消费者/生产者链,出于学习目的,我也没有使用通知/等待机制:

同步队列:

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

        }
    }
}
消费者:

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

        }
    }
}
制作人:

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

        }
    }
}
主要内容:

这里的问题是,如果isSet方法不同步,我会得到这样的输出:

97,
55
程序只是继续运行,没有输出任何值。如果isSet方法是同步的,则程序可以正常工作


我不明白为什么,没有死锁,isSet方法只是查询set实例变量而没有设置它,所以没有争用条件。

set
需要是
volatile

private boolean volatile set = false;
这可确保所有读卡器在写入完成时都能看到更新的值。否则,它们最终将看到缓存的值。这将在关于并发的文章中进行更详细的讨论,并提供使用
volatile
的不同模式的示例

现在,您的代码使用
synchronized
的原因可能最好用一个例子来解释<代码>同步方法可以编写如下(即,它们等效于以下表示形式):

在这里,实例本身被用作锁。这意味着只有线程可以持有该锁。这意味着任何线程都将始终获得更新的值,因为只有一个线程可以写入该值,而想要读取
set
的线程将无法执行
isSet
,直到另一个线程释放
this
上的锁,此时
set
的值将被更新

如果您想正确理解Java中的并发性,您应该认真阅读(我认为在某些地方也有一个免费的PDF)。我仍在阅读这本书,因为还有很多事情我不明白或是我错了



如前所述,当您有多个消费者时,您将遇到问题。这是因为他们都可以检查
isSet()
,发现有一个值要退出队列,这意味着他们都将尝试退出相同值的队列。归根结底,您真正想要的是使“check and dequeue if set”操作有效地原子化,但您编码它的方式并非如此。这是因为最初调用
isSet
的同一个线程不一定是随后调用
dequeue
的同一个线程。因此,操作作为一个整体不是原子的,这意味着您必须同步整个操作。

您遇到的问题是可见性(或者更确切地说,缺乏可见性)

如果没有任何相反的指令,JVM将假定分配给一个线程中的变量的值不必对其他线程可见。有时(在方便的时候)会使其可见,也可能永远不会。Java内存模型定义了控制哪些内容应该可见以及何时可见的规则,并对这些规则进行了总结。(一开始它们可能有点枯燥和可怕,但理解它们绝对至关重要。)

因此,即使生产者将
set
设置为
true
,消费者仍会将其视为false。如何发布新值

  • 将该字段标记为挥发性。这适用于基本值,如
    boolean
    ,对于引用,您必须更加小心
  • synchronized
    不仅提供互斥,而且还保证任何输入使用同一对象的
    synchronized
    块的人都可以看到其中设置的任何值。(这就是为什么如果您声明
    isSet()
    方法
    synchronized
    ,一切都能正常工作的原因)
  • 使用线程安全库类,如
    java.util.concurrent
  • 在您的情况下,
    volatile
    可能是最好的解决方案,因为您只更新
    布尔值,所以默认情况下保证更新的原子性



    正如所指出的,您的代码也有一个问题,因为您的线程可能会被
    isSet()
    enqueue()/dequeue()

    之间的另一个线程中断。我假设,当我们陷入线程问题时,第一步是确保两个线程都运行良好。(我知道他们会的,因为没有锁来创建死锁)

    为此,您还可以在enqueue函数中添加printf语句。这将确保入队和出队线程运行良好

    第二步应该是,“set”是共享资源,值的切换也应该足够好,以便代码能够以所需的方式运行


    我认为,如果您能够充分推理和处理日志记录,您就可以意识到问题中的问题。

    设置
    字段使用
    volatile
    ,以确保可见性。否则,CPU可以将其存储在缓存/寄存器中,而不作为优化进行更新。工作thx:),另一个问题:为什么使用synchronized关键字,程序可以正确运行?synchronized是否强制从主存而不是缓存/寄存器读取变量?因为
    synchronized
    具有相同的可见性结果,这在硬件中是存储内存屏障。我建议阅读
    Java并发性实践
    ,以获得一个温和的介绍。您将了解<代码>发生在<代码>和<代码>roach motel model<代码>之前,而不会迷失在硬件细节(屏障、MESI协议等)中。@karim:您需要的是一个内存屏障,以确保从<代码>集<代码>字段读取的内容是新的(而不是从缓存)。
    volatile
    关键字会导致为每次读写操作插入内存屏障。
    synchroni