并发-Final

1. 背景

2. 介绍

2.1 final修饰类、方法、变量的作用

  • 修饰类:不可被继承;
  • 修饰方法:不能被覆盖(重写);
  • 修饰变量:一旦初始化,如果是基础类型,则不能被修改;如果是引用类型,则不能指向其它引用对象;

2.2 final重排序规则

  • 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序;
  • 初次读一个包含final域的对象引用,与随后初次读这个final域,这两个操作之间不能重排序;

2.2.1 写final域的重排序规则

  • JMM禁止编译器把final域的写重排序到构造函数之外;
  • 编译器会在final域的写之后,构造函数return之前,插入一个StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造器之外,但是不会限制非final域,从而导致可能的线程问题。屏障的插入也是看处理器,例如X86处理器就不会对写-写重排序,因此会被省略掉(linux中uname -a可以看处理器是不是X86,目前大部分应该都是了)。
  • 如果final域是一个引用对象,则在构造函数内对一个final引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序;

        写final域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通域不具有这个保障

2.2.2 读final域的重排序规则

  • 在一个线程中,初次读对象引用与初次读该对象包含的final域,JMM禁止处理器重排序这两个操作(注意,这个规则仅仅针对处理器)。编译器会在读final域操作的前面插入一个LoadLoad屏障。同样如2.2.1所示,因处理器的不同而决定屏障是否插入(初次读对象引用与初次读该对象包含的final域,这两个操作之间存在间接依赖关系。由于编译器遵守间接依赖关系,因此编译器不会重排序这两个操作。大多数处理器也会遵守间接依赖,大多数处理器也不会重排序这两个操作。但有少数处理器允许对存在间接依赖关系的操作做重排序(比如alpha处理器),这个规则就是专门用来针对这种处理器);

       读final域的重排序规则可以确保:在读一个对象的final域之前,一定会先读包含这个final域的对象的引用。在这个示例程序中,如果该引用不为null,那么引用对象的final域一定已经被A线程初始化过了

2.3 final可见性

       通过final域增加写和读重排序规则,只要对象是正确构造的(被构造对象的引用在构造函数中没有逸出),那么不需要使用同步(指lock和volatile的使用)就可以保证任意线程都能看到这个final域在构造线程中被初始化之后的值(之所以对其它线程可见,是因为StoreStore屏障刷新数据至主存,但是X86是省略了插入屏障,所以对其它线程不具备可见性了)。

2.3.1 发布

发布对象意味着该对象能在当前作用域之外的代码中被使用,比如,将类内部的对象传给其他类使用,或者一个非私有方法返回了该对象的引用等等。Java中强调类的封装性就是希望能合理的发布对象,保护类的内部信息。发布类内部状态,在多线程的环境下可能问题不大,但是在并发环境中却用可能严重地破坏多线程安全。

2.3.2 逸出

某个不该发布的对象被发布了,这种情况被称为逸出。

  • 构造函数中将this逸出

  • 内部的匿名类是隐私持有外部类的this引用的,这就无意中将this发布给内部类,如果内部类再被发布,则外部类就可能逸出,无意间造成内存泄漏和多线程安全问题。
public class ThisEscape3 {
    private final int var;
 
    public ThisEscape3(EventSource source) {     //注册事件,会一直监听,当发生事件e时,会执行回调函数doSomething
        source.registerListener(       //匿名内部类实现
            new EventListener() {
                public void onEvent(Event e) {            //此时ThisEscape3可能还未初始化完成,var可能还未被赋值,自然就发生严重错误
                    doSomething(e);
                }
            }
        );
        var = 10;
    }
    // 在回调函数中访问变量
    int doSomething(Event e) {
        return var;
    }
}

3. 源码

4. 实战

5. FAQ

5.1 我发现部分人都习惯方法内局部变量、入参变量都修饰为final,这是有什么作用吗,比如提升性能、助于GC回收、线程安全?

5.2 屏障禁止两侧的指令重排序,这两侧是邻近的两条指令吗?

5.3 X86处理器限制了写-写重排序,那么会限制写普通域变量重排序到构造函数之外吗?另外既然X86省略了final构造函数内的StoreStore屏障,只是限制了写-写重排序,那么final域仍然不会重排序到构造函数之外了?

5.4 this的逸出导致了内存泄漏,是影响了GC吗?

6. 参考资料

对象共享:Java并发环境中的烦心事 

什么是This逃逸?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值