Java 多线程访问和线程的可变缓存

Java 多线程访问和线程的可变缓存,java,android,multithreading,volatile,Java,Android,Multithreading,Volatile,如果我读了一本关于多线程的完整章节/书籍,我就能找到答案,但是我想要一个更快的答案。(我知道stackoverflow问题类似,但还不够。) 假设有这样一个类: public class TestClass { private int someValue; public int getSomeValue() { return someValue; } public void setSomeValue(int value) { someValue = value; } }

如果我读了一本关于多线程的完整章节/书籍,我就能找到答案,但是我想要一个更快的答案。(我知道stackoverflow问题类似,但还不够。)

假设有这样一个类:

public class TestClass {
   private int someValue;

   public int getSomeValue() { return someValue; }
   public void setSomeValue(int value) {  someValue = value; }
}
有两个线程(A和B)访问该类的实例。考虑以下顺序:

  • A:getSomeValue()
  • B:setSomeValue()
  • A:getSomeValue()
  • 如果我是对的,someValue必须是可变的,否则第三步可能不会返回最新的值(因为A可能有一个缓存的值)。这是正确的吗

    第二种情况:

  • B:setSomeValue()
  • A:getSomeValue()
  • 在这种情况下,A将始终获得正确的值,因为这是它的第一次访问,所以他还不能拥有缓存的值。是这样吗

    如果一个类只通过第二种方式访问,那么就不需要volatile/同步,或者是

    注意,这个例子被简化了,实际上我想知道的是复杂类中的特定成员变量和方法,而不是整个类(即哪些变量应该是可变的或具有同步访问权限)。主要问题是:如果有更多线程访问某些数据,是否需要通过各种方式进行同步访问,还是取决于它们访问数据的方式(例如顺序)


    在阅读了评论之后,我试图用另一个例子来说明我困惑的根源:

  • 从UI线程:
    threadA.start()
  • threadA调用
    getSomeValue()
    ,并通知UI线程
  • UI线程获取消息(在其消息队列中),因此它调用:
    threadB.start()
  • threadB调用
    setSomeValue()
    ,并通知UI线程
  • UI线程获取消息,并通知threadA(以某种方式,例如消息队列)
  • threadA调用
    getSomeValue()

  • 这是一个完全同步的结构,但为什么这意味着threadA将在步骤6中获得最新的值?(如果
    someValue
    不是易失性的,或者从任何地方访问时没有放入监视器)

    如果两个线程调用相同的方法,则无法保证调用所述方法的顺序。因此,依赖于调用顺序的原始前提是无效的


    这与调用方法的顺序无关;这是关于同步的。这是关于使用某种机制使一个线程等待另一个线程完全完成其写操作。一旦决定使用多个线程,就必须提供同步机制以避免数据损坏。

    问题在于java只是一种规范。有许多JVM实现和物理操作环境的示例。在任何给定的组合上,动作可能是安全的,也可能是不安全的。例如,在单处理器系统上,示例中的volatile关键字可能完全没有必要。由于内存和语言规范的编写者无法合理地解释可能的操作条件集,因此他们选择将保证在所有兼容实现上工作的某些模式列为白名单。遵守这些指导原则可以确保代码在目标系统上工作,并且可以合理地移植

    在这种情况下,“缓存”通常指在硬件级别进行的活动。java中发生的某些事件会导致多处理器系统上的内核“同步”其缓存。对易失性变量的访问就是一个例子,同步块是另一个例子。想象一个场景,其中这两个线程X和Y被安排在不同的处理器上运行

    X starts and is scheduled on proc 1
    y starts and is scheduled on proc 2
    
    .. now you have two threads executing simultaneously
    to speed things up the processors check local caches
    before going to main memory because its expensive.
    
    x calls setSomeValue('x-value') //assuming proc 1's cache is empty the cache is set
                                    //this value is dropped on the bus to be flushed
                                    //to main memory
                                    //now all get's will retrieve from cache instead
                                    //of engaging the memory bus to go to main memory 
    y calls setSomeValue('y-value') //same thing happens for proc 2
    
    //Now in this situation depending on to order in which things are scheduled and
    //what thread you are calling from calls to getSomeValue() may return 'x-value' or
    //'y-value. The results are completely unpredictable.  
    
    关键是,
    volatile
    (在兼容的实现中)确保有序写入始终刷新到主内存,并且在下一次访问之前,其他处理器的缓存将被标记为“脏”,而不管从哪个线程进行访问

    免责声明:volatile不会锁定。这一点非常重要,尤其是在以下情况下:

    volatile int counter;
    
    public incrementSomeValue(){
        counter++; // Bad thread juju - this is at least three instructions 
                   // read - increment - write             
                   // there is no guarantee that this operation is atomic
    }
    
    如果您的目的是在
    getSomeValue


    如果意图是
    getSomeValue()
    必须始终反映最近对
    setSomeValue()
    的调用,那么这是使用
    volatile
    关键字的好地方。请记住,如果没有它,就不能保证
    getSomeValue()
    将反映到最近对
    setSomeValue()
    的调用,即使
    setSomeValue()
    是首先安排的

    众所周知,我们需要保护的是数据的关键状态,控制数据关键状态的原子语句必须同步

    我有一个例子,其中使用了volatile,然后我使用了两个线程,每次将计数器的值增加1,直到10000。所以总数必须是20000。但令我惊讶的是,这种事并不总是发生

    然后我使用了synchronized关键字使其工作。

    同步确保当线程访问同步方法时,不允许其他线程访问该对象的此方法或任何其他同步方法,确保数据未损坏

    线程安全类意味着它将在存在底层运行时环境的调度和交错的情况下保持其正确性,而不需要来自客户端的任何线程安全机制来访问该类。让我们看看

    一个字段可以声明为volatile,在这种情况下,Java内存模型(§17)确保所有线程都能看到变量的一致值

    因此,
    volatile
    是声明变量wo