《java并发编程实战》 第三章 对象如何共享

第三章 对象的共享

从第三章开始,(这本书就开始变态起来了),如何共享和发布对象,从而使它们能安全的被多个线程同时访问。

可见性

同步代码块和同步方法可以确保以原子方式执行操作,一种常见的误解,认为关键字synchronized只能用于实现原子性或者确定“临界区”。不仅是只有原子性功能,还有内存可见性,某个线程正在使用对象状态,而另一个线程正在同时修改该状态,有了内存可见性,两个线程之间的对象状态能及时同步更新。

 	public class NoVisibilty{
   
 		private static boolean ready;
 		private static int number;
 		private static class ReaderThread extends Thread{
   
 			public void run(){
   
 					while(!ready)
 					{
   
 						Thread.yield();
 						System.out.println(number);
 					}
 			}
 	}
 	public static void main(String[] args)
 	{
   
 		new ReaderThread().start();
 		number = 42;
 		ready = true;
 	}
}

由于重排序现象,ReaderThread 线程很有可能输出的number为0。

失效数据

失效数据更好的解释是:过时的数据。以上面NoVisibilty类为例,main主线程更改number值后,更改后的值只是停留在主线程的高速缓存中,在number值从高速缓存区更新至内存,再更新到ReaderThread 线程这段时间中,number的值相对于ReaderThread 就是过时的、失效的。失效数据可能导致一些令人困惑的故障,例如意料之外的异常、被破坏的数据结构、不精确的计算、无线循环等。
典型的失效数据出现的情况:

 @NotThreadSafe
 public class MutableInteger{
   
 	private int value;
 	public int get(){
   return value;}
 	public void set(int value){
   this.value = value;}
}

由于get和set都是在没有同步的情况下访问value,某个线程调用了set,另外正在调用get的线程可能看到更新后的value,也可能看不到。
正确的代码:

 @ThreadSafe
 public class MutableInteger{
   
 	@GuardedBy("this") private int value;
 	public synchronized  int get(){
   return value;}
 	public synchronized  void set(int value){
   this.value = value;}
}

最低安全性

当线程在没有同步情况下读取变量,可能会得到一个失效值,但是至少也是某个线程设置的值,而不是随机的。这种安全性保证也称为最低安全性,即至少读这个过程总是原子的、可靠的。最低安全性适用于绝大多数的变量,但是对非volatile类型的64位数值变量(double和long)例外。对于非volatile类型的64位数值变量(double和long)变量JVM会将64位的读、写操作分解成两个32位操作。当读取一个非volatile类型的long变量时,如果对该变量的读和写操作在不同的线程中,那么很有可能会读取到某个值的高32位和低32位。除非用关键字volatile或者锁保护起来。

volatile变量

当把共享变量声明为volatile类型后,编译器与允许时都会注意到这个变量是共享的,因此不会讲该变量上的操作与其他内存变量操作仪器重排序。相比于加锁机制,volatile不会使用执行线程堵塞,因此volatile变量是一种比sychronized关键字更加轻量化的同步机制。
volatile的典型用法:检查其他线程是否达到自己线程想达到的状态并标记。通常用作某个操作是否完成、终端、或者状态的标志。
例如:

volatile boolean wakeFlag;
...
	while(!wakeFlag)
	{
   .....}

但是需要注意!!
volatile变量只能保证可见性,并不能保证原子性。使用时不能使用count++这种操作,原子变量有提供自己的“读-改-写”操作方式。举个例子,A线程在执行count++。在读取count值时,B线程也执行了读取count操作,B线程也想执行count++操作 ,最后A B线程执行完后count也只加了1。当且仅当满足下面所有条件是,才可用volatile变量:

  1. 对变量读写操作不依赖变量本身当前值
  2. 该变量不会和其他状态变量一起纳入不变性条件中
  3. 在访问变量时不需要加锁

对象的发布

书上是这么定义:将一个指向该对象的引用保存至其他方法可以访问的地方,或者在某一个非私有的方法中返回该引用,或者将引用传递到其他类的方法中。
例如:将新建的HashSet<>对象的引用保存至其他代码可以访问的地方。

	public static Set<Secret> knownSecrets;
	public void initialize(){
   
		knownSecrets = new HashSet<Secret>();
	}

再例如:

对象的逸出

定义:某个不应该发布的对象被发布时,这种情况被称为逸出。
例如:在非私有的方法返回私有变量的引用,内部可变状态逸出了

   class UnsafeStates{
   
   private String[] states = new String[] {
   
   	"AK","AL",....
   };
   public String[] getStates() {
   return states;}
} 

例如:隐式的适用this引用逸出

   public	class ThisEscape{
   
   		public ThisEscape(EventSource source){
   
   			source.registerListener(
   			new EventListener(){
   
   			public void onEvent(Event e){
   
   					doSomething(e);
   					}
   			});
   	}
}

内部类在编译完成后会隐含保存一个它外围类的引用,"ThisEscape.this”,然而构造函数还没完成,ThisEscape在执行构造函数,其本身对象还没构造完,this引用就立刻间接被传递到其他类的方法中,这当然是不应该的,所以是隐式的逸出。正确的做法是构造函数返回时,this引用才能从线程中逸出,才能在该线程中被其他类的方法使用。

public class SafeListener{
   
   private final EventListener listener;
   private SafeListener(){
   
   listener  = new EventListener(){
   
   public void onEvent(Event e)
   {
   
   		doSomething(e);
   }; //在构造函数结束之前,外部类this的引用并没有被其他类的方法引用,并没有被发布,所以没有逸出
}
public static SafeListener newInstance(EventSource source){
   
   	SafeListener safe  = new SafeListener();
   	souce registerListener(safe.listener);
   	return safe;
   }
}

只有构造函数返回后,外部类的this才能被引用,此时外部类的对象已经构造完整。

线程封闭

当访问共享的可变数据一般都需要使用同步,避免使用同步的方式就是不共享数据,即在单线程中访问数据,这种技术称为线程封闭。线程安全性实现的最简单方式之一。

1、Ad-hoc线程封闭:

指维护线程封闭性的职责完全由程序实现承担,书中没有举例如何实现,一脸懵逼,鬼知道怎么怎么承担会非常脆弱。实际上没有一种特定的修饰符,可以将线程封闭到目标线程上。虽然有volatile变量上有一种特殊的线程封闭,但是是确保只有单个线程对共享的volatile变量进行写入操作,并不能封闭到指定的线程上。

2、栈封闭:

书中原话,只能通过局部变量才能访问对象。言下之意只用局部变量访问对象,而且这个对象也是单个线程中的局部对象关键在于确保某个对象只能由单个线程访问。书中举了个找animals中可能凑一对的数目,懵逼了半天看的我。自己的理解写的程序

public class StudentDao {
   
  public String 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值