您的位置:首页 > 编程语言 > Java开发

JAVA多线程(二)构建线程安全的类

2016-09-20 15:32 239 查看


构建线程安全的类


volatile

只保证数据可见性,不保证数据同步。也就是说,JVM只保证使用了volatile的数据变更后对所有线程暴露最新值,并不会对线程内部缓存数据的操作多限制。多线程同时变更某个共享的volatile数据并不会产生正确结果。


在满足以下条件时,可使用volatile:

1.写入变量时并不依赖变量当前值;或者能确保只有单一的线程修改变量的值。

2.变量不需要与其他变量共同参与不变约束。

3.访问变量时,没有其他原因需要加锁。


long和double

64位数据,分为上下32位两部分,其操作不是原子操作。


发布

一个对象能够被当前范围之外的代码所使用。

不要在构造函数中发布对象和启动线程。


逸出

一个对象在尚未准备好时就被发布了。此时类对象可能已经分配了空间(不为null),但是内部数据还未完成初始化。


安全发布模式

通过静态初始化器初始化对象引用;

将它的引用存储到volatile或者atomicReference;

将它的引用存储到正确创建的final域中,或者存储到由锁保护的域中。


安全共享对象

线程限制:一个线程限制的对象,被线程独占,且只能被占有它的线程修改(比如ThreadLocal类型数据)。

共享只读:一个共享的只读对象。

共享线程安全:一个线程安全的对象在内部进行同步,所以其他线程无需额外同步就可以访问。

被守护的(guarded):被锁保护的已发布对象。


线程安全的组合对象

制订同步策略(如何在不违反不可变约束的情况下,协调其他线程对对象的访问。)并文档化。


实例限制

对象被封装在另一个对象中(如hashset中的对象),把对被封装对象的访问限制转换为对封装对象(hashset实例)的封装数据访问路径的限制。


java监视器模式(java内部锁)

通过锁来实现。


委托线程安全

如果一个类由多个彼此独立的线程安全的状态变量组成,而且类的操作不包含任何无效状态的转换(比如check-then-act),可以讲线程安全委托给这些状态变量。


扩展线程安全的类

以组合对象的内部锁代替对待扩展对象的锁模拟。


避免活跃度风险


死锁


锁顺序死锁(lock-ordering deadlock)

如果所有线程以通用的固定顺序获得锁,就不会出现锁顺序死锁。

method(A,B)参数所代表的实例可能是同一实例但是顺序不同。比如转账,从我-他和从他-我时,锁顺序是不同的。


资源死锁(resource deadlock)

线程互相等待和持有对方所需的资源


协作对象间死锁

在持有锁的时候调用外部方法是在挑战活跃度问题。外部方法可能会获得其他锁(产生死锁风险),或者遭遇严重超时阻塞。当你持有锁的时候会延迟其他视图获得该锁的线程。


开放调用

不会产生锁问题。


避免和诊断死锁


尝试定时的锁

如果锁是在嵌套方法中获取的,你无法仅仅释放外层的锁。


通过线程转储(thread dump)分析死锁

unix:kill-3或者ctrl-\,windows:ctrl-break。jdk版本5下显示的lock不会转储,jdk版本6下会有粗略的信息


其他活跃度危险(、丢失信号、活锁)


饥饿(starvation)

无尽等待资源。


弱响应性

与其他线程竞争资源造成响应性不良。


活锁(livelock)

未发生死锁但是由于不断重复错误场景而造成无法响应,通过随机等待和撤回来避免。


性能和可伸缩性

避免不成熟的优化:首先使程序正确,然后再加快。


阿姆达尔定律(amdahl定律)

S=1/(1-A+A/N)

A为并行计算部分所占比例,N为并行处理节点数。

1-a=0(只有并行)最大加速比为N;

当a=0时(只有串行)最小加速比S=1;

当N趋近于无穷大时,计算加速比s->1/(1-A),也就是加速比上限。


线程引入的开销

切换上下文:竞争CPU和数据缓存

内存同步:存储关卡(memory barrier)指令-刷新缓存,使缓存无效,刷新硬件的写缓冲,并延迟执行的传递(存储关卡中,大多数操作不能被重排序)。

阻塞:被阻塞的线程可能自旋等待或者挂起,自旋更适合短期等待,挂起适合长期等待。

大多数通用处理器中,上下文切换开销相当于5k-10k个时钟周期,或者几微秒。


减少锁竞争(锁被请求频率*锁的持有时间)

减少持有锁的时间。

减少请求锁的频率或者协调机制取代独占锁,从而提高并发性。

缩小锁的范围:把与锁无关代码移出synchronized块,尤其是阻塞操作,如I/O操作。

减小锁的粒度:通过分拆锁(lock splitting)、分离锁(lock striping)实现。但是需要对容器进行加锁的独占的访问时,会更困难,比如ConcurrentHashMap的值需要扩展、重排时。

避免热点域

独占锁的替代:并发容器,读写锁, 不可变对象,原子变量。


检测CPU利用率

不充足的负载

IO限制:可以通过iostat或者perfmon判断一个程序是否受限于磁盘,或者通过网络监测是否受限于带宽。

外部限制:比如数据库、webservice等。

锁竞争。

对象池:需要考虑频繁创建对象带来的gc压力与池技术带来的锁切换之间的性能对比。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 多线程