本文共 5590 字,大约阅读时间需要 18 分钟。
java内存模型要求变量的读取操作和写入操作都必须是原子操作,但对于非volatile类型的64位数值变量(double,long),jvm允许将64位的读操作或写操作分解为两个32位的操作。当读取一个非volatile类型的long变量时,如果对该变量的读操作和写操作在不同的线程中执行,那么很可能会读取到某个值的高32位和另一个值的低32位。因此,即使不考虑失效数据问题,在多线程程序中使用共享且可变的long和double等类型的变量也是不安全的,除非使用volatile来声明他们或者用锁保护起来。
加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性。
把变量声明为volatile类型后,编译与运行时都会注意到这个变量是共享的,因此不会讲该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存到寄存器或者对其他处理器不可见的地方,因此在读取volatile变量时总会返回最新写入的值。
比如,while循环的条件变量,如果监视是否改变,需要设置为volatile,否则访问while的线程会将asleep放到工作区,一直循环直到某刻去内存读取。
1 2 3 4 | volatile boolean asleep; ... while (!asleep) countSomeSheep(); |
发布(Publish)一个对象是指:使对象能够在当前作用域之外的代码中使用。
在许多情况,我们要确保对象及其内部状态不被发布。而在某些情况,我又需要发布该对象,但如果在发布时要确保线程安全性,则可能需要同步。发布内部状态可能会破坏封装性,使线程难以维持不变的状态。例如,如果在对象构造完成之前就发布该对象,就会破坏线程安全性。
发布对象的最简单的方法是将对象的引用保存到一个共有的静态变量中。
逸出(Escape):当某个不应该发布的对象呗发布时。
1 2 3 4 | class UnsafeStates{ private String[] states = new String[]{ "as" , "dsf" ,...}; public String[] getStates(){ return states;} } |
如果按上述的方式发布states,则任何调用者都可以修改数组,这是不安全的。
上述构造函数好像没啥问题,至少我看本书之前看不出。构造函数中的对象是this,其他譬如引用类属性的拥有者为this,如果将这哥匿名类传递给source,source如果对其进行了引用,而这时候构造函数还没结束即没有创建ThisEscape的对象,这个匿名类也还没构造,即空。当且仅当对象的构造函数返回时,对象才处于一个可预测和一致的状态。
参考http://bruce008.iteye.com/blog/1461345
文章指出,匿名内部类编译的结果表明ThisEscape这个类会作为一个final成员变量放到匿名内部类中,即这个ThisEscape被逃逸出去了。因此不要再构造函数中发布匿名类和起线程。
当某个对象封闭在一个线程中时,这种方法将自动实现线程安全性,即使被封闭的对象本身不是线程安全的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public int loadTheArk(Collection<Animal> candidates){ SortedSet<Animal> animals; int numPairs = 0 ; Animal candidate = null ; //animals被封闭在方法中,不要使他们逸出 animals = new TreeSet<Animal>( new SpeciesGenderComparator()); animals.addAll(candidates); for (Animal a:animals) { if (candidate== null || !candidate.isPotentialMate(a)) candidate = a; else { ark.load( new AnimalPair(candidate,a)); ++numPairs; candidate = null ; } } return numPairs; } |
如果在线程内部(Within-Thread)上下文中使用非线程安全的对象,那么该对象仍然是线程安全的。但要注意后期维护的时候对象逸出。
维持线程封闭性的一种更规范方法是使用ThreadLocal。ThreadLocal对象通常用于防止对可变的单实例变量(Singleton)或全局变量进行共享。比如,在单线程应用中可能会维持一个全局的数据库连接,并在程序启动时初始化这个连接对象,从而避免在调用每个方法时都要传递一个connection对象。由于jdbc的连接对象不一定是线程安全的,因此当多线程应用程序在没有协同的情况下使用全局变量时,就不是线程安全的。通过将jdbc的连接保存到ThreadLocal对象中,每个线程都会拥有属于自己的连接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>(){ public Connection initialValue(){ try { return DriverManager.getConnection( "url" , "username" , "password" ); } catch (SQLException e) { e.printStackTrace(); } return null ; } }; public static Connection getConnection(){ return connectionHolder.get(); } |
当某个频繁执行的操作需要一个临时对象,例如一个缓冲区,而同时又希望避免在每次执行时都重新分配该临时对象,就可以使用这项技术。
当某个线程初次调用ThreadLocal.get方法时,就会调用initialValue来获取初始值。
ThreadLocal变量类似于全局比阿娘,它能降低代码的可重用性,并在类之间引入隐含的耦合性,因此在使用时要格外小心。
满足同步需求的另一种方法是使用不可变对象(Immutable Object)。如果某个对象在被创建后其状态就不能修改,那么这个对象就成为不可变对象。
正如除非需要更高的可见性,否则应将所有的域都声明为私有域是一个良好的习惯,除非需要某个域是可变的,否则应将其声明为final域也是一个良好的编程习惯。
在某些情况下,我们希望在多个线程间共享对象,此时必须确保安全地进行共享。以下是错误的:
1 2 3 4 5 | //不安全的发布 public Holder holder; public void initialize(){ holder = new Holder( 22 ); } |
看起来应该没什么问题才对,实际上多线程访问的时候可能得到一个尚未创建的对象。
再详细些:
1 2 3 4 5 6 7 8 9 | public class Holder{ private int n; public Holder( int n){ this .n = n} public void assertSanity(){ if (n!=n){ throw new AssertionError( "This statement is false." ); } } } |
除了创建hoder的线程,其他线程获取holder的状态是不一定的。也许是创建前获得为null,也许是创建了还没赋值,等等。这是不安全不正确的发布。
要安全的发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全地发布:
- 在今天初始化函数中初始化一个对象引用。
- 将对象的引用保存到volatile类型的域或者atomicReferance对象中
- 将对象的引用保存到某个正确构造对象的final类型域中
- 将对象的引用保存到一个由锁保护的域中
对象的发布需求取决于它的可变性:
- 不可变对象可以通过任意机制来发布。
- 事实不可变对象(对象从技术上来看是可变的,但其状态在发布后不会再改变)必须通过安全方式来发布
- 可变对象必须通过安全方式来发布,并且必须是线程安全的或者由某个锁保护起来。
当获得对象的一个引用时,你需要知道在这个引用闪个可以执行哪些操作。在使用它之前手否需要获得一个锁?是否可以修改它的状态,或者只能读取它?许多并发错误都是由于没有理解共享对象的这些“既定规则”而导致的。当发布一个对象时,必须明确地说明对象的访问方式。
在并发程序中使用和共享对象时,可以使用一些使用的策略,包括:
- 线程封闭:线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只能由这个线程修改。
- 只读共享:在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,但很任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象。
- 线程安全共享:线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步的同步欧。
- 保护对象:被保护的对象只能通过持有特定的锁来访问。保护对象包括封装在其他线程安全对象中的对象,以及已发布的并且由某个特定锁保护的对象。
本文转自Ryan.Miao博客园博客,原文链接:http://www.cnblogs.com/woshimrf/p/5238827.html,如需转载请自行联系原作者