volatile关键字

关键字volatile的作用是使变量在多个线程间可见。

在讲解volatile关键字之前我们先来看下下面这个例子:

public class DemoPrint {

    //开关
    private Boolean flag = true;
    
    public Boolean getFlag() {
        return flag;
    }
    //设置开关
    public void setFlag(Boolean flag) {
        this.flag = flag;
    }
    
    public void println() {
        try {
            while (flag) {
                System.out.println("running..........threadName:" 
                        + Thread.currentThread().getName());
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Test {
    public static void main(String[] args) {
        DemoPrint print = new DemoPrint();
        print.println();
        System.out.println("stop..........." + Thread.currentThread().getName());
        print.setFlag(false);
    }
}
image.png

程序运行后根本停止不下来,因为这个例子是单线程的,线程从while循环里根本出不来,更别提在后面将开关给关上了。

我们思考一下,能不能利用多线程来将这个死循环的开关给彻底的关上呢?

public class DemoPrint implements Runnable{

    //开关
    private Boolean flag = true;
    
    public Boolean getFlag() {
        return flag;
    }
    //设置开关
    public void setFlag(Boolean flag) {
        this.flag = flag;
    }
    
    public void println() {
        try {
            while (flag) {
                System.out.println("running..........threadName:" 
                        + Thread.currentThread().getName());
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public void run() {
        println();
    }
}

首先我们创建DemoPrint如上所示,

public class Test {
    public static void main(String[] args) {
        DemoPrint print = new DemoPrint();
        Thread thread = new Thread(print);
        thread.start();
        System.out.println("stop..........." + Thread.currentThread().getName());
        print.setFlag(false);
    }
}
image.png

程序运行如图所示,死循环在线程开启不久就被主线程给关闭了开关。

但据说上述代码在-server服务器模式中64bit的JVM上时,会出现死循环,解决办法是使用volatile关键字。

关键字volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。

image.png

使用volatile关键字增加了实例变量在多个线程之间的可见性。但是volatile关键字最致命的缺点是不支持原子性。

下面将关键字synchronized和volatile进行比较:
1)关键字volatile是线程同步的轻量级视线,所以volatile性能肯定比synchronized要好,并且volatile只能修饰变量,而synchronized可以修饰方法,以及代码块。

2)多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。

3)volatile能保证数据的可见性,但是不能保证原子性;而synchronized可以保证原子性,也可以简介保证可见性,因为他会将私有内存和公共内存中的数据做同步。

4)关键字vloatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。

vloatile非原子性

关键字volatile虽然增加了实例变量在多个线程之间的可见性,但它却不具备同步性,那么也就不具备原子性。

下面我们来看一个例子:

package other.thread7;

public class VolatileThread extends Thread{

    public volatile static int count;
    
    private static void addCount() {
        for (int i = 0; i < 100; i++) {
            count++;
        }
        System.out.println(count);
    }
    
    @Override
    public void run() {
        addCount();
    }
}
public class Test {
    public static void main(String[] args) {
        
        VolatileThread[] arr = new VolatileThread[100];
        for (int i = 0; i < 100; i++) {
            arr[i] = new VolatileThread();
        }
        for (int i = 0; i < 100; i++) {
            arr[i].start();
        }
    }
}
image.png

从结果上来看线程明显不是同步的,明明我们使用了volatile将变量从公共内存中取值,按理来说数据应该是0,100.200...这样同步的才对啊。

关键字volatile主要使用的场合是在多个线程中可以感知实例变量被更改了,并且可以获得最新的值使用,也就是用多线程读取共享变量时可以获得最新值使用。

但是在这里需要注意的是:如果修改了实例变量中的数据,比如说count++,也就是count=count + 1,则这样的操作其实并不是一个原子操作也就是非线程安全的,它包含三步
1)从内存中取出count的值;
2)计算count的值;
3)将count的值写入内存
如果在第二步时,另外一个线程也修改了count的值,那么这个时候就会出现脏读数据。解决的办法就是使用synchronized关键字,所以说volatile本身并不处理数据的原子性,而是强制对数据的读写及时影响到主内存的。

用图来演示一下用关键字volatile时出现非线程安全的原因:
1)read和load阶段:从主内存赋值变量到当前线程的工作内存;
2)use和assign阶段:执行代码,改变共享变量值;
3)store和write阶段:用工作内存数据刷新主内存对应变量的值。

image.png

在多线程环境中,use和assign是多次出现的,但这一操作并不是原子性,也就是在read和load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,也就是私有内存和公共内存中的变量不同步,所以计算出来的结果和预期不一样,也就出现了非线程安全问题。

对于用volatile修饰的变量,JVM虚拟机只是保证从主内存加载到线程工作内存的值是最新的,例如线程1和线程2在进行read和load的操作用,发现主内存中的count的值都是5,那么都会加载这个最新的值。也就是说volatile关键字解决的是变量读时的可见性问题,但无法保证原子性,对于多个线程访问同一个实力变量还是需要加锁同步。

除了在count++操作时使用synchronized关键字实现同步外,还可以使用AtomicInteger原子类进行实现。

原子操作时不能分割的整体,没有其他线程能够中断或检查正在原子操作中的变量。一个原子(atomic)类型就是一个原子操作可用的类型,它可以在没有锁的情况下做到线程安全。

嗯!书上是这么写的,那么让我们来验证一下。

package other.thread7;

import java.util.concurrent.atomic.AtomicInteger;

public class VolatileThread extends Thread{

    public volatile static  AtomicInteger count = new AtomicInteger(0);
    
    private static void addCount() {
        for (int i = 0; i < 100; i++) {
            count.incrementAndGet();
        }
        System.out.println(count);
    }
    
    @Override
    public void run() {
        addCount();
    }
}
public class Test {
    public static void main(String[] args) {
        
        VolatileThread[] arr = new VolatileThread[100];
        for (int i = 0; i < 100; i++) {
            arr[i] = new VolatileThread();
        }
        for (int i = 0; i < 100; i++) {
            arr[i].start();
        }
    }
}
image.png

嗯嗯嗯嗯???书上说的都是骗人的???

mmp,继续分析下原因。。。毕竟我是小白,写书的是大佬,相比于大佬还是我错误的几率更高。

作者外出查询与思考ing

作者外出查询与思考ed

作者回归。。。

mmp,作者果然是小白,明明原因十分的简单,就是addCount()没有同步造成的,AtomicInteger 虽然具有原子性,但是方法与方法间的调用却不是原子的,没招了,加锁吧。

image.png

加锁完后ok了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容