目录
多线程学习系列 - 1 - Single Threaded Execution Pattern
多线程学习系列 - 2 - Immutable Pattern
在android里面多线程是非常普遍的
之前工作中并不涉及太多多线程的问题,所以也就一直没有系统学习过
这个系列的学习选的书为《java多线程设计模式》-结城浩 著,博硕文化 译
一提到设计模式,大家可能有种种想法,但是不管大家怎么想,我希望您能先简单读读这本书
我觉得它讲的通俗易懂,例子也比较有代表性,分析的很全面,还有课后习题及答案
最重要的一点,它确实很实用,废话不多说了
学习过程中笔记也肯定会有些不严谨的地方,希望大家在踩我的时候能加上几句指导的话语,在此笔者感激不尽
书中最开始讲了一些简单的线程知识,这里也就不做整理了
书中以例子为主,我觉得效果还不错,所以我的笔记也以书中例子为主
第一章:Single Threaded Execution Pattern
考虑这样一个问题:
去某些公司面试的时候进出需要安检,一个门一次只能允许一个人通过,通过的时候工作人员对你进行身份识别。
如果多个人一起,那么工作人员所掌握的信息很有可能变得混乱。下面我们来看看工作人员的抱怨
假设有三个Alice,Boddy,Chris,他们分别来自Alaska,Brazil,Canada
那么我们先以一种简单的方式来做一个简单的检查,如果名字的首字母=国家的首字母,那么我们就认为检查通过
public static class Gate{
private int counter = 0;
private String name;
private String address;
public void pass(String name, String address){
this.counter++;
this.name = name;
this.address = address;
check();
}
private void check(){
if(this.name.charAt(0) != this.address.charAt(0)){
System.out.println("broken:" + toString());
}
}
public String toString(){
return "No. " + this.counter + ":" + this.name + "," + this.address;
}
}
大门对通过的人员进行信息记录并检查,如果出错则会打印错误信息,注意这里的pass方法可以改变Gate一些属性的状态
下面的类是捣乱的人
public static class UserClass extends Thread{
private final Gate gate;
private final String name;
private final String address;
public UserClass(Gate gate, String name, String address){
this.gate = gate;
this.name = name;
this.address = address;
}
public void run(){
while(true){
gate.pass(this.name, this.address);
}
}
}
这个人会不断反反复复的经过大门,同时告诉大门自己的信息
最后来看看程序运作
public static void main(String[] args) {
Gate gate = new Gate();
new UserClass(gate, "Alice", "Alaska").start();
new UserClass(gate, "Boddy", "Brazil").start();
new UserClass(gate, "Chris", "Canada").start();
}
执行,结果让人很不满意
基本上马上就会有错误信息提示,log我就不贴了。。。
原因其实很简单,我觉得书中的表格不错
所以还是借来
线程Alice |
线程Bruce |
this.name的值 |
this.address的值 |
this.counter++;
this.name = name;
this.address = address;
check();
|
this.counter++;
this.name = name;
this.address = address;
check();
|
(之前的值)
"Boddy"
"Alice"
"Alice"
"Alice"
"Alice"
broken...
|
(之前的值)
(之前的值)
(之前的值)
"Alaska"
"Brazil"
"Brazil"
|
上面是其中一种情况
工作人员智商有些问题,它一次只能记住一个名字和地址
Alice和Boddy都来到了门前,Bruce告诉工作人员他的名字,
工作人员记录名字:boddy
Alice来凑热闹把自己的名字和地址告诉了工作人员,这时候工作人员记录名字:Alice,地址Alaska
Boddy呢,这人他给忘了。。。
然后Boddy又告诉他自己的地址,于是工作人员脑中是这样记录的:名字Alice,地址Brazil
然后Alice和Boddy都等着工作人员核对(check()),于是悲剧发生了,工作人员认为这两个人都在骗他,所以发出了警告
发生这种情况的原因是Alice和Boddy非要争抢过安检,如果安检门弄小点,让他们一次最多过来一个,那就不会发生这种情况了
在这个例子中,也就是说:gate的pass方法一次只让一个线程调用,不允许Alice正在pass中呢,别人再凑过来
很幸运,java中用synchronized关键字就可以保证一次只有一个线程来执行这个方法
修改后的代码
public static class Gate{
private int counter = 0;
private String name;
private String address;
public synchronized void pass(String name, String address){
this.counter++;
this.name = name;
this.address = address;
check();
}
private void check(){
if(this.name.charAt(0) != this.address.charAt(0)){
System.out.println("broken:" + toString());
}
}
public synchronized String toString(){
return "No. " + this.counter + ":" + this.name + "," + this.address;
}
}
这时候再运行,则不会再出差错了
上面的代码只是给pass方法和toString方法加上了synchronized关键字
上面pass前面说过了
那么toString为什么也加上了synchronized关键字,为什么check没有加(课后习题3)
首先解释check的问题
check是private的,所以只能gate自己访问的到,在外面无法被调用。
gate里面pass调用了它,因为pass已经加上了synchronized,所以没有必要再给check函数设置此关键字
关于toString,由于它是public的,所以在外面可以被访问。
如果现在Alice正在调用toString,this.name = Alice,此时Boddy执行pass方法,把address改掉了,这时候Alice继续执行toString,那么悲剧发生了
所以,toString加上synchronized是必要的,这样执行toString方法时就不会被pass函数干扰了
在这个例子原始状态中,打印到出错的log时候counter已经到1000+了,如何让程序更快的看的错误log呢
其实不难,延长pass方法即可
public void pass(String name, String address) {
this.counter++;
this.name = name;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
this.address = address;
check();
}
pass方法执行时间变长了,出错的几率也就变高了(课后习题1)
关于Single Threaded Execution模式适用性,需要满足下面的条件:
1.多线程时
2.数据可被多个线程访问
3.状态可能改变
4.需要确保数据安全性
这些都很容易理解,不再加以解释
生命性与死锁
考虑下面的一种情况:
假设Alice和Boddy都是个吃货,他们打算享用意大利面,但是只有一套餐具:一把叉子一把汤匙
吃面的时候同时需要叉子和汤匙,Alice眼疾手快拿到了叉子,Boddy不甘示弱抢到了汤匙,这时候尴尬了
Alice等着Boddy放下汤匙,Boddy等着Alice放下叉子,然后他们就这么一直等下去,地老天荒,海枯石烂。。。
这时候就是所谓的死锁。
Single Threaded Execution满足那些条件时可能发生死锁呢?
1.具有多个参与者(SharedResource)
2.线程锁定一个SharedResource时,没有解除锁定就去锁定另一个SharedResource
3.获取SharedResource参与者的顺序不固定(和SharedResource参与者是对等的)
现在我们再看看吃货这个例子
1.多个参与者(SharedResource)相当于叉子和汤匙
2.Alice拿到了汤匙不放手就要去拿叉子
3.拿汤匙和叉子地位相同,不要求先拿谁
只要1,2,3其中一条被破坏了,那么就可以避免死锁的发生
下面用代码来描述这个例子(习题6)
餐具
public static class Tool{
private final String name;
public Tool(String name){
this.name = name;
}
public String toString(){
return "[" + name + "]";
}
}
吃货
public static class EaterThread extends Thread{
private String name;
private Tool leftHand;
private Tool rightHand;
public EaterThread(String name, Tool leftHand, Tool rightHand){
this.name = name;
this.leftHand = leftHand;
this.rightHand = rightHand;
}
public void run(){
while(true){
eat();
}
}
public void eat(){
synchronized (leftHand) {
System.out.println(name + " takes up " + leftHand + "(left.)");
synchronized (rightHand) {
System.out.println(name + " takes up " + rightHand + "(right.)");
System.out.println(name + " is eating now ,yam yam!");
System.out.println(name + " put down " + rightHand + "(right.)");
}
System.out.println(name + " put down " + leftHand + "(left.)");
}
}
}
public static void main(String[] args) {
Tool spoon = new Tool("Spoon");
Tool fork = new Tool("Fork");
new EaterThread("Alice", spoon, fork).start();
new EaterThread("Bobby", fork, spoon).start();
}
看看运行情况,果然他们没吃几口,就停住了
Alice takes up [Spoon](left.)
Alice takes up [Fork](right.)
Alice is eating now ,yam yam!
Alice put down [Fork](right.)
Alice put down [Spoon](left.)
Alice takes up [Spoon](left.)
Bobby takes up [Fork](left.)
原因也很简单,一个人先拿起一个餐具不放(synchronized (leftHand))的同时去拿另一个餐具,在他还没拿起来下一个餐具的时候,另一个人也打算eat,执行到这里的时候synchronized (leftHand),因为两个人拿到的是不同的餐具,所以leftHand指向不同的对象,代码并不相互影响所以程序继续执行,然后他们就发现无法获得另一把餐具,因为在对方手里
之前提到了Single Threaded Execution发生死锁的3个要素
我们来挨个尝试一下
首先破坏
1.具有多个参与者(SharedResource)
那么我们只提供叉子好了,你们都用一只手,有个吃相
public static class EaterThread extends Thread{
private String name;
private Tool hand;
public EaterThread(String name, Tool hand){
this.name = name;
this.hand = hand;
}
public void run(){
while(true){
eat();
}
}
public void eat(){
synchronized (hand) {
System.out.println(name + " takes up " + hand);
System.out.println(name + " is eating now ,yam yam!");
System.out.println(name + " put down " + hand);
}
}
}
public static void main(String[] args) {
Tool fork = new Tool("Fork");
new EaterThread("Alice", fork).start();
new EaterThread("Bobby", fork).start();
}
如果破坏
2.线程锁定一个SharedResource时,没有解除锁定就去锁定另一个SharedResource
习题答案的方式实际和上面差不多,如果只有一个工具那就不会打架了
下面引入一个新的类Pair
public static class Pair{
private Tool rightHand;
private Tool leftHand;
public Pair(Tool rightHand, Tool leftHand){
this.rightHand = rightHand;
this.leftHand = leftHand;
}
public String toString(){
return "[" + leftHand + "," + rightHand + "]";
}
}
改造EaterThread
public static class EaterThread extends Thread{
private String name;
private Tool leftHand;
private Tool rightHand;
public EaterThread(String name, Pair pair){
this.name = name;
this.leftHand = pair.leftHand;
this.rightHand = pair.rightHand;
}
public void run(){
while(true){
eat();
}
}
public void eat(){
synchronized (leftHand) {
System.out.println(name + " takes up " + leftHand + "(left.)");
synchronized (rightHand) {
System.out.println(name + " takes up " + rightHand + "(right.)");
System.out.println(name + " is eating now ,yam yam!");
System.out.println(name + " put down " + rightHand + "(right.)");
}
System.out.println(name + " put down " + leftHand + "(left.)");
}
}
}
public static void main(String[] args) {
Tool spoon = new Tool("Spoon");
Tool fork = new Tool("Fork");
Pair p = new Pair(spoon, fork);
new EaterThread("Alice", p).start();
new EaterThread("Bobby", p).start();
}
这样他们也会交替的你吃一会我吃一会了
最后来破坏
3.获取SharedResource参与者的顺序不固定(和SharedResource参与者是对等的)
这只需要让他们按着相同的顺序来拿餐具即可
public static void main(String[] args) {
Tool spoon = new Tool("Spoon");
Tool fork = new Tool("Fork");
new EaterThread("Alice", spoon, fork).start();
new EaterThread("Bobby", spoon, fork).start();
}
笔记截图
至此这章内容就基本结束了
在书中的进阶说明里有一些关于synchronized的讨论
关于synchronized,我们需要思考
1.synchronized在保护什么
确定下来要保护的内容之后,要思考一下,其他地方有没有需要保护的,为什么
2.该以什么单位保护
比如我需要同时获得叉子和汤匙,这是一个完整的动作不能分割(当然也不是不能分割,分割后就会出现大眼瞪小眼的情况),那么
public synchronized void setName(String name){
this.name = name;
}
public synchronized void setAddress(String address){
this.address = address;
}
这种形式就没有意义,并不安全(要把它当做一整体,可以像Pair那样封装一下)
3.获取谁的锁定来保护
调用synchronized方法是获得实例this的锁定,如果实例不同,锁也就不同,所以不同实例可同时执行synchronized的相同的方法。使用synchronized块的时候也是需要获得对象的锁,所以需要考虑好获得谁的锁。书中给出一个比喻:获得错误的锁就好比想要保护自己的家,却锁上了邻居的门
转贴请保留以下链接
本人blog地址
http://su1216.iteye.com/
http://blog.csdn.net/su1216/
- 大小: 86.7 KB
分享到:
相关推荐
标题中的“多线程精品资源--Chrome multi-threaded download manager extension”揭示了这是一个关于Chrome浏览器的多线程下载管理器扩展程序的资源集合。多线程下载管理器是一种能够利用多个连接同时下载文件的工具...
在多线程编程中,获取系统时间是一项常见的任务,特别是在实时系统、计时器或并发操作中。"get-system-time-in-Multi-threaded.rar_In Time"这个...通过学习和实践这个资源,开发者可以提升在多线程环境下的编程能力。
标题 "rknn-multi-threaded-nosigmoid.zip" 暗示了这个压缩包可能包含一个或多个关于RKNN(RISC-V Neural Network Kernel)的多线程实现,并且在模型中省略了Sigmoid激活函数。RKNN是针对RISC-V架构优化的神经网络...
"linux-ix86-threaded"部分表明该版本是为32位Linux系统设计的,并且支持多线程。这意味着可以在单个进程中同时执行多个Tcl脚本,提高程序的并发性和效率。这对于处理大量并发请求或执行并行计算的任务特别有用。 ...
总的来说,"Single-threaded-file-transfer.zip_single"是一个专注于基础性能优化的单线程文件传输实现,适合初学者了解网络编程的基础,以及理解单线程与多线程在实际应用中的区别。对于更复杂的应用场景,如大型...
此版本支持多线程,这意味着可以在同一个Tcl进程中并发执行多个任务,这对于需要并行处理的应用程序来说是一个重要的特性。 该压缩包中的主要文件是"ActiveTcl-8.5.18.0.298892-win32-x86_64-threaded.exe",这是一...
ActiveTcl8.6.4.1.299124-win32-ix86-threaded.exe
ActiveTcl8.6.4.1.299124-win32-x86_64-threaded
ActiveTcl8.6,Tcl脚本运行环境,32位及64位 ActiveTcl8.6,Tcl脚本运行环境,32位及64位
标题中的“single-threaded-tcp-scanner.rar_single”暗示了这是一个关于单线程TCP扫描器的编程资源,可能是一个源代码包。描述确认了这一点,它指出这是使用C++编程语言实现的一个扫描器,主要关注单线程TCP扫描器...
标题"ActiveTcl8.6.3.1.298624-win32-ix86-threaded"表明这是一个针对Windows 32位Intel x86架构的ActiveTcl版本,版本号为8.6.3.1,构建号为298624,且支持多线程。这意味着它可以充分利用多核处理器的性能,提高...
ActiveTcl8.4.11.2.201775-win32-ix86-threaded.exe 包含了很多库,另外是支持win32,32bit的老版本
ActiveTcl8.4.16.0.282109-win32-ix86-threaded
这个版本是8.4.19,包含了线程支持,这意味着它可以在多线程环境中运行,提高性能和并发性。 在描述中提到的“在编译postgresql时,需要下载很多依赖库”,指的是PostgreSQL,一个开源的对象关系型数据库管理系统。...
标题中的"ActiveTcl8.4.19.4.292682-win32-ix86-threaded.exe_2"指的是一个特定版本的ActiveTcl软件的安装程序,适用于Windows 32位操作系统,且是线程安全(threaded)的。ActiveTcl是由Tcl/Tk开发公司ActiveState...
这个压缩包是线程安全的,意味着它支持多线程编程,这对于在多核处理器上运行的系统来说尤其重要。TCL是一种动态编程语言,常用于脚本编写、自动化任务、GUI开发以及测试套件等。 描述中提到,这个包可能是为在ARM...
3. **线程支持**:版本8.6.4.1.299124-win32-ix86-threaded中的“threaded”标识意味着它支持多线程,可以利用多核处理器进行并行计算。 4. **预编译二进制**:ActiveTcl提供预编译的二进制包,用户可以直接下载...
多线程入门,多线程基础知识,描述了Synchronized的线程互斥原理,和Single Threaded Execution模式,符合基础入门的用户可以好好学习,加深理解
第1章 Single Threaded Execution——能通过这座桥的,只有一个人 第2章 Immutable——想破坏它也没办法 第3章 Guarded Suspension——要等到我准备好喔 第4章 Balking——不需要的话,就算了吧 第5章 Producer-...
正在进行中的一本书,着重介绍如何使用Java语言进行面向对象的多线程设计和编程。