当前位置: 首页 > 面试题库 >

Java中的volatile和Synchronized之间的区别

宋昕
2023-03-14
问题内容

我想知道将变量声明为as volatile和始终synchronized(this)在Java块中访问变量之间的区别吗?


问题答案:

重要的是要了解线程安全有两个方面。

  1. execution control, and
  2. memory visibility

第一个与控制代码何时执行(包括执行指令的顺序)以及是否可以同时执行有关,第二个与其他线程可以看到存储器中已完成操作的效果有关。由于每个CPU与主内存之间都具有多个高速缓存级别,因此运行在不同CPU或内核上的线程在任何给定的时间可以看到不同的“内存”,因为允许线程获取并使用主内存的专用副本。

使用synchronized防止任何其他线程获取同一对象的监视器(或锁),从而防止在同一对象上通过同步保护的所有代码块同时执行。同步还会创建“先于先发生”的内存屏障,从而导致内存可见性约束,使得直到某个线程释放锁的点之前所做的所有操作都在另一个线程中出现,随后又在获取该锁之前获取了相同的锁。实际上,在当前硬件上,这通常会导致在获取监视器时刷新CPU缓存,并在释放监视器时写入主内存,这两者都是(相对)昂贵的。

使用volatile,而另一方面,将强制所有访问(读或写)到易失性可变发生到主存储器,有效地把挥发性变量out CPU的高速缓存。这对于某些仅要求变量的可见性正确且访问顺序不重要的操作很有用。使用volatile还改变了对它们的处理,long并double要求对其进行原子访问;在某些(较旧的)硬件上,这可能需要锁,但在现代64位硬件上则不需要。在适用于Java 5+的新(JSR-133)内存模型下,就内存可见性和指令顺序而言,volatile的语义已得到增强,几乎与同步一样强大(请参见http://www.cs.umd.edu /users/pugh/java/memoryModel/jsr-133-faq.html#volatile)。出于可见的目的,对易失性字段的每次访问都像同步的一半。

在新的内存模型下,volatile变量不能彼此重新排序仍然是正确的。区别在于,现在不再很容易对它们周围的常规字段访问进行重新排序。写入易失性字段具有与监视器释放相同的存储效果,而从易失性字段中读取具有与监视器获取相同的存储效果。实际上,由于新的内存模型对易失性字段访问与其他字段访问(无论是否为易失性)的重新排序施加了更严格的约束,A因此在写入易失性字段f时线程看到的任何内容B在读取时对线程都是可见的f。

  • JSR 133(Java的内存模型)的常见问题解答

因此,现在两种形式的内存屏障(在当前的JMM下)都会导致指令重新排序屏障,这会阻止编译器或运行时跨屏障对指令进行重新排序。在旧的JMM中,volatile不会阻止重新排序。这一点很重要,因为除了内存障碍之外,唯一的限制是,对于任何特定线程,代码的最终效果都与如果指令以它们在内存 中出现的确切顺序执行的情况相同。资源。

volatile的一种用法是在运行时重新创建共享但不可变的对象,许多其他线程在其执行周期中的特定点引用该对象。一旦发布了重新创建的对象,就需要其他线程开始使用它,但是不需要完全同步以及随之而来的争用和缓存刷新的额外开销。

// Declaration
public class SharedLocation {
    static public SomeObject someObject=new SomeObject(); // default object
    }

// Publishing code
// Note: do not simply use SharedLocation.someObject.xxx(), since although
//       someObject will be internally consistent for xxx(), a subsequent 
//       call to yyy() might be inconsistent with xxx() if the object was 
//       replaced in between calls.
SharedLocation.someObject=new SomeObject(...); // new object is published

// Using code
private String getError() {
    SomeObject myCopy=SharedLocation.someObject; // gets current copy
    ...
    int cod=myCopy.getErrorCode();
    String txt=myCopy.getErrorText();
    return (cod+" - "+txt);
    }
// And so on, with myCopy always in a consistent state within and across calls
// Eventually we will return to the code that gets the current SomeObject.

具体来说,请讲你的读写更新问题。考虑以下不安全代码:

public void updateCounter() {
    if(counter==1000) { counter=0; }
    else              { counter++; }
    }

现在,在不同步updateCounter()方法的情况下,两个线程可以同时输入它。在可能发生的多种排列中,一个是线程1对counter == 1000进行了测试,发现它为true,然后被挂起。然后线程2进行相同的测试,并且也看到它是正确的并被挂起。然后线程1恢复并将计数器设置为0。然后线程2恢复并再次将计数器设置为0,因为它错过了线程1的更新。即使未发生线程切换,也可能发生这种情况,这仅仅是因为两个不同的CPU内核中存在两个不同的缓存计数器计数器副本,并且每个线程都在一个单独的内核上运行。为此,一个线程可能由于缓存而在一个值上具有计数器,而另一个线程可能在某个完全不同的值上具有计数器。

在此示例中重要的是,将变量计数器从主存储器读取到高速缓存中,然后在高速缓存中进行更新,并且仅在出现内存屏障或其他情况下需要高速缓冲存储器时,才在某个不确定的时间点将其写回主存储器。volatile对于该代码的线程安全而言,使计数器不足是因为对最大值的测试和分配是离散操作,包括增量(一组非原子read+increment+write机器指令),例如:

MOV EAX,counter
INC EAX
MOV counter,EAX

易变变量仅在对其执行的所有操作都是“原子的” 时才有用,例如在我的示例中,仅读取或写入对完全形成的对象的引用(实际上,通常仅从单个点写入)。另一个示例是支持写时复制列表的易失性数组引用,前提是该数组仅通过首先对该引用进行本地复制来读取。



 类似资料:
  • 本文向大家介绍java中volatile和synchronized的区别与联系,包括了java中volatile和synchronized的区别与联系的使用技巧和注意事项,需要的朋友参考一下 java中volatile和synchronized的区别与联系 这个可能是最好的对比volatile和synchronized作用的文章了。volatile是一个变量修饰符,而synchronized是一个

  • 本文向大家介绍Java中volatile和transient之间的区别,包括了Java中volatile和transient之间的区别的使用技巧和注意事项,需要的朋友参考一下 volatile关键字用于多线程环境中,其中两个线程同时读取和写入同一变量。volatile关键字将更改直接刷新到主内存,而不是CPU缓存。  另一方面,在序列化过程中使用了transient关键字。标记为瞬态的字段不能成为

  • 问题内容: 原子/易失性/同步在内部如何工作? 以下代码块有什么区别? 代码1 代码2 代码3 是否volatile以以下方式工作?是 相当于 我认为两个线程不能同时进入同步块…对吗?如果这是真的,那么atomic.incrementAndGet()没有它synchronized怎么办?而且它是线程安全的吗? 内部读写可变变量/原子变量之间有什么区别?我在某些文章中读到,线程具有变量的本地副本-那

  • 问题内容: 我知道已经解决了这个问题,但是我在SO上看到了不一致的论点。 所以,如果我有: 我得到 FALSE 。 据我了解,这是因为和是对同一对象()的两个不同引用。 所以我会有类似的东西: 现在,如果我只想比较两个字符串的 内容 ,我将使用 这是否意味着如果两个引用指向同一个对象,JVM就会简单地返回?因此,它不是在逐个字符地进行比较吗? 谢谢 编辑 拿着电话。感谢您指出优先顺序!!! 当我将

  • 本文向大家介绍详解java并发编程(2) --Synchronized与Volatile区别,包括了详解java并发编程(2) --Synchronized与Volatile区别的使用技巧和注意事项,需要的朋友参考一下 1 Synchronized 在多线程并发中synchronized一直是元老级别的角色。利用synchronized来实现同步具体有一下三种表现形式: 对于普通的同步方法,锁是当

  • 问题内容: 正如问题标题本身所言,Java中的Executors和ExecutorCompletionService类之间有什么区别? 我是Threading的新手,所以如果有人可以用一段代码进行解释,那将会很有帮助。 问题答案: 假设您有一组任务,并且要在中异步执行每个任务,并在完成时按1逐个处理结果。 使用,您将像这样: 这种方法的问题是不能保证任务将首先完成。因此,当主线程可能正在处理另一个