Java并发编程——如何保证对象的线程安全

最近正在学习Spring源码部分,看到了有关保证对象线程安全的方法,就回头复习了相关Java并发编程的内容,并在这里整理出来归入Java并发编程栏目,当然,后续我还会持续更新有关Java并发编程栏目的内容,欢迎大家关注。

我们已经介绍了线程安全和同步的一些知识,如果要对一个线程安全的对象加以修改,那么还需重新判断是否是线程安全。所以我们这一章就从对象间组合的角度再来具体探讨线程安全。

设计线程安全的类

通过之前几节的学习我们已经知道,设计一个线程安全的类至少要满足三个步骤:

1. 找出构成对象状态的所有变量
2. 找出上述变量的约束性条件
3. 建立对象状态的并发访问策略
接下来我们用具体的代码将这些步骤对应起来,我们可以先设计一个普通的计数器:
(1)Count类的状态变量只有一个

class Count{
	private int value=0;
	
	public int getValue(){
		return value;
	}
	public int increase(){
		return ++value;
	}
}

(2)有关状态变量的有效性

public int increase(){
	//有效性具体就是计数值value不能小于0
	if(value<0)
		throw new IllegalStateException("value overflow")
	return ++value;
}

(3)增加类的并发访问

class Count{
	private int value=0;
	public synchronized int getValue(){
		return value;
	}
	public synchronized int increase(){
		if(value<0)
			throw new IllegalStateException("value overflow")
		return ++value;
	}
}

非线程安全的类

1、实例封装

若某个对象不是线程安全的,则可以通过实例封装在多线程中安全地使用以下是实例封装的三个步骤:
1. 把非线程安全对象作为类的私有成员
2. 把调用该对象的方法内部的对该对象的引用作为局部变量
3. 线程A要把对象传递到线程B,而不是两个线程共享这个对象

class StringList{
	private List<String> myList=new ArrayList<>();
	public synchronized void addString(String s){
		myList.add(s);
	}
	public synchronized void removeString(String s){
		myList.remove(s);
	}
}

ArrayList对象本身是不安全的,但对象声明myList确是private,而且访问它额两个方法addString()和removeString()都是synchronized,所以在使用myList对象时就是线程安全的。

以上这种方式称作是Java监视器模式:可变的对象可以封装在一个类中并声明为private,而在这个类中访问该可变对象的方法必须加锁。

2、往现有的线程安全类中添加功能

同样的,添加功能我们也有以下四个方法:

1. 修改源码
2. 继承原来线程安全的类
3. 扩展原来线程安全的类的方法
4. 线程安全类的组合

对于第一种方法,简单易行。但是更多时候我们是无法直接修改源码的,这时我们就必须在原来线程安全类的基础上加以修改了。

首先是继承原来线程安全的类,但我们已经知道,一个线程安全的类中的变量应是private,所以这就限制了我们直接去继承这个类。实际上,只有线程安全类中的变量是protected才方便我们继承这个类,但实际上这种情况是少之又少的。

然后就是扩展原来线程安全的类的方法

class Method_3{
	public List<E> list=new ArrayList<E>();
	//...
	public synchronized boolean putIfAbsent(E x){
		//...
	}
}

我们可以先假设确定list对象是线程安全的,但是对于这个增加新方法putIfAbsent()的类是线程安全的吗?答案是否定的,我们之前已经知道,synchronized关键字或者是内置锁是需要指定对象的,在这个例子中本来已经线程安全的list对象的内置锁指向的对象自然是它本身,但是,对于putIfAbsent()方法所指向的对象却不是list而是这个Method_3类对象。所以说,它们使用了不同的锁,putIfAbsent()方法对于list对象并不是原子性的,所以Method_3这个类并不是线程安全的。
那么对于这个问题该如何解决呢,其实我们可以用到客户端的加锁机制,具体来说就是对于使用对象X的客户端代码,使用X本身用于保护自己状态的锁来保护这段代码。

public class Method_3{
	public List<E> list=new ArrayList<E>();
	//...
	public boolean putIfAbsent(E x){
		sychronized(list){
			//...
		}
	}
}

而第四种方法线程安全类的组合则是对于方法二中不能继承线程安全对象的类来说的,具体做法是仍然继承原来线程安全的对象,然后在继承的子类的private域中实例化一个被final修饰的线程安全对象。

public class ImprovedList<T> extends List<T>{
	private final List<T> list;
	public ImprovedList(List<T> list){ this.list=list;}//构造器
	public synchronized boolean putIfAbsent(T s){
		//...
	}
}

上例中声明一个private的不可变对象list,而ImprovedList通过自身的内置锁增加了一层额外的锁,虽然这可能有轻微的性能损失,但这个Improved类却非常健壮。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值