我们前面所讲的一切其实都只是为了一个目标那就是能正确发布一个线程安全的对象。
一:线程封闭
这个很好理解如果一个变量是在一个线程中完成的状态改变,那么这个变量肯定是线程安全的。
我们常使用的是栈封闭和ThreadLocal类。
在java运行时内存区中有一个虚拟机栈,栈封闭说的就是这个栈,这个栈是线程私有的,它的生命周期与线程相同。虚拟机栈描述描述的是java方法执行的内存模型:每个方法被执行的时候会同时创建一个栈帧用于存储局部变量、操作数栈等。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中的从入栈到出栈的过程。
那么我们定义在被调用方法的内部的局部变量就被封闭在栈中了,这样的变量只要不将该变量的引用发布出去那么它一定是线程安全的。
public int getBadNum(List<String> paralist) { List<String> localList; int num = 0; localList = paralist; for(String t:localList){ if("bad".equals(t)){ ++num; } } return num; }
该方法中num无论如何都不会破坏栈的封闭性,我们也看到localList是一个局部对象,一个对象的引用指向了它,而它被封闭在线程中所以那个引用也会被封闭在线程中执行完成。但是如何一旦把localList对象发布出去那么封闭性将被破坏。其它外部方法会怎么使用它我们就不能确定了。
ThreadLocal变量我们在代码中很少使用了,因为它的特性会降低代码的可重用性,并在类之间引入隐含的耦合性,因此在使用时要格外小心,我们这里就不讲它的实例了。
二:不可变对象
不可变对象有以下几个条件。
对象创建以后其状态就不能修改。
对象的所有域都是final的类型。
对象是正确创建的。
事实上我们经常会使用这样的不可变对象在程序启动时对其进行初始化,然后多线程并发访问,这里最关键的是正确创建对象并初始化,所以一定是保证所有的对象不能发生逸出,通常的做法是在所有这样的变量初始化完成在启动并发访问线程,这一点一定要保证。
三:安全发布常用模式
1: 在静态初始化函数中初始化一个对象的引用。
public class MongoDBTools { protected Logger logger = LoggerFactory.getLogger(MongoDBTools.class); private MongoClient configClient; private DB configDB; private Map<String, MongoTemplate> shardMongoTemplateMap = new HashMap<String, MongoTemplate>(); private MongoDBTools() { init(); } private static class SingletionHolder { private static MongoDBTools appConfigTools = new MongoDBTools(); } public static MongoDBTools getInstance() { return SingletionHolder.appConfigTools; } @SuppressWarnings("deprecation") public void init() { } }
我们使用getInstance方法获得的就是利用static域初始化好的单例对象。
2: 将对象的引用保存到volatile类型的域或是AtomicReferance对象中。
这种方式有很大的局限性,一般只适用于只读共享模式。
3:将对象的引用保存到某个正确构造的对象的final类型域中。
这是方式我们经常会用但是如果是可变状态的对象的发布就一定要通过锁来处理了。
package com.uskytec.ubc.interf.webservice; import java.util.concurrent.ConcurrentHashMap; /** * @author gaoxu * */ public class ConcurrentHashMapInstance { static ConcurrentHashMap<?, ?> chm = null; /**获取ConcurrentHashMap的全局对象 * @return */ public synchronized static ConcurrentHashMap<?, ?> getCHM(){ if(chm==null){ chm = new ConcurrentHashMap<String, Boolean>(); } return chm; } }
利用java提供的ConcurrentHashMap并发对象可以实现线程安全共享。
4:将对象的引用保存到一个由锁保护的域中。
package com.uskytec.ubc.foundation.queue; import java.util.HashMap; /**路由cache * @author GaoXu * */ public class RouteCache { private static RouteCache instance = null; public synchronized static RouteCache getInstance() { if (instance == null) instance = new RouteCache(); return instance; } private final static HashMap<String,Object> routeCache = new HashMap<String,Object>(); /** * @param key * @param value * @return */ public synchronized int add(String key,Object value) { routeCache.put(key, value); return 1; } /** * @param key * @return */ public synchronized Object get(String key) { if (routeCache != null) return routeCache.get(key); return null; } } 实例中的routeCache是一个可变状态的对象,所以我们采用线程安全共享的方式发布出来,针对它的操作一律采用公有接口提供操作。
掌握好这些发布策略我们就能更好的编写线程安全共享对象,同时也可以安全的实现并发任务的执行。