javaweb icon indicating copy to clipboard operation
javaweb copied to clipboard

java transient类型和volatile

Open www1350 opened this issue 8 years ago • 0 comments

transient

如果一个对象实现了Serilizable,如果你想排除某些属性的序列化,就在改变量钱加上transient 如private transient Thread exclusiveOwnerThread;

volatile

一个很通俗的解释 http://www.importnew.com/18126.html

在一个多线程应用里面,如果没有使用volatile修饰变量,为了性能,每个线程可能会从内存拷贝变量到cpu。如果你的电脑是多核,每个线程跑在不同的cpu里面。这意味着,每个线程可能会把同一个变量拷贝到不同cpu的cpu缓存里面。

假如现在有多个线程使用下面这个类

public class SharedObject {

    public int counter = 0;

}

假如线程1对counter增加,线程2只是偶尔读取。 如果该变量没有声明为volatile,就可能发生,这个值没有被写回内存,线程2读到的还是原来的值。

一个线程更新一个线程读取发生的问题被叫做一个visibility问题。

声明为 volatile所有的写着会把值写回内存,而读取这会直接从内存读取

public class SharedObject {

    public volatile int counter = 0;

}

从Java 5 开始,volatile除了保证从主内存变量读写,此外: 假如线程A写一个volatile变量线程B随后便读入,然后所有变量就在写入volatile前对线程A可见,然后在读入后对线程B可见。读写指令将不会对volatile变量重排。指令的前后都可以重排,但是不会和volatile混排。 当一个线程写入volatile变量,不只是刷入主存,写volatile变量前所有其他的volatile变量也会被线程改变然后刷入主存。

说白了,就是会一次性刷所有volatile 变量,读取的时候也会一次性刷

Look at this example:

Thread A:

    sharedObject.nonVolatile = 123;
    sharedObject.counter     = sharedObject.counter + 1;

Thread B:

    int counter     = sharedObject.counter;
    int nonVolatile = sharedObject.nonVolatile;

因为nonVolatile在volatile变量 sharedObject.counter之前,写入sharedObject.counter的时候,两个变量会被同时写入主存 线程B开始读入sharedObject.counter的时候,两个变量都会从主存读入CPU cache,所以线程B将会看到线程A写入的值 开发者可以使用整个特性拓展可见性保证线程之间优化变量的可见性。不是每个变量可以声明为volatile。下面是例子:

public class Exchanger {

    private Object   object       = null;
    private volatile hasNewObject = false;

    public void put(Object newObject) {
        while(hasNewObject) {
            //wait - do not overwrite existing new object
        }
        object = newObject;
        hasNewObject = true; //volatile write
    }
    public Object take(){
        while(!hasNewObject){ //volatile read
            //wait - don't take old object (or null)
        }
        Object obj = object;
        hasNewObject = false; //volatile write
        return obj;
    }
}

线程A可能会通过put不时地把对象放入。线程B可能不时地通过take取出。使用volatile变量(没有使用synchronized同步块)可以工作的得很好,如果只是线程A put线程B take。

然而,JVM可能重排指令来优化性能。

while(hasNewObject) {
    //wait - do not overwrite existing new object
}
hasNewObject = true; //volatile write
object = newObject;

注意到写入volatile变量hasNewObject现在是在newObject被设置之前。从JVM来看是完全有效的,这两个值互相没有依赖

然而,指令重排会损害object变量的可见性。首先,线程B可能会看到hasNewObject被设置为true但是线程A还没有把新值写入object。其次,不能保证写入object的值会被刷回主存

为了避免这种情况,volatile有“发生前保证”的功效。保证来读写指令不能被重排,volatile前后不能被混排

Look at this example:

sharedObject.nonVolatile1 = 123;
sharedObject.nonVolatile2 = 456;
sharedObject.nonVolatile3 = 789;



sharedObject.volatile     = true; //a volatile variable

int someValue1 = sharedObject.nonVolatile4;
int someValue2 = sharedObject.nonVolatile5;
int someValue3 = sharedObject.nonVolatile6;

JVM只能重排前三个属性

同样地, 后三个指令也会重排

volatile 不总是足够的

In fact, multiple threads could even be writing to a shared volatile variable, and still have the correct value stored in main memory, if the new value written to the variable does not depend on its previous value. In other words, if a thread writing a value to the shared volatile variable does not first need to read its value to figure out its next value.

As soon as a thread needs to first read the value of a volatile variable, and based on that value generate a new value for the shared volatile variable, a volatile variable is no longer enough to guarantee correct visibility. The short time gap in between the reading of the volatile variable and the writing of its new value, creates an race condition where multiple threads might read the same value of the volatile variable, generate a new value for the variable, and when writing the value back to main memory - overwrite each other's values.

The situation where multiple threads are incrementing the same counter is exactly such a situation where a volatile variable is not enough. The following sections explain this case in more detail.

想象一下,如果线程1读取共享计数器变量值0到CPU缓存,将它加到1而不是写改变值回主内存. 线程2可以从主内存读取相同的计数器变量,变量的值仍然是0,到其自己的CPU缓存. 线程2也可以增加计数器1,也不写回主内存. 这种情况见下图: 两个线程读过一个共享计数器变量纳入本地CPU缓存和增加。 线程1和线程2现在几乎同步。的真正价值共享计数器变量应该是2,但每个线程的变量的值为1,在CPU缓存和内存的值仍然是0。这是一片混乱!即使线程最终写值计数器变量回主内存,值将是错误的

When is volatile Enough? As I have mentioned earlier, if two threads are both reading and writing to a shared variable, then using the volatile keyword for that is not enough. You need to use a synchronized in that case to guarantee that the reading and writing of the variable is atomic. Reading or writing a volatile variable does not block threads reading or writing. For this to happen you must use the synchronized keyword around critical sections.

As an alternative to a synchronized block you could also use one of the many atomic data types found in the java.util.concurrent package. For instance, the AtomicLong or AtomicReference or one of the others.

In case only one thread reads and writes the value of a volatile variable and other threads only read the variable, then the reading threads are guaranteed to see the latest value written to the volatile variable. Without making the variable volatile, this would not be guaranteed.

The volatile keyword is guaranteed to work on 32 bit and 64 variables.

Performance Considerations of volatile

Reading and writing of volatile variables causes the variable to be read or written to main memory. Reading from and writing to main memory is more expensive than accessing the CPU cache. Accessing volatile variables also prevent instruction reordering which is a normal performance enhancement technique. Thus, you should only use volatile variables when you really need to enforce visibility of variables. http://tutorials.jenkov.com/java-concurrency/volatile.html

www1350 avatar Sep 09 '16 08:09 www1350