多线程的一些问题

来源:互联网 发布:mac用pe安装win7系统 编辑:程序博客网 时间:2024/06/05 12:02

保证线程安全的基本知识:

读写失效:

多线程操作一个变量时,仅仅对set操作进行同步是不够的,必须对get操作也进行同步,这是因为仅对set进行同步无法保证变量的可见性。即两个线程分别对一个变量进行读操作和写操作时,可能读到失效值。而且jvm可能对程序进行重新排序,影响多线程的逻辑。

double和long是64位变量,在进行读写操作时会被分成两个32位进行,所以不是原子操作。所以多线程中使用这种类型必须添加volatile

volatile保证了内存可见性,当满足以下条件时可以用:访问该变量时不需要加锁,变量不会与其他变量一起纳入不变性条件中,对变量的写操作不依赖当前值(或者只有一个线程进行写操作)


逸出:

一个对象(类)中的任何域不论以什么方式发布出去,可以被外界访问到,都是逸出。包括公有的方法,添加到容器中,甚至是方法中的内部类调用了该对象的方法。这些都会导致外部可以间接的访问到对象中的域,使其线程不安全。逸出可以传递(逸出的域中如果有公有的域或者方法,这些也会被发布)


线程封闭:

1.栈封闭    :  在方法中使用局部变量来达到线程封闭。基本类型不会逸出,引用类型需要注意不要逸出。

2.ThreadLocal类  


不变性:利用不可变对象和volatile来控制并发。不可变对象是线程安全的,但是也是可以更新的,更新方法是用新的对象替换老的。这时用volatile来控制可见性就可以达到目的。


利用一些组件实现线程安全:

在实时线程安全时,需要考虑先验条件,后验条件,不变性条件(属性之间的依赖)。一旦发布一个对象后,这个对象的所有权将会转移或者被共享。


对象的封装:将一个对象a封装到另一个对象b中,a的所有访问途径必须通过b来实现。b中可以利用锁的同步策略实行同步。因此即使a不是线程安全的,但是b是线程安全的,也可以保证a的并发安全性。前提是a不能被发布。

委托:当类中的状态全部由线程安全的类管理时,如果各个状态彼此独立不存在依赖,则这个类也是线程安全的。 但如果状态之间存在不变性条件,则需要添加同步策略。

 在现有的线程安全的类上添加方法:1.扩展 : 新的类封装旧的安全类,同时添加新的方法,注意新方法的锁应该用封装的类的锁      2.组合 : 与原来的类实现同样的接口,方法的实现均调用旧类的方法,但在新的方法上添加对象锁,同时自己新实现的方法也要添加对象锁(组合并不关心底层是不是线程安全的)  


并发容器

迭代器一定要加锁,否则会出现concurrentModificationException


几种控制并发流程的工具  1.闭锁CountDownLatch  2.FutureTask  3.信号量Semaphore


死锁

嵌套的获得两个锁,如果顺序不当会引发死锁。如方法一和方法二分别以相反的顺序获得锁1和锁2,那么在两个线程同时调用这两个方法时会死锁。

动态的顺序也有一定几率出现死锁。如果两个线程分别以相反的参数调用同一个方法,每个参数都有一个锁,有可能死锁。解决方案是利用hashcode值以固定顺序获取锁。

如果出现hashcode相等的情况,可以在类中添加一个额外的对象(加时锁),用来表示特殊情况,这时方法需要获取三个锁

这种情况不光会发生在一个方法上,还有可能出现在两个对象协作的时候。第一个对象调用自身的同步方法,方法中需要掉到第二个对象的某个同步方法。第二个对象同样,这样就可能出现死锁。

线程饥饿死锁:线程池    有界线程池/资源池 不能与相互依赖的任务一起使用

开放调用:调用某个方法时不需要持有锁。  尽可能使用开放调用,使用同步代码块减小同步的代码范围


避免死锁的方法:

1.开放调用  2.获取多个锁时固定顺序  3.可以设置超时的显式锁  


其他的影响性能的问题:活锁 (信道发包冲突,会随机延迟一段时间防止再一次冲突) 饥饿


显示锁Lock

所有的加锁和解锁都是显式的,内存可见性语义与内置锁相同。但性能,算法,顺序等可能不同。

内置锁必须在获取锁的代码块中释放,无法中断一个正在获取锁的请求。Lock实现了一种更为灵活的加锁机制。

一定要在finally中释放锁,否则该锁将永远无法释放。还要做好异常处理。这是Lock比同步代码块危险的地方。

利用tryLock()实现轮询锁和定时锁,tryLock可以添加时间作为参数  

lock.lockInterruptubly()是一个可中断的获取锁的操作,抛出InterruptedException.

ReentrantLock与内置锁在java6以后性能近乎相同

锁可以是公平的或者非公平的,默认非公平。非公平的锁比公平锁有更好的性能,原因在于非公平锁减少了线程挂起以及唤醒的开销。在公平的前提下,每个线程在获取一个被占有的锁时都会先挂起排队,最终被唤醒。而非公平的锁可以插队,减少了挂起以及唤醒的几率。

除非synchronized不能满足需求,一般情况下优先级大于显示锁    前者是JVM内置属性,可以执行一些优化  而且显示锁操作更易于出错


读写所ReadWriteLock  当锁的持有时间比较长,而且大部分操作都不会修改被守护的资源时,读写锁有较高的性能。











原创粉丝点击