失效链接处理 |
Java并发编程实践 PDF 下载
本站整理下载:
相关截图:
主要内容:
1.介绍
1.1线程和进程的区别
线程共享其所属进程的内存地址空间,同一进程中的线程可以访问相同的变量,从同一个堆中分配对象,这相对于进程间通信机制实现了很更好的数据共享,但必须有可靠的同步机制来保证。
2.线程安全
2.1什么是线程安全性
多个线程同时访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替咨询,并且不需要额外的同步及在调用方代码不必做其他的协调,这个类的行为仍然是正确的,那么这个类就是线程安全的。
无状态对象永远是线程安全的。
2.2原子性
I++; 实际上是“读-改-写”,这样的操作不是原子操作
2.2.1竞争条件
2.2.2懒加载的单例模式
检查在运行模式
结合双重检查来分析
原子操作:一个操作对于所有的操作(包括它自己)而已,要么没有执行,要么全部执行完成。
JDK1.5 java.util.concurrent.atomic包中包括了原子变量(atomic variable)类
对象引用的原子变量AtomicReference
为保护状态的一致性,要在单一的原子操作中更新相互关联的状态变量。
如果是多个分开独立的原子操作,那么线程安全性就无法保证了。那怎么呢?采用锁。
2.3锁
2.3.1内部锁
内置锁机制:synchronized块:锁对象的引用,以及这个锁保护的代码块。
对象锁:方法所在对象本身this
类锁:静态synchronized方法,从Class对象上获取锁
也叫监视器锁,Java每个对象都可以充当。它是一种互斥锁
对象的内部锁与它的状态没有任何关系。即使获得了某个对象的锁,也不能阻止其他线程访问这个对象。唯一的作用,就是避免另一个线程再获得该对象的锁。
尽量不要在方法上
2.3.2重进入(Reentrancy)
同一个线程多次获取同一个锁,JVM将记录锁的占有者,并将请求锁的次数递增+1
子类覆写父类synchronized方法doXXX(),并在方法内调用父类方法super. doXXX()时,就是内部锁的Reentrancy特性,避免了死锁。
当一个线程重新获取锁,读写锁或其他不可重入的同步器时,就可能发生重入锁死。可重入的意思是线程可以重复获得它已经持有的锁。Java的synchronized块是可重入的。
如果一个线程持有某个管程对象上的锁,那么它就有权访问所有在该管程对象上同步的块。这就叫可重入。若线程已经持有锁,那么它就可以重复访问所有使用该锁的代码块。
下面这个锁的实现是不可重入的:
public class Lock{
private boolean isLocked = false;
public synchronized void lock()
throws InterruptedException{
while(isLocked){
wait();
}
isLocked = true;
}
public synchronized void unlock(){
isLocked = false;
notify();
}
}
如果一个线程在两次调用lock()间没有调用unlock()方法,那么第二次调用lock()就会被阻塞,这就出现了重入锁死。
避免重入锁死有两个选择:
编写代码时避免再次获取已经持有的锁
使用可重入锁
2.4用锁来保护状态
对于每个可被多个线程访问的可变状态变量,如果所有访问它的线程在执行时都占有同一个锁,我们称这个变量是由这个锁保护的。
锁协议:常见方式,在对象内部用内部锁来保护所有可变状态,使对象变成线程安全。
注意:即使将类中每个方法都声明为synchronized,也不能保证对对象的复合操作是原子的。因为复合操作中,每个原子操作之间是有空隙的。
2.5活跃度与性能
当使用锁的时候,必须清楚代码的功能是否和很耗时,如:运算密集型操作、可能阻塞的IO操作等。耗时操作,最好不要占有锁。
3.共享对象
3.1可见性
重排序(reordering):在没有同步的多线程环境,Java存储模型允许编译器重排序操作,在寄存器中缓存数值,允许CPU重排序,并在处理器中持有缓存中的缓存数值。这会导致我们意想不到的结果。详见16章。
|