Java Concurrency In Practice 第二章读书笔记

线程是CPU调度的最小单位,与进程不同,它们拥有相同的地址和fd描述符,操作系统的基本调度单元是线程。进程为线程提供了独立的地址(通过vm)和独立的资源,文件句柄,是实体单元。

总的来说,多线程的安全性问题主要是对于多线程之间的可变共享变量如何进行操作的问题。共享意味着可以由多个线程共同访问,可变意味着变量可以在这个过程中发生改变。所以可能会产生的竞争条件。必须使用同步来保证各个线程之间对于这些共享变量的协调问题。

不包含类的实例域或者静态域的方法是无状态的(stateless),由于方法内的局部变量是栈上的,栈是线程私有的,其他线程无法主动访问线程的栈。如果不主动发布一个局部变量到外部的话,那么该方法总是线程安全的,因为这些不发布的局部变量只在栈内部。如果方法想要发布局部变量到其他线程的话1.如果是一个基本类型的引用,由于方法之间的参数传递是值传递,外部方法只能得到这个基本类型的一个拷贝,Java的语言特性保证了该应用是线程安全的。因为其他对象无法实际访问到它(只是得到值的拷贝)。如果是指向对象的引用,那么通过参数传递外部线程可以得到在栈上的某个实际对象的应用,所以必须要进行必要的线程安全措施。

例如,来看一下书上的这个例子就可以明白什么是无状态了。因为这个线程没有引用外部线程的方法,亦没有将局部引用变量发布到线程之外,其service方法只有两个线程私有的局部变量BigInteger i 和 BigInteger[] factors ,所以这个线程一定是线程安全的。


@ThreadSafe

public class StatelessFactorizer implements Servlet {

public void service(ServletRequest req, ServletResponse resp) {

BigInteger i = extractFromRequest(req);

BigInteger[] factors = factor(i);

encodeIntoResponse(resp, factors);

}

}

这里,我们可以考虑一下StatelessFactorizer类在如果加入一个实例域count,会发生什么。考虑这样一件事,对于一个实例域来说,它并不是线程私有的,作为类的实例域,他被分配到线程们共享的堆上。对于线程来说,如果是一个PUBLIC的域,那么所有线程都拥有访问他的权限,而对于一个private变量来说,访问限制是在类层次上,不是对象层次上的。只要是调用了privat域所属类的方法的线程都可以访问它。

@NotThreadSafe 
public class UnsafeCountingFactorizer implements Servlet { 
 private long count = 0; 
 public long getCount() { return count; } 
 public void service(ServletRequest req, ServletResponse resp) { 
 BigInteger i = extractFromRequest(req); 
 BigInteger[] factors = factor(i); 
 ++count; 
 encodeIntoResponse(resp, factors); 
 } 
} 

现在问题出现了,主要在于count++这里,对于多个线程来说很可能出现竞争问题。最主要的是count++虽然看上去是一个操作,但他实际上由读取-自加-写回三步组成。所以很可能出现两个线程同时读取,同时自加,最后同时写回,这样count只自加了一次。对于这种问题通常称为竞争状态(Race-Condition)。必须提出一种手段,当线程在修改时不允许其他线程对这个共享域的访问,并且要保证一旦值发生了改变,所有线程都必须能看到这种改变,这就是原子性和可见性这同步的两大要素。

Java通过同步快的形式来实现内部锁,内部锁的对象是this自身。

1.内部锁。(Intrinsic Locks)


synchronized (lock) {

// Access or modify shared state guarded by lock

}

通过传入lock对象进入synchronized块来实现同步。当执行线程进入到同步块时自动获得内部锁对象。离开时自动释放。

2.重入(Reentrancy)

重入意味着java中的内部锁持有单位是进程本身(one lock per thread)而不是POSIX标准中的每一次方法调用(one lock per tinvocation).这意味着JVM将锁与线程以及计数相关联。当计数是零,锁被认为不被任何线程持有。当一个线程请求一个未被持有的锁时,此时如果计数是零,则JVM会记录此时的线程,并将计数写成1。如果不是零,那么JVM会检查这个进程是否是锁的持有者,如果是,JVM会允许对于锁的再次获取,并将计数自加,换句说JAVA允许持有锁的对象对于锁的重复进入。如果否,那么会吊起进程直到持有锁的进程释放锁再进行锁的分配。

下面来看一个例子,这个例子在基于调用的锁下回造成死锁。例如:在Widget的doSomething方法中调用子类的doSomething,此时父类的方法持有一个独立的锁,子类的方法也持有一个独立的锁,在子类的方法中我们调用了父类的方法,对于基于调用的锁来说,由于父类的锁的存在,在想要调用父类的方法时,子类会被挂起,由于父类在等待子类方法的结束,而子类由于父类的锁的存在被挂起,这样造成了这个进程无法再继续执行,陷入了死锁。


public class Widget {

public synchronized void doSomething() {

}

}

public class LoggingWidget extends Widget {

public synchronized void doSomething() {

System.out.println(toString() + ": calling doSomething");

super.doSomething();

}

}

3.性能和同步的折中。
通常情况下不需要对所有块都进行同步保护,这样会大大的降低性能,也违背了使用多线程的初衷。一般只在出现了共享变量,并对其进行了操作的块才需要添加同步,这样我们在性能和正确性上进行了折中。要注意的一点是不止是setter方法,getter方法也需要同步,才能保证读到的不是一个无效的值,这涉及到线程的可见性问题。一般编译器会对没有同步保护和没有volatile说明的变量进行优化到cache或者register,对于多核来说,不同线程属于不同的核,他们的cache和register内的值对于各个其他核是不可见的。如果一个setter方法改变了值,而其getter方法没有同步,那么其他线程可能读到的是一个setter方法之前的过期值。使用同步可以保证我读到的是最新的。当然这里也可以使用Volatile进行可见性声明。

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

推荐阅读更多精彩内容

  • 第一章:Java程序设计概述 Java和C++最大的不同在于Java采用的指针模型可以消除重写内存和损坏数据的可能...
    loneyzhou阅读 1,229评论 1 7
  • 本文出自 Eddy Wiki ,转载请注明出处:http://eddy.wiki/interview-java.h...
    eddy_wiki阅读 2,040评论 0 14
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,531评论 18 399
  • 一、多线程 说明下线程的状态 java中的线程一共有 5 种状态。 NEW:这种情况指的是,通过 New 关键字创...
    Java旅行者阅读 4,646评论 0 44
  • 一次回眸,一次凝睇 一阵沉默,一次笑语 一回欢聚,一回别离 当时说成是意外 而今谁踏过陈迹 人生如梦,日出惊鸿 日...
    雪高老师阅读 322评论 5 10