并发编程实战:3.对象的共享

发布时间:2022-07-02 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了并发编程实战:3.对象的共享脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

3.对象的共享

同步可以确保以原子的方式实现操作,还可以确保内存可见性,即确保一个线程修改了对象状态后,另一个线程可以看见.

策略: 1.线程封闭 2.只读共享 3.线程安全共享 4.保护对象

可见性

即"内存可见性",当共享变量在某个线程中变化时,其他线程在使用该共享变量前能得到变化后的值,仿佛这个变量的一动一静所有线程都能察觉一样. synchronized 块是能保证块中的共享变量的可见性的。首先它要求写该变量的时候进行同步。它是如何做到可见性的呢?当变量在同步块中改变后,在退出同步块时会迫使该变量马上把修改后的值告诉主内存,并且更新所有线程中的拷贝。这样就保证了变量的可见性。

除了同步块,还有一种方式可使变量保持可见性:volatile 关键字。它的实现方式是迫使该关键字修饰的变量写之后马上更新到主内存,使用之前总会再从主内存中取最新值,通过这种貌似放弃线程私有拷贝的方式来保证可见性。

非原子的64位操作

非volatile的long,double变量,JVM会在读写中把它分解为两个32位操作,如果读写操作在不同线程中执行,那么就可能读到某个值的高32位和低32位.

赋值操作的步骤

1.分配变量空间 2.引用指向变量 3.初始化 (在非volatile的情况,指令重排导致2,3可倒序)

volatile

在一个多线程的应用中,线程在操作非volatile变量时,出于性能考虑,每个线程可能会将变量从主存拷贝到CPU缓存中。如果你的计算机有多个CPU,每个线程可能会在不同的CPU中运行。这意味着,每个线程都有可能会把变量拷贝到各自CPU的缓存中,意味着缓存和主存保存的变量可能不一样,,如下图所示:

并发编程实战:3.对象的共享

完整的volatile可见性保证

public class MyClass {
02
    PRivate int years;
03
    private int months
04
    private volatile int days;
05
 
06
 
07
    public void update(int years, int months, int days){
08
        this.years  = years;
09
        this.months = months;
10
        this.days   = days;
11
    }
12
}


days变量写入主存时,前面两个也会被写入. 读取volatile变量时,也同样会触发线程中所有变量从主存中重新读取。因此,应当尽量将volatile的写入操作放在最后,而将volatile的读取放在最前,这样就能连带将其他变量也进行刷新。(但是指令重排造成了例外) 局限性:只确保了可见性,并未确保原子性,应使用synchronized保证读写变量是原子的. 性能:读写volatile变量会导致变量从主存读写。从主存读写比从CPU缓存读写更加“昂贵”。访问一个volatile变量同样会禁止指令重排,而指令重排是一种提升性能的技。因此,应当只在需要保证变量可见性的情况下,才使用volatile变量。

发布与逸出

发布:对象能够在当前作用域之外的代码中被使用. 如果在对象构造完成之前就发布它,就会破坏线程安全性,如果不应该发布的对象被发布了,就被称为逸出.

线程封闭

一种避免使用同步的方式就是不共享数据,仅在单线程中访问数据,就不存在同步. 如JDBC中线程从连接池中获取一个connection对象,用完再还给连接池,这就隐含地把对象封闭再线程中.

栈封闭

"栈"是指java虚拟机栈,或者说是虚拟机栈中局部变量表部分。首先Java虚拟机栈是私有的,它的生命周期和线程相同。Java虚拟机栈描述的Java方法执行的内存模型:每个方法在执行时都会创建一个“栈帧”,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应这个一个栈帧在虚拟机中入栈到出栈的过程。

为什么局部变量是线程安全的呢?因为局部变量存放在虚拟机栈中,而虚拟机栈是线程私有的,既然线程不共享,所以它是线程安全的。封闭栈的线程安全性体现在Java虚拟机的内存特性。

ThreadLocal类

当多线程应用程序在没有协同的情况下使用全局变量时,就不是线程安全的,通过将对象保存到ThreadLocal中,每个线程都会拥有属于自己的对象。

ThreadLocal内部维护了一个Map,Map的key是每个线程的名称,而Map的值就是我们要封闭的对象。每个线程中的对象都对应着Map中一个值,也就是ThreadLocal利用MaP实现了对象的线程封闭。**ThreadLOcal 包含了Map<Thread,T>对象.

/** 线程封闭示例 */
public class Demo7 {
    /** threadLocal变量,每个线程都有一个副本,互不干扰 */
    public static ThreadLocal<String> value = new ThreadLocal<>();
 
    /**
     * threadlocal测试
     *
     * @throws Exception
     */
    public void threadLocaltest() throws Exception {
 
        // threadlocal线程封闭示例
        value.set("这是主线程设置的123"); // 主线程设置值
        String v = value.get();
        System.out.println("线程1执行之前,主线程取到的值:" + v);
 
        new Thread(new Runnable() {
            @override
            public void run() {
                String v = value.get();
                System.out.println("线程1取到的值:" + v);
                // 设置 threadLocal
                value.set("这是线程1设置的456");
 
                v = value.get();
                System.out.println("重新设置之后,线程1取到的值:" + v);
                System.out.println("线程1执行结束");
            }
        }).start();
        Thread.sleep(5000L); // 等待所有线程执行结束
        v = value.get();
        System.out.println("线程1执行之后,主线程取到的值:" + v);
 
    }
 
    public static void main(String[] args) throws Exception {
        new Demo7().threadLocalTest();
    }
}
# 结果
线程1执行之前,主线程取到的值:这是主线程设置的123
线程1取到的值:null
重新设置之后,线程1取到的值:这是线程1设置的456
线程1执行结束
线程1执行之后,主线程取到的值:这是主线程设置的123

说明主线程和子线程中的执行结果互不干扰.

不变性

用不可变对象来满足同步需求,它永远是安全的 不可变三个条件:

  • 创建以后状态不变
  • 所有域都是final类型
  • 正确创建的(没有this引用逸出)

安全发布

错误的发布对象:

私有成员变量在对象的公有方法中被修改。当其他线程访问该私有变量时可能得到不正确的值。

https://blog.csdn.net/MyySophia/article/details/104784202

安全发布的常用模式

  • 静态初始化函数中初始化一个对象的引用
  • 对象的引用保存到一个voliate类型的域或者AtomicReferance对象中
  • 保存到某个正确构造对象的final域
  • 保存到某个由锁保护的域

事实不可变对象:从技术上来看可变,但发布后不会改变,视为不可变对象即可,要通过安全方式发布 可变对象:必须通过安全方式来发布,并且一定要是线程安全且由锁保护

脚本宝典总结

以上是脚本宝典为你收集整理的并发编程实战:3.对象的共享全部内容,希望文章能够帮你解决并发编程实战:3.对象的共享所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。