Java Concurrency In Practice 第三章读书笔记

除了保证操作的原子性以外,同步还可以保证变量在不同线程之间的内存可见性。原子性和可见性共同构成了同步的两个核心要素。第三章主要讲述如何在线程之间安全的发布和共享变量。

首先可以通过书上的例子来看一下什么是可见性。

public class NoVisibility { 
 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; 
 } 
} 

表面上我们启动了两个线程,一个线程对ready flag进行无限期的检查,而另一个线程则对number和ready进行改变。从逻辑上说,我们可能会期待屏幕上打印出42.但是实际上由于可见性问题,另一个线程可能永远也看不到ready的值,因为JMM中线程都拥有自己的工作内存,并且只能对自己的工作内存进行存取,工作内存中保存的其实是主内存的一个副本。也有可能JVM会对指令进行重排序优化,尽管在单线程下会保证得到的最终结果是我们期待的逻辑结果(as-if-serial),但是其指令的执行过程不一定是声明的顺序。所以上面有可能是先对ready进行了置位,导致之后打印出了零。在多线程中如果没有进行同步或者volatile声明(在缺乏同步的程序中),就不能对指令的执行顺序抱有期待。

可见性问题会导致一个问题那就是其他线程得到的数据可能是已经过期的,但其他线程却一无所知。所以在可见性上java提供了volatile关键字来进行可见性的保证。如果将一个变量声明为volatile,jvm会在写入时将线程工作内存的最新值拷贝回主内存保证值是最新的,以及每次使用时都要刷新主内存的值,并且所有与volatile变量相关的语句都将禁止重排序。volatile虽然可以保证可见性,却不能保证操作的原子性。

总的来说。volatile作为更加轻量化的线程安全手段,适用的范围比同步更加有限。书上说:
1.写入的操作不应该依赖于当前的值,因为volatile无法保证原子性。
2.该变量没有被纳入其他变量的不变式关系之中,也就是说他是独立的。
3.访问时不需要加锁。

只有都满足上面三个条件时,volatile才适用。

发布指的是发布一个对象(实质上是发布对对象的引用)使得对象可以再当前作用域之外使用。发布是会破坏封装性的。当一个不应发布的对象被发布时就产生了溢出问题。发布一个对象到外部的最简单的方法是使用一个公有的static变量来保存发布对象。

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

通过一个公有的静态变量指向我们新建的HashSet,其他线程得到了新建HashSet的引用。

在发布对象时,可能会间接地发布其他对象。例如我发布一个hashset,那么hashset里面的值便也被间接的发布了。

有一种逸出是对于可变的private对象,private访问权限将这个对象封装到当前类,如果将其发布到外部便使得其他线程得到了关于一个我们希望是pirvate对象的引用,这违背了使用private原有的语义。

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

另一种比较隐晦的发布其他对象的例子是内部类,由于内部类中保存着对于outerclass的外部应用,当我发布一个innnerclass时会间接的发布外部类的引用,可能会造成意想不到的结果。

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

最后是动态的construct初始化过程,如果我们在constructor的构造过程中向外发布this引用,那么发布出去的对象很有可能是部分构造的,即使是发布代码是最后一行,由于重排序也有可能是部分构造的。所以对象的this引用只有在完成constructor的构造之后才可以发布。
一个比较常见的错误是在构造体内启动一个线程,由于线程是共享this引用的,所以启动的线程可以看到未完全构造好的对象。所以在线程的启动方法一般是start。

对于这种情况,作者的建议是如果要对外发布对象,那么使用静态的工厂方法来完成这个操作。

public class SafeListener { 
 
 private final EventListener listener; 
  private SafeListener() { 
 
 listener = new EventListener() { 
 
 public void onEvent(Event e) { 
  doSomething(e); 
  } 
  }; 
  } 
  public static SafeListener newInstance(EventSource source) { 
  SafeListener safe = new SafeListener(); 
 
 source.registerListener(safe.listener); 
  return safe; 
  } 
} 

还有其他技术可以完成线程安全。例如线程封闭将对象封闭在一个线程中,这样无需其他同步手段就是线程安全的。1.stack封闭.由于stack是线程私有的,被封闭在stack内的对象自然只有当前线程可以访问。2.使用ThreadLocal类。3.在线程内发布不可变对象,他们一定是线程安全的。满足下列三条的是不可变对象:1。对象创建之后就不可变。2.域都是final类型3.在对象构造的过程中其this引用没有逸出。

例如这种发布将public引用发布,由于不可见性,其他线程不一定可以看到最新的对象。静态初始化和动态初始化不同,静态初始化是JVM在类的load阶段初始化的,JVM可以保证内部的同步机制不出错。

// Unsafe publication 
public Holder holder; 
public void initialize() { 
 holder = new Holder(42); 
} 

但是我们可以知道的是,对于一个对象,即便将这个对象的引用发布到其他线程,它的状态也不一定是对于其他线程可见的。因为其他线程只能通过私有的对象公有API来访问对象。所以即便我们发布了一个对象到其他线程,那么通过同步API方法和同步发布过程(安全发布),同样可以让这个对象是线程安全的。~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容