并发编程三大特性之可见性

一、什么是可见性?

               可见性问题是基于CPU位置出现的,cpu处里速度非常快,相对CPU来说去主内存

      获取数据这个事情太慢了,CPU就提供了 L1,L2,L3的三季缓存,每次去主内存拿完

      数据后,数据就先存储到三级缓存,然后cpu再去三级缓存取数据,效率肯定会提升;

      三级缓存就是是每个线程的工作内存,是相互独立的。

              这就带来了一个问题:现在CPU都是多核,每个线程的工作内存(CPU三级缓存)都

      是独立的,会告知每个线程做修改时,只修改自己的工作内存,数据没有及时同步到主内存

      ,从而导致数据不一致的问题

     线程运行时数据处里过程如下:

             

     使用下边代码来验证数据可见性的问题,代码如下:

             

             

二、解决可见性问题的方式

       1、volatile 

             volatile是一个关键字,用于修饰成员变量

              如果属性被volatile修饰,相当于告诉cpu,对于当前属性的操作,不允许使用CPU

              缓存(即线程私有内存),必须去操作主内存。

              volatile的内存语义:

                    (1)volatile 属性被写:当写一个volatile变量,JMM会将当前线程的CPU缓存的

                                 数据及时刷新到主内存中

                    (2)volatile 属性被读:当读一个volatile变量,JMM会将当前线程对应的CPU缓存

                                 设置为无效,必须从主内存读取数据。

               其实变量加了volatile就是告诉cpu,对当前变量的读写操作,不允许使用CPU缓存;加

               了volatile 的变量会在编译成汇编之后追加一个lock前缀,CPU执行这个指令时,如果

                带有lock前缀会做2件事:

                       (1)将当处理器缓存行的数据写回到主内存

                       (2)这个写会的数据,在其他的CPU内核的缓存中,直接无效

                  总结:volatile 就是让CPU每次操作这个数据时,必须立即同步到主内存,以及从主内

                             存读取数据。

                             在代码中若先对volatile属性进行操作,则其他属性也是可见性的。

               针对上边的代码,采用volatile解决可见性问题实现如下:

/*******************************************************
 * 验证线程的可见性问题
 * 每个线程都有自己的私有内存,相互独立,线程运行时处里的是自己私有内存的数据
 *
 * 使用volatile解决内存可见性
 *******************************************************/
public class Test02 {

    private volatile static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            while (flag){

            }
            System.out.println("子线程 t1 中 flag改变");
        });

        t1.start();
        //预期:flag修改成false后,不影响线程t1的运行
        Thread.sleep(100);
        //修改flag 的值
        flag = false;
        System.out.println(" main 线程中修改 flag = false");
    }
}


//方案二:在代码中若先对volatile属性进行操作,则其他属性也是可见性的
public class Test02 {

    private volatile static int i= 0;
    private  static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            while (flag){
                //todo: 在代码中若先对volatile属性进行操作,则其他属性也是可见性的
                i++;
            }
            System.out.println("子线程 t1 中 flag改变");
        });

        t1.start();
        //预期:flag修改成false后,不影响线程t1的运行
        Thread.sleep(100);
        //修改flag 的值
        flag = false;
        System.out.println(" main 线程中修改 flag = false");
    }
}

       2、synchronized

              synchronized也是可以解决可见性问题。

              synchronized内存语义:

                      如果涉及到了synchronized 的同步代码块或者同步方法,获取资源之后,将内部

                      涉及到的变量从CPU缓存(线程私有内存)中移除,必须重新去主内存中取数据;

                      而且在释放锁之后,会立即将CPU缓存中的数据同步到主内存中。

              使用 synchronized 解决内存可见性 示例代码如下:

                           

/*******************************************************
 * 验证线程的可见性问题
 * 每个线程都有自己的私有内存,相互独立,线程运行时处里的是自己私有内存的数据
 *
 * 使用synchronized解决内存可见性
 *******************************************************/
public class Test03 {

    private  static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            while (flag){
                /*
                 * 问题:为什么这里synchronized 放在while循环里边,不放在外边?
                 *     因为线程 在获取到 synchronized 锁之后才会从主内存拿数据,若把synchronized 放在while外边
                 *     则只会从主内存拿一次数据,后边不能监听到变量 flag 变化
                 */
                synchronized (Test03.class){
                    //todo
                }

            }
            System.out.println("子线程 t1 中 flag改变");
        });

        t1.start();
        //预期:flag修改成false后,不影响线程t1的运行
        Thread.sleep(100);
        //修改flag 的值
        flag = false;
        System.out.println(" main 线程中修改 flag = false");
    }
}

       3、Lock

             Lock锁保证可见性的方式和synchronized 完全不同,synchronized是基于内存语义在获取

             锁和释放锁对CPU缓存做一个同步到主内存的操作。

             lock 锁是基于volatile实现的,lock 锁内部进行加锁和释放锁时,会对一个volatile修饰的属

             state做加减操作。

             如果对volatile属性进行写操作,CPU会执行带有lock前缀的指令,会将CPU缓存的数据立

             即同步到主内存,同时也会将其他非volatile属性页一起同步到主内存。还会将其他CPU缓

             存行 中这个volatile数据设置为无效,必须从主内存重新拉取。

             lock解决可见性示例代码如下:

                  

/*******************************************************
 * 验证线程的可见性问题
 * 每个线程都有自己的私有内存,相互独立,线程运行时处里的是自己私有内存的数据
 *
 * 使用lock解决内存可见性
 *******************************************************/
public class Test04 {

    private  static boolean flag = true;
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            while (flag){

                lock.lock();
                try {
                    //。。。。。
                }finally {
                    lock.unlock();
                }

            }
            System.out.println("子线程 t1 中 flag改变");
        });

        t1.start();
        //预期:flag修改成false后,不影响线程t1的运行
        Thread.sleep(100);
        //修改flag 的值
        flag = false;
        System.out.println(" main 线程中修改 flag = false");
    }
}

       4、final

             final本质上说并不能像synchronized和volatile 那种形式保证可见性,final修饰的属性在

             运行期间是不允许修改的,这样一来就间接保证了可见性。

             final与volatile不允许同时修饰一个属性,final修饰的属性不允许被修改,而volatile保证

             每次从主内存读取数据,并且volatile会影响一定性能,就不需要同时修饰。

              final与volatile同时修饰属性会报错,如下图所示:

                   

  • 22
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值