Java多线程学习之基础知识篇(对象的共享-可见性学习)

点击跳转>>>>>Java多线程学习之基础知识篇(线程的安全性)

引言:上篇博客写到了一些线程的基础知识,并且介绍了如何通过同步来避免多个线程在同一个时刻访问相同的数据.我们进行多线程学学其实就是为了更好的编写并发程序,这个关键就是:在访问共享的可变状态时需要进行正确的管理,这篇博客将介绍如何共享和发布对象,从而使它们能够安全地由多个线程同时访问.这也是线程安全类和通过java.util.concurrent类库来构建并发应用程序的重要基础.

第二章:对象的共享

2.1 可见性

  • 概述:

使用同步代码块和同步方法可以确保以原子的方式执行操作,但使用关键字synchronized不仅只能用于实现原子性或者确定临界区,还有一点就是:内存可见性
对于多线程,我们不仅希望可以防止某个线程正在使用对象状态而另外一个线程在同时的修改该状态,而且还希望一点:确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化.

  • 案例代码:
package com.offcn.service;

public class NoVisiblity {
    private static boolean ready;
    private static int number;

    private static class ReaderTherad extends Thread{
        public void run(){
            while (!ready){
                Thread.yield();
                System.out.println(number);
            }
        }
    }

    public static void main(String[] args) {
        new ReaderTherad().start();
        number=42;
        ready=true;
    }
}
  • 上述代码的输出结果看似会输出42,但实际却是0或者是没输出.

在这里插入图片描述

  • 定义:

可见性是一个复杂的属性,在没有同步的情况下,编译器,处理器以及运行时都可能对操作的执行顺序进行一些意想不到的调整.在缺乏足够同步的多线程程序中,要想对内存操作的执行顺序进行判断,几乎无法得出正确的结论.

  • 对于避免这些复杂问题的解决办法是:只要有数据在多个线程之间共享,就使用正确的同步.
  • 重排序(Reordering):Java内存模型里面允许编译器和处理器对指令进行重排序以提高运行效率,并且只会对不存在数据依赖的指令进行重排序

2.1.1 失效数据

  • 概述

当读取线程查看一个变量时,可能会得到一个已经失效的值,这就是失效数据
失效值可能不会同时出现,当一个线程可能获得某个变量的最新值,而获得另一个变量的失效值

  • 影响:对于实际生活中,食物过期几个小时也许还可以食用,只是味道差了些,但对于程序来说是很危险的.因为这可能会导致一些意料之外的异常,被破坏的数据结果,不精确的计算以及无限循环

2.1.2 非原子的64位操作

  • 最低安全性(out-of-thin-airsafety)概述

当线程在没有同步的情况下读取变量时,可能会得到一个失效值,但至少这个值是由之前某个线程设置的值,而不是一个随机值

  • 最低安全性适用于绝大多数变量,但有一个例外:非volatile类型的64位数值变量(double和long)
  • 原因:JVM对于非volatile类型的double和long变量,允许将64位的读操作或者写操作分解为两个32位的操作,因此当对该变量读操作和写操作在不同的线程中执行,就很有可能会读取到某个值的高32位和另外一个值的低32位

2.1.3 加锁与可见性

  • 概述

内置锁可以用于确保某个线程以一种可预测的方式来查看另外一个线程的执行结果

  • 案例:

当线程A执行某个同步代码时,线程B随后进入由同一个锁保护的同步代码块,在这种情况下可以保证,在锁被释放之前,线程A看到的变量值在线程B获的锁后同样可以由线程B看到

  • 作用:

加锁的含义不仅仅局限于互斥行为,还包括内存的可见性.为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步

2.1.4 Volatile

  • 概述:

Java语言提供的一种稍微弱的同步机制,volatile变量,用于确保将变量的更新操作通知到其他线程
当变量声明为volatile类型时,编译器和运行时都会注意到这个变量是共享的,因此不会重排序,并且被修饰的变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值

  • 对于volatile理解:

1.volatile修饰的变量的读写操作可以类似看成是分别执行get和set方法
2.在访问volatile修饰的变量时不会执行加锁操作,因此就不会执行线程阻塞,所以volatile变量是一种比sychronized关键字更加轻量级的同步机制
3.从内存角度来看,写入volatile修饰的变量相对于退出了同步代码块,而读取就相对于进入了同步代码块
4.我们不能过度依赖volatile变量提供的可见性.

  • 何时使用volatile?

1.对变量的写入操作不依赖变量的当前值,或者能确保只有单个线程更新变量的值
2.该变量不会与其他状态变量一起纳入不变性条件
3.在访问变量时不需要加锁

  • 正确使用方式:
方式一确保它们自身状态的可见性
方式二确保它们所引用对象的状态的可见性
方式三标识一些重要的程序生命周期事件的发生(初始化/关闭)
  • 总结:

加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值