Java 中最简单易懂的关键字的例子

我正在阅读关于 Java 中的 反复无常关键字,并且完全理解它的理论部分。

但是,我要寻找的是,一个很好的例子,它显示了如果变量不是 反复无常会发生什么,如果它是。

下面的代码片段不能按预期工作(取自 给你) :

class Test extends Thread {


boolean keepRunning = true;


public void run() {
while (keepRunning) {
}


System.out.println("Thread terminated.");
}


public static void main(String[] args) throws InterruptedException {
Test t = new Test();
t.start();
Thread.sleep(1000);
t.keepRunning = false;
System.out.println("keepRunning set to false.");
}
}

理想情况下,如果 keepRunning不是 反复无常,线程应该无限期地继续运行。

我有两个基本问题:

  • 有人能用例子来解释不稳定性吗? 没有人能用 JLS 的理论来解释。
  • 易失性是同步的替代品吗? 它是否实现了原子性?
83950 次浏览

Ideally, if keepRunning wasn't volatile, thread should keep on running indefinitely. But, it does stop after few seconds.

If you are running in a single-processor or if your system is very busy, the OS may be swapping out the threads which causes some levels of cache invalidation. Not having a volatile doesn't mean that memory will not be shared, but the JVM is trying to not synchronize memory if it can for performance reasons so the memory may not be updated.

Another thing to note is that System.out.println(...) is synchronized because the underlying PrintStream does synchronization to stop overlapping output. So you are getting memory synchronization "for free" in the main-thread. This still doesn't explain why the reading loop sees the updates at all however.

Whether the println(...) lines are in or out, your program spins for me under Java6 on a MacBook Pro with an Intel i7.

Can anyone explain volatile with example ? Not with theory from JLS.

I think your example is good. Not sure why it isn't working with all System.out.println(...) statements removed. It works for me.

Is volatile substitute for synchronization ? Does it achieve atomicity ?

In terms of memory synchronization, volatile throws up the same memory barriers as a synchronized block except that the volatile barrier is uni-directional versus bi-directional. volatile reads throw up a load-barrier while writes throw up a store-barrier. A synchronized block is a bi-directional barrier with the addition of mutex locking.

In terms of atomicity, however, the answer is "it depends". If you are reading or writing a value from a field then volatile provides proper atomicity. However, incrementing a volatile field suffers from the limitation that ++ is actually 3 operations: read, increment, write. In that case or more complex mutex cases, a full synchronized block may be necessary. AtomicInteger solves the ++ issue with a complicated test-and-set spin-loop.

volatile is not going to necessarily create giant changes, depending on the JVM and compiler. However, for many (edge) cases, it can be the difference between optimization causing a variable's changes to fail to be noticed as opposed to them being correctly written.

Basically, an optimizer may choose to put non-volatile variables on registers or on the stack. If another thread changes them in the heap or the classes' primitives, the other thread will keep looking for it on the stack, and it'll be stale.

volatile ensures such optimizations don't happen and all reads and writes are directly to the heap or another place where all threads will see it.

When a variable is volatile, it is guaranteeing that it will not be cached and that different threads will see the updated value. However, not marking it volatile does not guarantee the opposite. volatile was one of those things that was broken in the JVM for a long time and still not always well understood.

Volatile --> Guarantees visibility and NOT atomicity

Synchronization (Locking) --> Guarantees visibility and atomicity (if done properly)

Volatile is not a substitute for synchronization

Use volatile only when you are updating the reference and not performing some other operations on it.

Example:

volatile int i = 0;


public void incrementI(){
i++;
}

will not be thread safe without use of synchronization or AtomicInteger as incrementing is an compound operation.

Why program does not run indefinitely?

Well that depends on various circumstances. In most cases JVM is smart enough to flush the contents.

Correct use of volatile discusses various possible uses of volatile. Using volatile correctly is tricky, I would say "When in doubt, Leave it out", use synchronized block instead.

Also:

synchronized block can be used in place of volatile but the inverse is not true.

For your particular example: if not declared volatile the server JVM could hoist the keepRunning variable out of the loop because it is not modified in the loop (turning it into an infinite loop), but the client JVM would not. That is why you see different results.

General explanation about volatile variables follows:

When a field is declared volatile, the compiler and runtime are put on notice that this variable is shared and that operations on it should not be reordered with other memory operations. Volatile variables are not cached in registers or in caches where they are hidden from other processors, so a read of a volatile variable always returns the most recent write by any thread.

The visibility effects of volatile variables extend beyond the value of the volatile variable itself. When thread A writes to a volatile variable and subsequently thread B reads that same variable, the values of all variables that were visible to A prior to writing to the volatile variable become visible to B after reading the volatile variable.

The most common use for volatile variables is as a completion, interruption, or status flag:

  volatile boolean flag;
while (!flag)  {
// do something untill flag is true
}

Volatile variables can be used for other kinds of state information, but more care is required when attempting this. For example, the semantics of volatile are not strong enough to make the increment operation (count++) atomic, unless you can guarantee that the variable is written only from a single thread.

Locking can guarantee both visibility and atomicity; volatile variables can only guarantee visibility.

You can use volatile variables only when all the following criteria are met:

  • Writes to the variable do not depend on its current value, or you can ensure that only a single thread ever updates the value;
  • The variable does not participate in invariants with other state variables; and
  • Locking is not required for any other reason while the variable is being accessed.

Debugging tip: be sure to always specify the -server JVM command line switch when invoking the JVM, even for development and testing. The server JVM performs more optimization than the client JVM, such as hoisting variables out of a loop that are not modified in the loop; code that might appear to work in the development environment (client JVM) can break in the deployment environment (server JVM).

This is an excerpt from "Java Concurrency in Practice", the best book you can find on this subject.

Objects that are declared as volatile are usually used to communicate state information among threads,To ensure CPU caches are updated, that is, kept in sync, in the presence of volatile fields, a CPU instruction, a memory barrier, often called a membar or fence, is emitted to update CPU caches with a change in a volatile field’s value.

The volatile modifier tells the compiler that the variable modified by volatile can be changed unexpectedly by other parts of your program.

The volatile variable must be used in Thread Context only. see the example here

I have modified your example slightly. Now use the example with keepRunning as volatile and non volatile member :

class TestVolatile extends Thread{
//volatile
boolean keepRunning = true;


public void run() {
long count=0;
while (keepRunning) {
count++;
}


System.out.println("Thread terminated." + count);
}


public static void main(String[] args) throws InterruptedException {
TestVolatile t = new TestVolatile();
t.start();
Thread.sleep(1000);
System.out.println("after sleeping in main");
t.keepRunning = false;
t.join();
System.out.println("keepRunning set to " + t.keepRunning);
}
}

Please find the solution below,

The value of this variable will never be cached thread-locally: all reads and writes will go straight to "main memory". The volatile force the thread to update the original variable for each time.

public class VolatileDemo {


private static volatile int MY_INT = 0;


public static void main(String[] args) {


ChangeMaker changeMaker = new ChangeMaker();
changeMaker.start();


ChangeListener changeListener = new ChangeListener();
changeListener.start();


}


static class ChangeMaker extends Thread {


@Override
public void run() {
while (MY_INT < 5){
System.out.println("Incrementing MY_INT "+ ++MY_INT);
try{
Thread.sleep(1000);
}catch(InterruptedException exception) {
exception.printStackTrace();
}
}
}
}


static class ChangeListener extends Thread {


int local_value = MY_INT;


@Override
public void run() {
while ( MY_INT < 5){
if( local_value!= MY_INT){
System.out.println("Got Change for MY_INT "+ MY_INT);
local_value = MY_INT;
}
}
}
}


}

Please refer this link http://java.dzone.com/articles/java-volatile-keyword-0 to get more clarity in it.

What is the volatile keyword? The volatile keyword prevents caching of variables.

Consider this code, first without the volatile keyword:

class MyThread extends Thread {
private boolean running = true;   //non-volatile keyword


public void run() {
while (running) {
System.out.println("hello");
}
}


public void shutdown() {
running = false;
}
}


public class Main {


public static void main(String[] args) {
MyThread obj = new MyThread();
obj.start();


Scanner input = new Scanner(System.in);
input.nextLine();
obj.shutdown();
}
}

Ideally, this program should print hello until the Return key is pressed. But on some machines it may happen that the variable running is cached and you cannot change its value from the shutdown() method which results in infinite printing of the hello text.

Thus, by using the volatile keyword, it is guaranteed that your variable will not be cached and the code will run fine on all machines.

private volatile boolean running = true;  //volatile keyword

Using the volatile keyword is a good and safer programming practice.

Variable Volatile: Volatile Keyword is applicable to variables. volatile keyword in Java guarantees that value of the volatile variable will always be read from main memory and not from Thread's local cache.

Access_Modifier volatile DataType Variable_Name;

Volatile Field: An indication to the VM that multiple threads may try to access/update the field's value at the same time. To a special kind of instance variables which has to shared among all the threads with Modified value. Similar to Static(Class) variable, Only one copy of volatile value is cached in main memory, So that before doing any ALU Operations each thread has to read the updated value from Main memory after ALU operation it has to write to main memory direclty. (A write to a volatile variable v synchronizes-with all subsequent reads of v by any thread) This means that changes to a volatile variable are always visible to other threads.

enter image description here

Here to a nonvoltaile variable if Thread t1 changes the value in t1's cache, Thread t2 can't access the changed value untill t1 writes, t2 read from main memory for the most recent modified value, which may lead to Data-Inconsistancy.

volatile cannot be cached - assembler

    +--------------+--------+-------------------------------------+
|  Flag Name   |  Value | Interpretation                      |
+--------------+--------+-------------------------------------+
| ACC_VOLATILE | 0x0040 | Declared volatile; cannot be cached.|
+--------------+--------+-------------------------------------+
|ACC_TRANSIENT | 0x0080 | Declared transient; not written or  |
|              |        | read by a persistent object manager.|
+--------------+--------+-------------------------------------+

Shared Variables: Memory that can be shared between threads is called shared memory or heap memory. All instance fields, static fields, and array elements are stored in heap memory.

Synchronization: synchronized is applicable to methods, blocks. allows to execute only 1-thread at a time on object. If t1 takes control, then remaining threads has to wait untill it release the control.

Example:

public class VolatileTest implements Runnable {


private static final int MegaBytes = 10241024;


private static final Object counterLock = new Object();
private static int counter = 0;
private static volatile int counter1 = 0;


private volatile int counter2 = 0;
private int counter3 = 0;


@Override
public void run() {
for (int i = 0; i < 5; i++) {
concurrentMethodWrong();
}


}


void addInstanceVolatile() {
synchronized (counterLock) {
counter2 = counter2 + 1;
System.out.println( Thread.currentThread().getName() +"\t\t « InstanceVolatile :: "+ counter2);
}
}


public void concurrentMethodWrong() {
counter = counter + 1;
System.out.println( Thread.currentThread().getName() +" « Static :: "+ counter);
sleepThread( 1/4 );


counter1 = counter1 + 1;
System.out.println( Thread.currentThread().getName() +"\t « StaticVolatile :: "+ counter1);
sleepThread( 1/4 );


addInstanceVolatile();
sleepThread( 1/4 );


counter3 = counter3 + 1;
sleepThread( 1/4 );
System.out.println( Thread.currentThread().getName() +"\t\t\t\t\t « Instance :: "+ counter3);
}
public static void main(String[] args) throws InterruptedException {
Runtime runtime = Runtime.getRuntime();


int availableProcessors = runtime.availableProcessors();
System.out.println("availableProcessors :: "+availableProcessors);
System.out.println("MAX JVM will attempt to use : "+ runtime.maxMemory() / MegaBytes );
System.out.println("JVM totalMemory also equals to initial heap size of JVM : "+ runtime.totalMemory() / MegaBytes );
System.out.println("Returns the amount of free memory in the JVM : "+ untime.freeMemory() / MegaBytes );
System.out.println(" ===== ----- ===== ");


VolatileTest volatileTest = new VolatileTest();
Thread t1 = new Thread( volatileTest );
t1.start();


Thread t2 = new Thread( volatileTest );
t2.start();


Thread t3 = new Thread( volatileTest );
t3.start();


Thread t4 = new Thread( volatileTest );
t4.start();


Thread.sleep( 10 );;


Thread optimizeation = new Thread() {
@Override public void run() {
System.out.println("Thread Start.");


Integer appendingVal = volatileTest.counter2 + volatileTest.counter2 + volatileTest.counter2;


System.out.println("End of Thread." + appendingVal);
}
};
optimizeation.start();
}


public void sleepThread( long sec ) {
try {
Thread.sleep( sec * 1000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

Static[Class Field] vs Volatile[Instance Field] - Both are not cached by threads

  • Static fields are common to all threads and get stored in Method Area. Static with volatile no use. Static field cant be serialized.

  • Volatile mainly used with instance variable which get stored in heap area. The main use of volatile is to maintain updated value over all the Threads. instance volatile field can be Serialized.

@see

The volatile keyword tells the JVM that it may be modified by another thread. Each thread has its own stack, and so its own copy of variables it can access. When a thread is created, it copies the value of all accessible variables in its own memory.

public class VolatileTest {
private static final Logger LOGGER = MyLoggerFactory.getSimplestLogger();


private static volatile int MY_INT = 0;


public static void main(String[] args) {
new ChangeListener().start();
new ChangeMaker().start();
}


static class ChangeListener extends Thread {
@Override
public void run() {
int local_value = MY_INT;
while ( local_value < 5){
if( local_value!= MY_INT){
LOGGER.log(Level.INFO,"Got Change for MY_INT : {0}", MY_INT);
local_value= MY_INT;
}
}
}
}


static class ChangeMaker extends Thread{
@Override
public void run() {


int local_value = MY_INT;
while (MY_INT <5){
LOGGER.log(Level.INFO, "Incrementing MY_INT to {0}", local_value+1);
MY_INT = ++local_value;
try {
Thread.sleep(500);
} catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
}

try out this example with and without volatile.

Lot's of great examples, but I just want to add that there are a number of scenarios where volatile is required so there is no one concrete example to rule them a.

  1. You can use volatile to force all threads to get latest value of the variable from main memory.
  2. You can use synchronization to guard critical data
  3. You can use Lock API
  4. You can use Atomic variables

Check it out for more Java volatile examples.

public class VolatileDemo {
static class Processor {
//without volatile program keeps running on my platform
private boolean flag = false;


public void setFlag() {
System.out.println("setting flag true");
this.flag = true;
}


public void process() {
while(!flag) {
int x = 5;
// using sleep or sout will end the program without volatile.
// Probably these operations, cause thread to be rescheduled, read from memory. Thus read new flag value and end.
}


System.out.println("Ending");
}
}


public static void main(String[] args) throws InterruptedException {
Processor processor = new Processor();
Thread t1 = new Thread(processor::process);


t1.start();


Thread.sleep(2000);
processor.setFlag();


}
}