java学习(2).static,`volatile`,transient,final等关键字

二、volatile关键字

volatile是为了 保持变量的可见性,用于在并发使用的过程中。可以看成是一种轻量级的synchronized。使用起来代码更少。本文先介绍下volatile的原理,然后再进行使用场景的介绍。

首先要明确几个事情
1.volatile作用于共享变量,共享变量包括所有的实例变量,静态变量等。都存在堆内存中。

1。实现原理

既然volatile的作用是保证共享变量的可见性。那就分析是如何实现的。

private static volatile instance = null;
instance = new Singleton(); instance就是一个共享变量

通过JIT编译器生成的汇编看到加了Volatile修饰的变量多了一个lock语句。

这个操作会引发两个事情。
1.将当前处理器中缓存的数据写会到系统内存。
2.写回内存的操作会引起这部分内存在其他cpu里缓存的这部分内存地址的数据无效

处理器为了提高速度会创建缓存机制,不直接和内存通信。如果操作声明了volatile的变量,JVM就发送一条lock指令,将这个变量写回到系统内存。当在多核或者多线程情况下。通过嗅探在总线上传播的数据来判断自己持有的缓存的值是否过期了。如果过期了。就会把自己手里的值置为无效状态,会强制重新刷新把数据读出来。所以性能也会变差的。
所以用volatile修饰的变量,线程在每次使用的时候,都会读取变量修改后的值。

2.volatile和Synchronized的区别。

1.volatile是一个共享变量修饰符,synchronized则可以作用一段代码和方法。
2.volatile的作用是保证主内存和当前内存中此变量的值相等。相应的,用这个volatile变量修饰之后,这个变量的存取是比普通变量更耗时的。
3.synchronized干的是volatile干不了的。首先,synchronized获得和释放锁。用synchronized修饰的代码块能够保证只有一个线程走到。synchronized也进行内存的同步,他把线程的所有内存进行和和主内存的同步。
4.针对一些非原子操作volatile并不能够保证其线程安全。毕竟volatile只能保证其可见性嘛。

3.volatile的使用实例

(1)
这个例子虽然有问题但是底下的评论非常好。能够学到很多东西。
这个实例有问题,因为最开始的时候百度上搜到的所以也要记录一下哪个地方有问题

public class Counter {

    public volatile static int count = 0;
    public static void inc(){
        try{
            Thread.sleep(1);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        count++;        
        System.out.println("The Result is new :" + count);
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        for(int i = 0; i < 1000;i ++){
            new Thread(new Runnable(){
                @Override
                public void run(){
                    Counter.inc();
                }
            }).start();
        }

        /* 此处进行输出有2个问题,1。因为并不确定Thread是否全部执行完成,只能说全部start了之后 */
        /* 2。本程序共创建1001个线程,输出实现main线程中,那么此处的输出肯定也还是有问题的 */
        System.out.println("The Result is ALL :" + Counter.count);
    }

}

1.这段代码中,最开始不能用volatile来测试 ++操作。因为++本身就不是一个原子操作。volatile又不能够保证原子性。所以这样操作的结果说服力不大且有误导性
2.针对System.out.prinln部分,输出的地方在启动的1001个线程中的main线程,所以输出的也只是main线程当前能够获取到的count值,运行这段代码输出结果如下

The Result is new :988
The Result is new :983
The Result is new :982
The Result is new :979
The Result is new :989
The Result is new :987
The Result is ALL :992
The Result is new :992
The Result is new :993
The Result is new :994
The Result is new :991
The Result is new :990
The Result is new :996
The Result is new :995
The Result is new :997
The Result is new :998

ALL的显示在一堆的new之中。所以这个输出也并不能表示最终的值,只能表示这个输出在所有线程start之后
3.在inc函数上添加synchronized之后,ALL的显示会在很多的new之前,表明synchronized效率更低
4.volatile只是保证了可见性,也就是说一个线程修改了inc。那么其他线程在下次读取的时候保证从内存读取而不是缓存中。但是对于已经读取inc的线程就没有办法了。也就是说volatile没有保证整个数据的读取、修改、回写的原子性。
5.多线程程序的顺利执行一定要保证原子性,可见性,有序性。

引申:
1.如何等待线程的停止?
先来个坑:1.使用service.shutdown.2.使用join。
2.没有使用volatile修饰的变量,在多线程情况下怎么同步主内存。

这个帖子很好

3.那么volatile使用的场景应该怎么写呢?

针对单例模式中的double if判断,来说一下volatile关键字。
volatile实际上实现的是一个内存栅栏的操作。也就是说。volatile修饰的关键字类似于一个点,这个点之前和之后的指令是不能够相互重排的。并且执行到volatile之后时,前面的指令一定要执行完成。同时对此变量的修改也要直接和主内存进行交互。

4.java中哪些操作是原子操作,哪些不是呢?
1.除了long和double以外的基本类型的赋值操作是原子操作。
2.所有引用的赋值操作是原子操作。
3.java.concurrent.Atomic.*包中所有类的操作。。。。
但是AtomicInteger.set(AtomicInteger.get() + 1) 就会有并发问题了需要使用AtomicInteger.getAndIncrement()才可以避免并发问题。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值