1、对象的发布和逸出
发布(publish)对象意味着其做用域以外的代码能够访问操做此对象。例如将对象的引用保存到其余代码能够访问的地方,或者在非私有的方法中返回对象的引用,或者将对象的引用传递给其余类的方法。
为了保证对象的线程安全性,不少时候咱们要避免发布对象,可是有时候咱们又须要使用同步来安全的发布某些对象。
逸出即为发布了本不应发布的对象。
使用静态变量引用对象是发布对象最直观和最简单的方式。例如如下代码示例,在示例中咱们也看到,因为任何代码均可以遍历咱们发布persons集合,致使咱们间接的发布了Person实例,天然也就形成能够肆意的访问操做集合中的Person元素。java
package com.codeartist; import java.util.HashSet; public class ObjectPublish { public static HashSet<Person> persons ; public void init() { persons = new HashSet<Person>(); } }
在非私有的方法内返回一个私有变量的引用会致使私有变量的逸出,例如如下代码安全
package com.codeartist; import java.util.HashSet; public class ObjectPublish { private HashSet<Person> persons= new HashSet<Person>(); public HashSet<Person> getPersons() { return this.persons; } }
发布一个对象也会致使此对象的全部非私有的字段对象的发布,其中也包括方法调用返回的对象。
在构造函数中使用直接初始化或者调用可改写的实例方法都会致使隐式的this逸出也是常常发生的事情,例如如下代码,在EventListener的实例中也经过this隐含的发布了还没有构造完成的ConstructorEscape实例,可能会形成没法预知的结果。多线程
package com.codeartist; public class ConstructorEscape { public ConstructorEscape(EventSource eventSource) { eventSource.registerListener( new EventListener(){ public void OnEvent(Event e) { doSomeThing(e); } } ); } }
咱们可使用工厂方法防止隐式的this逸出问题,例如如下代码并发
package com.codeartist; public class ConstructorEscape { private final EventListener listener; private ConstructorEscape() { this.listener= new EventListener(){ public void OnEvent(Event e) { doSomeThing(e); } }; } public static ConstructorEscape getInstance(EventSource eventSource) { ConstructorEscape instance = new ConstructorEscape(); eventSource.registerListener(instance.listener); return instance; } }
2、避免对象发布之线程封闭
线程封闭可使数据的访问限制在单个线程以内,相对锁定同步来讲,其实实现线程安全比较简单的方式。
java提供了ThreadLocal类来实现线程封闭,其可使针对每一个线程存有共享状态的独立副本。其一般用于防止对可变的单实例变量和全局变量进行共享,例如每一个请求做为一个逻辑事务须要初始化本身的事务上下文,这个事务上下文应该使用ThreadLocal来实现线程封闭。
栈封闭是线程封闭的特例,即数据做为局部变量封闭在执行线程中,对于值类型的局部变量不存在逸出的问题,若是是引用类型的局部变量,开发人员须要确保其不要做为返回值或者其余的关联引用等而被逸出。
3、避免对象发布之不变性
某个对象建立以后就不能修改其状态,那么咱们就说这个对象是不可变对象。
因为多线程操做可变状态会致使原子性、可见性一系列问题,因此线程安全性是不可变对象与生俱来的特性。
不可变对象由构造函数初始化状态,并能够安全的传递给任何不可信代码使用。
全部字段标记为final的对象,因为引用字段的对象可能能够直接修改,因此其并不必定是不可变对象,其须要知足如下条件
对象的全部字段都用final标记
对象建立以后任何状态都不能修改
对象不存在this隐式构造函数逸出函数
package com.codeartist; import java.util.HashSet; public class ObjectPublish { private HashSet<Person> persons= new HashSet<Person>(); public ObjectPublish() { persons.add(new Person("wufengtinghai")); persons.add(new Person("codeartist")); } public Person getPerson(String name) { Person result = null; for(Person p : this.persons) { if(p.name == name) { result = p.Clone(); } } return result; } }
4、对象的安全发布
不少时候咱们是但愿在多线程之间共享数据的,此时咱们就必须确保安全的发布共享对象。
要安全的发布一个对象,对象的引用以及对象的状态对其余线程都是可见的,一个正确构造的对象能够经过如下方式安全的发布
在静态构造函数中初始化对象引用
使用volatile和AtomicReferance限定对象引用
使用final限定对象引用
将对象引用保存到有锁保护的字段中
1.不可变对象
任何线程均可以在不须要同步的状况下安全的访问不可变对象及其final字段,若是字段的引用能够改变则须要进行同步。
不可变对象在确保初始化安全的前提,能够自由的发布
有时为了确保不可变对象对于多个线程呈现一致的状态,须要使用同步不可变对象的初始化。
须要具有以上说的三个不变性限制条件。
2.隐式约定不可变对象
实际能够改变对象在发布后不会再改变状态,则此对象成为隐式约定不可变对象。
虽然任何线程均可以无需同步便可安全的访问隐式约定不可变对象,可是因为其自己仍是可变的,因此其须要以安全方式进行发布。
3.可变对象
须要使用同步来确保发布和共享的安全性。
5、对象的安全共享
为了确保使用共享对象的安全性,咱们须要遵循其既定的规则(例如是不是不可变对象)来肯定咱们访问对象的方式
不可变对象和隐式约定不可变对象能够直接由多线程并发访问。
线程封闭对象只能由建立线程持有并修改。
本身内部实现安全性的线程安全对象也能够直接由多线程访问。
通常的可变对象只能经过持有锁进行同步来实现安全共享。
this