注:本文的主要参考资料为结城浩所著《JAVA多线程设计模式》。
单线程执行模式(Single Threaded Execution Pattern)是最简单的多线程设计模式,几乎所有其他的模式都在不同程度上应用了该模式。先看一个程序,通过它可以体验多线程程序无法正确执行的场景,这里所写的是个关于“只能单个通过的门”的程序:有三个人频繁地、反复地经过一个只能容许单人经过的门,当人通过门的时候,这个程序显示出通过人的“姓名”与“出生地”,其代码如下:
public class Gate {
private int counter = 0;
private String name = "Nobody";
private String address = "Nowhere";
public void pass(String name, String address) {
this.counter++;
this.name = name;
this.address = address;
check();
}
public String toString() {
return "No." + counter + ": " + name + ", " + address;
}
private void check() {
if (name.charAt(0) != address.charAt(0)) {
System.out.println("***** BROKEN ***** " + toString());
}
}
}
public class UserThread extends Thread {
private final Gate gate;
private final String myname;
private final String myaddress;
public UserThread(Gate gate, String myname, String myaddress) {
this.gate = gate;
this.myname = myname;
this.myaddress = myaddress;
}
public void run() {
System.out.println(myname + " BEGIN");
while (true) {
gate.pass(myname, myaddress);
}
}
}
public class Main {
public static void main(String[] args) {
System.out.println("Testing Gate, hit CTRL+C to exit.");
Gate gate = new Gate();
new UserThread(gate, "Alice", "Alaska").start();
new UserThread(gate, "Bobby", "Brazil").start();
new UserThread(gate, "Chris", "Canada").start();
}
}
这里用到了一个小小的技巧:我们将姓名与出生地的“头一个”字母设计为相同(A、B或者C),因此可以通过校验两者来观察线程间是否有“互窜”的现象。
在PC机上运行一会儿,一定会打印出“***Broken***”字样,说明上述程序存在线程安全问题(确切来说,是Gate.java是非线程安全的类)。
上述现象之所以会发生,关键问题还是出在Gate类pass方法中,详细看一下代码:
public void pass(String name, String address) {
this.counter++;
this.name = name;
this.address = address;
check();
}
为简单说明,现假设只有两个线程(Alice与Bobby),它们每次调用pass的顺序可能是完全随机的,因此会存在某一刻,pass中的四条语句可能是交错执行的;假设它们的执行顺序如下:
线程Alice | 线程Bobby | this.name的值 | this.address的值 |
this.counter++; | this.counter++; | (之前的值) | (之前的值) |
| this.name = name; | "Bobby" | (之前的值) |
this.name = name; | | "Alice" | (之前的值) |
this.address = address; | | "Alice" | "Alaska" |
| this.address = address; | "Alice" | "Brazil" |
check(); | check(); | "Alice" | "Brazil" |
线程Alice | 线程Bobby | this.name的值 | this.address的值 |
this.counter++; | this.counter++; | (之前的值) | (之前的值) |
this.name = name; | | "Alice" | (之前的值) |
| this.name = name; | "Bobby" | (之前的值) |
| this.address = address; | "Bobby" | "Brazil" |
this.address = address; | | "Bobby" | "Alaska" |
check(); | check(); | "Bobby" | "Alaska" |
无论发生上述哪一种,都会使name与address出现非预期的结果。以上是没有使用Single Threaded Execution Pattern的情况。如需做线程安全的改造,可将Gate改造为如下:
public class Gate {
private int counter = 0;
private String name = "Nobody";
private String address = "Nowhere";
public synchronized void pass(String name, String address) {
this.counter++;
this.name = name;
this.address = address;
check();
}
public synchronized String toString() {
return "No." + counter + ": " + name + ", " + address;
}
private void check() {
if (name.charAt(0) != address.charAt(0)) {
System.out.println("***** BROKEN ***** " + toString());
}
}
}
在我的机器上,无论多久都没有显示BROKEN消息。这个执行结果虽然不能证明Gate类的安全性,但我们可以说该程序安全的可能性很大。
上述情况之所以会显示BROKEN,是因为pass方法内的程序可能会被多个线程穿插执行。synchronized方法,能够保证同时只有一个线程可以执行它。线程Alice执行pass方法的时候,线程Bobby就不能调用pass方法。在线程Alice执行完pass方法之前,线程Bobby会在pass方法的入口处被阻挡下。当线程Alice执行完pass方法之后,将锁定解除线程Bobby才可以开始执行pass方法。所有,只要将pass方法声明称synchronized的,就绝对不会出现上面表中的情况;而一定是下图的两种情况之一:
线程Alice | 线程Bobby | this.name的值 | this.address的值 |
【获取锁定】 | | | |
this.counter++ | | (之前的值) | (之前的值) |
this.name = name | | "Alice" | (之前的值) |
this.address = address | | "Alice" | "Alaska" |
check(); | | "Alice" | "Alaska" |
【解除锁定】 | | | |
| 【获取锁定】 | | |
| this.counter++ | "Alice" | "Alaska" |
| this.name = name | "Bobby" | "Alaska" |
| this.address = address | "Bobby" | "Brazil" |
| check(); | "Bobby" | "Brazil" |
| 【解除锁定】 | | |
线程Alice | 线程Bobby | this.name的值 | this.address的值 |
| 【获取锁定】 | | |
| this.counter++ | (之前的值) | (之前的值) |
| this.name = name | "Bobby" | (之前的值) |
| this.address = address | "Bobby" | "Brazil" |
| check(); | "Bobby" | "Brazil" |
| 【解除锁定】 | | |
【获取锁定】 | | | |
this.counter++ | | "Bobby" | "Brazil" |
this.name = name | | "Alice" | "Brazil" |
this.address = address | | "Alice" | "Alaska" |
check(); | | "Alice" | "Alaska" |
【解除锁定】 | | | |
这里再说明一下,toString方法需要加上synchronized的理由,以及check方法不加上synchronized的理由:
- 假设线程A正在调用pass方法,而线程B此时正在调用toString,由于线程B在引用name之后再引用address,此间隙线程A可能会改掉address的值,因此可能会输出不一致的name与address;即此时,pass是线程安全的,但toString却不是线程安全的。
- 由于check方法是private的,这意味着它不会被客户端直接调用,而唯一调用check方法的pass已被设成synchronized了,因此,不需要再将check设置成synchronized方法。虽然将check方法设置成synchronized不会产生问题,但锁定会带来一定的开销,因此完全没有必要。
总的来说,一个多线程下的程序,往往有会一块“限制多个线程访问”的程序块,这部分可称为临界区。临界区的存在一定会使程序的执行性能下降,主要是因为:
- 获取锁定需要花时间
- 线程冲突时必须进行等待。当一个线程执行临界区内的操作时,其他要进入临界区的线程会被阻挡。
学习&理解该模式的一个很好的方法,就是每当看见synchronized方法时,都去思考一下“该synchronized是在保护什么东西”?在上面的例子中,这个方法实质上是在保护counter、name以及address字段不会被多个线程同时访问。
如果我们为Gate类添加synchronized的setter方法,它还是线程安全的吗?
public synchronized void setName(String name)
{
this.name = name;
}
public synchronized void setAddress(String address)
{
this.address = address;
}
尽管这些方法都被设置成synchronized了,但是Gate类还是不安全的。因为name与address非得合在一起赋值才行。之所以将pass方法设置成synchronized,主要就是为了不要让多个线程穿插赋值。如果开放出setName、setAddress等方法,线程对字段的赋值操作就被分散了。因此,要保护,就要合在一起保护,否则是没有意义的。
另外,调用synchronized方法的线程,一定会获取this的锁定。一个实例的锁定,某个时刻内只能被一个线程所享用。换句话说,如果实例不同,即使用synchronized方法保护,多个线程还是能各自执行。
分享到:
相关推荐
第1章 Single Threaded Execution——能通过这座桥的,只有一个人 第2章 Immutable——想破坏它也没办法 第3章 Guarded Suspension——要等到我准备好喔 第4章 Balking——不需要的话,就算了吧 第5章 Producer-...
第1章 Single Threaded Execution——能通过这座桥的,只有一个人 第2章 Immutable——想破坏它也没办法 第3章 Guarded Suspension——要等到我准备好喔 第4章 Balking——不需要的话,就算了吧 第5章 Producer-...
1、Single Threaded Execution ———— 能通过这座桥的,只有一个人 2、Immutable ———— 想破坏它也没办法 3、Guarded Suspension ———— 要等到我们准本好哦 4、Balking ———— 不需要的话,就算了吧 5、...
多线程入门,多线程基础知识,描述了Synchronized的线程互斥原理,和Single Threaded Execution模式,符合基础入门的用户可以好好学习,加深理解
使用C++编写的扫描器源码。包括单线程tcp扫描器源代码。
实现单线程文件传输,不支持断点传出,功能相对简单,但是性能很好。
Single-threaded superscalar out-of-order execution, multithreaded SMT and CMP execution model; Multi-level inclusive cache hierarchy with the directory-based MESI coherence protocol; Simple cycle-...
基于JAVA毕业设计-JAVA图书管理系统毕业设计(源代码+论文).rar JAVA是INTERNET开发的一个强大的工具,它是一个C++的简化版本。JAVA是一门"简单的、面向对象的、强类型的、编译型的、结构无关的、多线程(multi-...
对SMP机器上的MPI的实现进行详细讲述,对thread理论有大帮助
用Java作的HTTP1.0 下的Webserver服务器 用TCP协议,采用多线程技术,可作为WEbServer的入门例子
正在进行中的一本书,着重介绍如何使用Java语言进行面向对象的多线程设计和编程。
测试无问题,默认字符集请改为utf-8,端口号可以按自己喜好更改,已经准备好iisstart.html文件在其中,可以访问这个网页或是自己新令。
Therefore, even if you've written a threaded program in Java, this book can help you to exploit new features of Java to write even more effective programs. The first few chapters of the book deal ...
sun公司开发的,java2核心技术,卷II:高级性能,包括一系列的高级java应用技术,如数据库德连接,高级swing,多线程,软件本地化等等,本文件中则包含该书中的所用实例,配合该书使用,使对java的学习更具效率-sun developed, ...
stproxy是小型,快速且高效的HTTP / SSL代理,具有以下功能:高匿名性,基本的HTTP授权,使用Web界面的用户管理,异步dns解析器,单线程(仅一个进程线程)
JAVA是一门"简单的、面向对象的、强类型的、编译型的、结构无关的、多线程(multi-threaded)的、垃圾自动收集的、健壮的、安全的、可扩展的语言。本系统使用的是美国微软公司的MICROSOFT VISUAL J++6.0。 MICROSOFT...
1. Introduction to Java 1.1Beginnings of the Java Language Project 1.2Design Goals of Java 1.2.1Simple, Object Oriented, and Familiar 1.2.2Robust and Secure 1.2.3Architecture Neutral and ...
线程交互是比较复杂的问题,SCJP要求不很基础:给定一个场景,编写代码来恰当使用等待、通知和通知所有线程
Multi-Threaded Game Engine Design
资源分类:Python库 所属语言:Python 资源全名:threaded-4.0.8.tar.gz 资源来源:官方 安装方法:https://lanzao.blog.csdn.net/article/details/101784059