本文参考《java并发编程实战》,java并发必读书籍
要编写正确的并发程序,关键问题在于:访问共享的可变状态需要正确的管理。
一、可见性(变量的更新操作通知到其他线程)
1.多个线程读写时对内存的可见问题。是因为java内存模型导致的。在之后的博文中会介绍内存模型
重排序是内存模型的一种表现
重排序:在没有同步的情况下,编译器、处理器以及运行时都可能对操作的执行顺序进行调整
2.所以共享必须 正确的同步
不同步会导致:产生失效数据
非原子的64操作就会得到失效值(备注:double/long, 在读写的时候会拆分成俩个32位的操作,俩个操作就有可能不同步,就会得到失效值)
3.加锁能保证可见性(不局限于互斥行为)
上篇博文讲过内置锁就能保证线程之间数据共享
4.volatile关键字
稍弱的同步机制,比sychronized更轻量级的同步机制
编译器与运行时都会关注这个变量是共享的,所以不会重排序,valatile变量不会缓存在寄存器或者其他处理器不可见的地方
volatile通常用于某个操作完成、发生中断或者状态的标志
5.加锁与volatile
加锁: 确保可见性 又可确保原子性
volatile:确保可见性 但是不能确保原子性
二、发布与逸出
发布(Publish):使对象能够在当前作用域之外的代码中使用。
比如:a.对象的引用保存在其他类中可以访问的地方,b.某一个非私有的方法中返回该引用,c.引用传递到其他类的方法中
逸出(Escape):某个不应该发布的对象被发布了。
发布方式
1.共有静态变量发布:public static Set ss;//ss发布了
2.通过方法发布私有变量(逸出了,因为私有变量是私有了,不应该发布)
private String[] states = new String[] {"AA", "BB"};
public String[] getStates[] {
return states;
}
3.内部类被发布(逸出了,内部类不应该发布)
4.最不容易发现的是this引用逸出。
不用在构造过程中使this引用逸出
一般使用工厂方法防止this指针逸出(可参考java并发实战)
三、线程封闭
含义:仅在单线程内访问数据
1.应用场景
Swing组件:封闭到Swing的事件分发线程中
JDBC(Java Database Connectivity)的Connection对象,一个连接对应一个线程
Spring事件机制
2.线程封闭的三种方式
方式一、Ad-hoc线程封闭
维护线程封闭的职责完全由程序来承担
方式二、栈封闭(也称为线程内部使用或者线程局部使用)
只能局部变量才能访问对象
同步变量也能使对象更容易封闭在线程中
方式三、ThreadLocal类(比较规范的方式,在框架中使用广泛)
ThreadLocal 提供get与set等接口为线程提供一份独立的副本,防止对可变的单实例变量或者全局变量进行共享
ThreadLocal<T> 等价于 Map<Thraad, T>,
举例
private static ThreadLocal<Connection> connectionHolder =
new ThreadLocal<Connection>() {
public Connection initalValue() {
return DriverManage.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
四、不变性
不可变对象一定是线程安全的
对象可变的条件:
1.对象创建以后其状态就不能修改
2.对象的所有域都是final类型
3.对象是正确创建的(在对象创建期间,this引用没有逸出)
编程习惯
所有域 尽量 private私有化
所有域 尽量 final
五、安全发布
任何线程都可以在不需要额外的同步的情况下安全地访问 不可变对象 , 即使在发布这些对象没有使用同步
对象的引用以及对象的状态安全发布模式方式
方式一:在静态初始化函数中初始化一个对象引用
方式二:将对象的引用保存到volatile类型的域或者AtomicReferance对象中
方式三:将对象的引用保存到某个正确构造对象的final类型域中
方式四:将对象的引用保存到一个由锁保护的域中
事实不可变对象:如果对象在发布后不会被修改,就是对象状态在发布后不会改变
在没有额外的同步的情况下,任何线程都可以安全发布的
可变对象:对象在构造后可以修改,只能确保“发布当时”状态的可见性
对象发布总结:
不可变对象可以通过任意机制发布
事实不可变对象:必须通过安全方式发布
可变对象:必须通过安全方式发布,并且必须是县残缺的或者由某个锁保护起来
并发程序中使用和共享对象时的策略:
线程封闭:只能由一个线程拥有,对象封闭在该线程中,只能在该线程修改
只读共享:任何线程都不能修改。共享只读对象包括不可变对象和事实不可变对象
线程安全共享:内部实现同步,通过对象的公有接口来进行访问而不需要进一步同步。
保护对象:只能通过持有特定的锁来访问。
保护对象包括封装在其他线程安全对象中的对象,以及已发布的并且由某个特定锁保护的对象。