跟着实例学习java多线程6-如何正确发布线程安全的对象

我们前面所讲的一切其实都只是为了一个目标那就是能正确发布一个线程安全的对象。

一:线程封闭

这个很好理解如果一个变量是在一个线程中完成的状态改变,那么这个变量肯定是线程安全的。

我们常使用的是栈封闭和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是一个可变状态的对象,所以我们采用线程安全共享的方式发布出来,针对它的操作一律采用公有接口提供操作。

掌握好这些发布策略我们就能更好的编写线程安全共享对象,同时也可以安全的实现并发任务的执行。



时间: 2024-10-24 11:49:48

跟着实例学习java多线程6-如何正确发布线程安全的对象的相关文章

跟着实例学习java多线程7-对象的组合发布

我们学习线程安全与同步的知识目的就是要实现一些可复用组件或编写出更大的程序. java中类是对象抽象,那么怎么实现一个线程安全类是我们必须要知道的并正确使用的技术. 在设计线程安全类的过程中,需要包含以下三个基本元素: 找出构成对象状态的所有变量. 找出约束状态变量的不变性条件. 建立对象状态的并发访问管理策略. package com.home.thread.thread7; import com.home.thread.safe; public class Counter { /** * @

跟着实例学习java多线程-2

上一篇文章我们通过一个实例来说明了并发编程为什么要做同步处理,下面我们再来巩固一下. 对象如果拥有可变状态的变量,并且被多线程访问,那么这个时候我们要对可变状态变量的状态改变做原子操作处理. 锁机制是保证这样的操作的一个有效的方法,它可以保证变量的状态在被更新时是在一个原子操作中进行的. java提供了一种内置锁机制来支持原子性:同步代码块(Synchronized Block). 同步代码块包括两个部分:一个是作为锁的对象引用,一个是作为由这个锁保护的代码块. 让我们在来回忆上一篇文章最后的问

跟着实例学习java多线程8-同步容器类的问题

我们知道java有很多线程安全的容器类,我们也知道如果把可变状态的管理交给这些线程安全类来管理就可以实现线程安全,但是我们还可能遇到不可想象的问题. 例如: package com.home.thread.thread8; import java.util.Vector; /** * @author gaoxu * 实践出真知! */ public class VectorQueue { private static VectorQueue instance; private static Ve

跟着实例学习java多线程9-定时任务实例

定时任务是我们经常遇到的业务场景,我们有很多的功能都需要这样的技术来实现,例如:定时获取一些数据push出去,定时处理一些清理任务,定时检查某个值等.那么我们该怎么实现,在实现中又该注意一些什么? 定时任务就是另开一个线程来执行,其实也是并发的一类,大家可能不好理解,说定时不就是到时间执行一下,怎么还会产生并发,这里主要是看两个指标,一是看执行频率,二是看每次执行的时间,如果执行频率高并且执行任务又会很耗时,那么这时候就形成了并发,当然还有一种情况那就是,定时的job中调用其它服务的方法,而正常

跟着实例学习java多线程4-内存可见性

前三篇我们主要说了多线程访问共享可变状态时需要进行正确的同步处理,保证同一时刻只有一个线程访问相同的数据,我们使用synchronized关键字来实现原子性操作. 今天我们在来认识一下同步的另一个重要方面:内存可见性,这个概念其实很好理解,就是保证在同一个时刻,共享可变状态对访问它的线程呈现出自己最新的状态变化. 我们经常遇到的情景是这样的,一个全局变量计数器,一个线程负责更新该数值,另一些线程获取这个值,那么可见性就是获取值的线程,可以获取到更新线程更新的最新的值. 让我们先来看一个例子,在没

跟着实例学习java多线程5-初识volatile变量

同步机制可以保证原子操作和内存可见性,但是同步机制对变量的访问性能是我们不得不考虑的问题,java语言提供了一种弱同步机制,volatile变量. 它的原理大致是这样的,当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将变量上的操作与其他内存操作一起重排序.volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量总会返回最新写入的值.(参考<java并发编程实践>一书) 让我们来看一个实例: package

跟着实例学习java多线程-3

同步代码块是一种有效实现操作原子性的方法,上一章我们讲了一些同步的原子操作的基础. 现在我们回忆一下上一章的两个问题. 1:不同的synchronized的写法有什么区别,又该怎么写创建线程的代码呢? 以class实例对象作为锁的写法 写法1 package com.home.thread; /** * @author gaoxu * */ public class SafeThread { @safe public void testPrint(){ synchronized(SafeThre

局部创建对象(不正确发布:当好对象变坏时)

局部创建对象(不正确发布:当好对象变坏时) 代码清单1: public class StuffIntoPublic { public Holder holder; public void initialize() { holder = new Holder(42); } } 代码清单2: public class Holder { private int n; public Holder(int n) { this.n = n; } public void assertSanity() { if

Java多线程并发09——如何实现线程间与线程内数据共享

本文将为各位带来 Java 阻塞队列相关只是.关注我的公众号「Java面典」了解更多 Java 相关知识点. 线程间数据共享 Java 里面进行多线程通信的主要方式就是共享内存的方式,共享内存主要的关注点有两个:可见性和有序性原子性.Java 内存模型(JMM)解决了可见性和有序性的问题,而锁解决了原子性的问题,理想情况下我们希望做到"同步"和"互斥".有以下常规实现方法: 将数据抽象成一个类 将数据抽象成一个类,并将对这个数据的操作作为这个类的方法,这么设计可以和