关于多线程:Java中volatile和synchronized之间的区别

Difference between volatile and synchronized in Java

我想知道将变量声明为EDCOX1(0),在Java中的EDOCX1×1块中总是访问变量吗?

根据本文http://www.javamex.com/tutorials/synchronization_volatile.shtml,有很多话要说,有很多不同之处,但也有一些相似之处。

我对这条信息特别感兴趣:

...

  • access to a volatile variable never has the potential to block: we're only ever doing a simple read or write, so unlike a synchronized block we will never hold on to any lock;
  • because accessing a volatile variable never holds a lock, it is not suitable for cases where we want to read-update-write as an atomic operation (unless we're prepared to"miss an update");

读更新写是什么意思?写操作不是一个更新操作,还是仅仅意味着更新操作是一个依赖于读操作的写入操作?

最重要的是,什么时候更适合声明变量volatile,而不是通过synchronized块访问它们?对于依赖输入的变量,使用volatile是一个好主意吗?例如,有一个名为render的变量通过呈现循环读取并由按键事件设置?


理解线程安全有两个方面很重要。好的。

  • 执行控制,以及
  • 内存可见性
  • 第一个问题与控制代码何时执行(包括执行指令的顺序)以及它是否可以并发执行有关,第二个问题与其他线程在内存中看到所做操作的效果有关。因为每个CPU在它和主内存之间都有几个级别的缓存,所以在不同的CPU或核心上运行的线程在任何给定时刻都可以看到不同的"内存",因为线程可以获得主内存的私有副本并在其上工作。好的。

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

    另一方面,使用volatile,强制对易失性变量的所有访问(读或写)都发生在主内存中,有效地将易失性变量从CPU缓存中移出。这对于一些只要求变量可见性正确且访问顺序不重要的操作非常有用。使用volatile还改变了longdouble的处理方式,要求访问它们是原子的;在某些(旧的)硬件上,这可能需要锁,但在现代64位硬件上则不需要锁。在Java 5 +的新的(JSR-133)内存模型下,易失性的语义已经增强到与内存可见性和指令排序同步的强(参见http://www. und.eDu//Pug/Java/MeMyMy/JSR-133-FAQ。为了提高可视性,对易失性字段的每个访问都相当于同步的一半。好的。< Buff行情>

    在新的内存模型下,不稳定变量仍然不能相互重新排序。不同的是,现在对它们周围的正常字段访问重新排序不再那么容易了。写入易失性字段与监视器释放具有相同的内存效果,从易失性字段读取与监视器获取具有相同的内存效果。实际上,由于新的内存模型对易失性字段访问与其他字段访问(无论是否易失性)的重新排序施加了更严格的约束,因此线程A在写入易失性字段f时可见的任何内容在线程B读取f时可见。好的。

    ——符合JSR(Java内存模型(133)常见问题解答

    步></

    所以,现在这两个形式的内存屏障(根据目前的JMM)产生一个屏障,防止重新排序指令在编译或运行时的指令重新排序,从对面的屏障。在旧的jmm,防止挥发性没有重新排序。这可以是重要的,因为除了记忆障碍,唯一的限制是强加的任何特定的线程,这样,在网络效应的代码是相同的,如果它的将是在指令执行的顺序中,他们appear准确的源代码。

    一个使用挥发性是共享的对象是不变的,但recreated飞,与许多其他的线程,以在该对象的参考点是在其执行周期。另一个线程需要开始使用,它是一recreated对象上,而不需要额外的开销,完全同步与它的日志和缓存的争用的冲洗。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 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.

    你说读写的问题,新的,具体的。考虑下面的代码:不安全

    1
    2
    3
    4
    public void updateCounter() {
        if(counter==1000) { counter=0; }
        else              { counter++; }
        }

    现在,updatecounter()方法的非同步双线程,它可以输入在同一时间。许多可能的子排列序列所发生的是,一个线程(1),该计数器测试= 1000和发现真实和再悬浮。多线程测试2相同的位置,也不真实,是暂停。然后线程1套和总结计数器到0。然后总结了集线程计数器2和0°,因为它想从线程1的更新。这也可以发生即使没有发生线程切换的描述,因为我有一个简单的,但不同的缓存副本的两个计数器的CPU是目前在两不同的颜色与每个单独的线程运行在线的核心。这是一个问题,线程可以在一个计数器的计数器值和其他一些可能有完全不同的价值(Just Because of缓存。

    什么是重要的是,在这个例子中的变量是从主存储器读取计数器的更新到高速缓存中,高速缓存和主存储器的写回只在一些点后indeterminate时发生内存屏障内存(缓存或当别人所需要的东西。制造柜台volatile是线程安全不足是这个代码,因为测试的最大的任务是和离散增量操作,包括在其中一集的非原子read+increment+write机器指令,这样的事情:

    1
    2
    3
    MOV EAX,counter
    INC EAX
    MOV counter,EAX

    变量是有用的,只有当所有的物质都是做在线业务的"原子",如我的例子在参考到的完全形成的对象仅仅是一个读或写的(和,事实上,它只是typically书面从单点)。另一个例子将是挥发性阵列基准收益的在线列表复制写入阵列读,只提供对第一参考位置,以它的副本。

    好的。


    volatile is a field modifier, while synchronized modifies code blocks and methods. So we can specify three variations of a simple accessor using those two keywords:

    1
    2
    3
    4
    5
    6
    7
    8
        int i1;
        int geti1() {return i1;}

        volatile int i2;
        int geti2() {return i2;}

        int i3;
        synchronized int geti3() {return i3;}

    geti1() accesses the value currently stored in i1 in the current thread.
    Threads can have local copies of variables, and the data does not have to be the same as the data held in other threads.In particular, another thread may have updated i1 in it's thread, but the value in the current thread could be different from that updated value. In fact Java has the idea of a"main" memory, and this is the memory that holds the current"correct" value for variables. Threads can have their own copy of data for variables, and the thread copy can be different from the"main" memory. So in fact, it is possible for the"main" memory to have a value of 1 for i1, for thread1 to have a value of 2 for i1 and for thread2 to have a value of 3 for i1 if thread1 and thread2 have both updated i1 but those updated value has not yet been propagated to"main" memory or other threads.

    On the other hand, geti2() effectively accesses the value of i2 from"main" memory. A volatile variable is not allowed to have a local copy of a variable that is different from the value currently held in"main" memory. Effectively, a variable declared volatile must have it's data synchronized across all threads, so that whenever you access or update the variable in any thread, all other threads immediately see the same value. Generally volatile variables have a higher access and update overhead than"plain" variables. Generally threads are allowed to have their own copy of data is for better efficiency.

    There are two differences between volitile and synchronized.

    Firstly synchronized obtains and releases locks on monitors which can force only one thread at a time to execute a code block. That's the fairly well known aspect to synchronized. But synchronized also synchronizes memory. In fact synchronized synchronizes the whole of thread memory with"main" memory. So executing geti3() does the following:

  • The thread acquires the lock on the monitor for object this .
  • The thread memory flushes all its variables, i.e. it has all of its variables effectively read from"main" memory .
  • The code block is executed (in this case setting the return value to the current value of i3, which may have just been reset from"main" memory).
  • (Any changes to variables would normally now be written out to"main" memory, but for geti3() we have no changes.)
  • The thread releases the lock on the monitor for object this.
  • So where volatile only synchronizes the value of one variable between thread memory and"main" memory, synchronized synchronizes the value of all variables between thread memory and"main" memory, and locks and releases a monitor to boot. Clearly synchronized is likely to have more overhead than volatile.

    http:///2007/12/difference-between-volatile-and.html javaexp.blogspot.com


    一个方法synchronized/块级访问限制级别的改性剂。它将使拥有一个确信的线程锁的关键部分。只有在它自己的线程,锁synchronizedCAN键块。如果其他线程试图访问这个关键的部分,直到他们想流的所有者释放锁。

    volatile变量访问修饰符这是部队的所有最新的线程被从主内存变量的值。没有锁的要求是volatile变量访问。所有线程可以访问在同一时间波动的变量值。

    是一个很好的例子:使用挥发性Date变量变量。

    认为你有约会volatile制造变量。所有的线程访问这个变量,它总是从主存储器的最新数据显示,所有的线程,这样的真实价值(实际)的日期。你不需要在不同的时间显示不同的线程变量。所有线程应该显示正确的日期值。

    enter image description here

    看看在这篇文章volatile为更好的理解概念。

    我以为你read-write-update queryCleary劳伦斯。

    关于你的其他查询

    When is it more suitable to declare variables volatile than access them through synchronized?

    你有使用volatile如果你认为应该得到所有线程的变量的实际值实时样的例子解释变量的第一个日期。

    Is it a good idea to use volatile for variables that depend on input?

    同样的答案将在第一个查询。

    这篇文章的问题,为更好的理解。


    我喜欢詹科夫的解释

    共享对象的可见性

    如果两个或多个线程共享一个对象,而没有正确使用volatile声明或同步,则一个线程对共享对象所做的更新可能对其他线程不可见。

    假设共享对象最初存储在主内存中。然后,在CPU One上运行的线程将共享对象读取到其CPU缓存中。在那里,它对共享对象进行更改。只要CPU缓存没有刷新回主内存,其他CPU上运行的线程就看不到共享对象的更改版本。这样,每个线程最终可能会得到它自己的共享对象副本,每个副本都位于不同的CPU缓存中。

    下图说明了草图情况。在左侧CPU上运行的一个线程将共享对象复制到其CPU缓存中,并将其count变量更改为2。在正确的CPU上运行的其他线程看不到此更改,因为计数更新尚未刷新回主内存。

    enter image description here

    为了解决这个问题,可以使用Java的易失性关键字。volatile关键字可以确保直接从主内存中读取给定的变量,并且在更新时总是写回主内存。

    竞争条件

    如果两个或多个线程共享一个对象,并且多个线程更新该共享对象中的变量,则可能发生争用条件。

    假设线程A将共享对象的变量计数读取到其CPU缓存中。想象一下,线程B做的是相同的,但是进入了不同的CPU缓存。现在线程A加一个来计数,线程B也这样做。现在var1已经增加了两次,在每个CPU缓存中增加一次。

    如果按顺序执行这些增量,变量计数将增加两次,并将原始值+2写回主内存。

    但是,这两个增量是同时执行的,没有适当的同步。无论线程A和线程B中哪个线程将其更新版本的计数写回主内存,更新后的值将仅比原始值高1,尽管有两个增量。

    此图说明了上述比赛条件下出现的问题:

    enter image description here

    为了解决这个问题,可以使用Java同步块。同步块保证在任何给定时间只有一个线程可以进入代码的给定关键部分。同步块还保证在同步块内访问的所有变量都将从主内存中读取,当线程退出同步块时,无论变量是否声明为volatile,所有更新的变量都将再次刷新回主内存。


    DR:

    多线程有三个主要问题:

    1)比赛条件

    2)缓存/过时内存

    3)编译器和CPU优化

    volatile可以解2&3,但不能解1。synchronized/显式锁可以解决1、2和3。

    详述:

    1)将此线程视为不安全代码:

    x++;

    虽然它看起来像一个操作,但实际上是3:从内存中读取x的当前值,向其中添加1,然后将其保存回内存。如果很少有线程同时尝试执行此操作,则操作的结果未定义。如果x最初是1,那么在2个线程操作代码之后,它可能是2,也可能是3,这取决于哪个线程完成了操作的哪个部分,然后控制权被转移到另一个线程。这是一种比赛条件。

    在代码块上使用synchronized使其具有原子性——这意味着它使3个操作同时发生,而另一个线程无法进入中间并进行干扰。所以如果x是1,2个线程尝试执行x++,我们知道最终它将等于3。所以它解决了竞赛条件问题。

    1
    2
    3
    synchronized (this) {
       x++; // no problem now
    }

    x标记为volatile并不能使x++;原子化,所以不能解决这个问题。

    2)此外,线程有自己的上下文——也就是说,它们可以从主内存缓存值。这意味着一些线程可以有一个变量的副本,但是它们在工作副本上操作,而不在其他线程之间共享该变量的新状态。

    考虑在一个线程上,x = 10;。后来,在另一个线程中,x = 20;。由于另一个线程已将新值保存到其工作内存中,但尚未将其复制到主内存中,因此在第一个线程中可能不会出现x值的更改。或者它确实把它复制到了主内存中,但是第一个线程没有更新它的工作副本。因此,如果现在第一个线程检查if (x == 20),答案将是false

    将变量标记为volatile基本上告诉所有线程只在主内存上执行读写操作。synchronized通知每个线程在进入块时从主内存更新其值,并在退出块时将结果刷新回主内存。

    请注意,与数据竞争不同,过时内存不太容易(重新)生成,因为对主内存的刷新无论如何都会发生。

    3)编译器和CPU可以(在线程之间没有任何形式的同步)将所有代码视为单线程。这意味着它可以查看一些代码,这在多线程方面是非常有意义的,并将其视为单线程的,而不是非常有意义的。因此,如果它不知道这个代码是为多线程设计的,那么它可以查看一个代码,为了优化,决定对它重新排序,甚至完全删除其中的一部分。

    考虑以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    boolean b = false;
    int x = 10;

    void threadA() {
        x = 20;
        b = true;
    }

    void threadB() {
        if (b) {
            System.out.println(x);
        }
    }

    您可能认为,threadb只能打印20(如果在设置b为真之前执行了检查,则threadb根本不能打印任何内容),因为只有在将x设置为20之后,b才设置为真,但编译器/cpu可能决定重新排序threada,在这种情况下,threadb也可以打印10。将b标记为volatile,确保不会重新排序(或在某些情况下丢弃)。这就是说,废纸只能印20张(或者什么都不能印)。将方法标记为同步将获得相同的结果。同样,将变量标记为volatile只会确保它不会被重新排序,但它之前/之后的所有内容仍然可以被重新排序,因此同步在某些情况下更适合。

    注意,在Java 5新的内存模型之前,易失性没有解决这个问题。