励精图治---Concurrency---共享对象

之前说线程安全就是对状态访问变量的管理。

状态访问变量的管理,主要是实现原子性和内存可见性。

什么是内存可见性?

我们的赋值操作,一般包含3个步骤 读取---修改---写入。

在没有同步的情况下,编译器,处理器以及在运行时的操作,都对执行顺序进行调整,这些调整往往会打乱应有的顺序,导致意想不到的问题。

通常JVM对这个顺序会有调整。比如一些常用的服务器程序。在开发调试阶段,我们会增加-server命令行。server模式的JVM比clien模式的JVM有更多的优化。导致很多在研发阶段不会出现的bug,在安装阶段却层出不穷。这就是因为不同的开发模式下,JVM的优化导致代码执行的顺序不一样。


什么是失效数据

对于在这个多线程环境下,未同步的操作,导致部分状态对象读取到错误的值,这些就是失效数据。

面向对象开发中,我们通常会将数据的获取通过get set函数进行,往往这些操作是未经过同步的。要修改这样函数调用,需要在函数名前添加synchronized关键字。


特殊的类型

long和double变量类型是64位的。JVM对于普通的long跟double型进行分解操作,分解成两个32位操作。

可想而知,当你利用这两种类型时,就存在race condition,要想解决这个问题,就必须将这两种类型的变量声明为volatile。

ps。除非处理器能够提供64位的原子操作。


加锁的意义

将原先执行的ABABAB操作,变得有序可循,我们看待代码块加锁应该要有互斥行为以及内存可见性的观念


更轻量级的锁volatile

volatile能很好的简化代码,在内存层面上的表现,volatile将变量从分内存提升到了主存中,保证了其内存可见性。任何有效更改都能及时的反映出来。

volatile的使用

原则上,volatile保证其自身的可见性。以及确保其引用的对象的可见性,以及标识一些程序生命周期的开始或者结束标识。

我们要注意volatile所标注的变量对象不会于其他状态变量被一起纳入不变性条件中。volatile不能被依赖,若有操作需要依赖这个volatile变量,那么就不能这么使用了。必须找其他的方式去实现。

volatile的意义在乎提升了变量所储存的位置。在于内存可见性。对原子性却并没有体现。


publish和escape

之所以存在线程安全问题,是因为我们胡乱的使用了其他对象内的变量\对象,publish和escape指的是两种共享行为。


何为安全的函数构造

很多文章说要慎防this的escape。

不要在构造过程中使this引用escape。这是为什么?因为在对象尚未构造完整之前就急启动它,这可能引发时许混乱。另外一个理由是this的泄漏,虽然你不用,但是依旧存在被引发的可能性。不经意的一笔可能给你带来很大的麻烦。要保持严谨性!

因此,在构造函数中创建线程并没有错误,但最好不要立即启动它,而是通过start或者inititalize启动它。

引用一段代码

public class SafeListener {

    private final EventListener listener;

    private SafeListener() {

        listener = new EventListener () {

            public void onEvent(Event e) {

        doSomething(e);

    }

        };

    }


    public static SafeListener newInstance(EventSource source) {

SafeListener safe = new SafeListener();

source.registerListener(safe.listener);

return safe;

    }

}


线程封闭

Ad-hoc线程封闭:将线程封闭对象保存的引用保存在公有变量中。要确定 !!!只有一个线程!!!对共享变量进行写入操作!!!

栈封闭:只能通过局部变量才能访问对象。局部变量有一个属性:封闭在执行线程中。其他线程无法访问这个栈。那么就是安全的。

ThreadLocal:1. 解决线程执行时要频繁访问,而不断重新分配的问题。

                           2. 让对象跟对象值关联起来。同时提供一个独立的副本。使得每次都get set都是安全的。


不可变的对象是线程安全的

有几种情况是线程安全的。

1.对象创建后,就无法更改的对象

2.对象所有域都是final的

3.对象是完整创建的,没有escape的

这里说的,不一定 只有final对象。如果,这个对象不能修改。也是线程安全的。

注. final类型的函数在内存上有一个特定的区域被分配。类似于一个主存上的数据


publish

未被完整publish的对象是没有完整性的是线程不安全的。

public class Holder {

private int n;

public Holder (int n) { this.n = n; }//#这里在发布n的时候,要先写入默认的值,在写入n。这样就存在race condition。一个间隔。足以导致错误

public void asserttSanity() {

if (n != n)

throw new AssertionError("This statements is wrong");

}

}


如何做到安全publish

1. 在静态初始化函数中初始化一个对象引用。类似factory方法

2. 将对象引用保存成volatile或者AtomicReferance对象中

3. 将对象引用保存成final

4. 将对象引用保存到synchronized中。有锁保护的域内就是安全的。

5. 活用jdk中现有的线程安全类:Hashtable, synchronizedMap, ConcurrentMap, Vector, CopyOnWriteArrayList, CopyOnWriteArraySet, synchronizedList, synchronizedSet,

 BlockingQueue, ConcurrentLinkedQueue等等。

6. 对象被发布后根本就不可能被更改

7. 也是最重要的一条。要熟知对象的 既定规则。知道怎么用,什么时候用,就不会犯错误。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值