volaile仅能保证可见性,不能保证原子性。即一个线程对共享变量的修改,其它线程可见。
package threadTest;
public class volatileTest {
public static volatile boolean isStop =false;//
public static void stopIt()
{
isStop = true;
}
public static void main(String[] args) throws InterruptedException
{
MyThread mt = new MyThread(5);
new Thread(mt).start();
long start = System.currentTimeMillis();
/*isStop是volatile修饰的,因而当其被其它线程修改时,主线程是可见的,
* 因而当mt执行后isStop被修改了,while循环结束
*/
while(!isStop)
{
}
System.out.println("stop");
long end = System.currentTimeMillis();
System.out.println(end-start);
}
static class MyThread implements Runnable
{
private int times;
public void run()
{
try{
Thread.sleep(times*1000);
}catch(InterruptedException e){
e.printStackTrace();
}
stopIt();
}
public MyThread(int times)
{
this.times = times;
}
}
}
看到主线程在指定的时间后结束了。如果不加volatile修饰,则mt线程对isStop的修改在mt线程结束后同步回主内存,但是主线程的while循环总是从线程空间读取,而自己又没有改变它,因而这里成了一个死循环。
即volatile保证了其修饰的变量的可见性,某个线程对这个变量的修改会立马同步到主内存,而所有线程对这个变量的读取每次都从主内存读取。
读多写少的场景
对于读多写少的场景,可以用volatile保证变量可见性,用synchronized修饰修改的方法,达到线程安全的目的
public class volatileTest {
public static volatile long n =0;//volatile
public static synchronized void add()//
{
n++;
}
public static void main(String[] args) throws InterruptedException
{
for(int i=0;i<1000;i++)
{
Thread th = new Thread(new MyThread2());
th.start();
//th.join();
//new Thread(new MyThread2()).start();
}
while(n<2000000)
{
/*这里发现一个有意思的现象,在n不加volatile修饰时,若这里不做任何操作的时候,主线程会一直卡主,但是循环中创建的线程其实是输出了2000000的;而输出n之后所有的线程都会很快执行。说明虽然n最后修改成了正确的值,主线程最后也获得了CPU使用权,但是它的线程空间保存的却不是最终值,因为没有保证变量n的可见性。但是打印n的值为什么又刷新了呢?*/
//System.out.println("n的值:"+n);
}
long end = System.currentTimeMillis();
System.out.println("执行时间:"+(end-start));
System.out.println("stop");
}
static class MyThread2 implements Runnable
{
public void run()
{
try{
Thread.sleep(1);
}catch(InterruptedException e){
e.printStackTrace();
}
for(long i=0;i<2000;i++)
{
add();
}
System.out.println(Thread.currentThread().getName()+":" +n);
}
}
}
由volatile保证可见性,由synchronized保证修改操作的原子性。
当然,对于本例中的场景,使用注释中的join()方法也能保证结果的正确性