注:本文的主要参考资料为结城浩所著《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、...
在COM学习笔记中,主要涉及的是COM线程管理的概念,包括线程单元(Thread Apartments,简称TA)以及线程如何与COM对象交互。 线程单元是COM中管理线程与对象交互的核心机制。它是一个逻辑上的容器,确保同一单元内...
本项目名为"Single-threaded-file-transfer.zip_single",其核心内容是实现了一个单线程的文件传输机制,不包含断点续传功能,但以性能优化为亮点。 单线程文件传输是指在执行文件传输时,整个过程只使用一个线程来...
标题中的“single-threaded-tcp-scanner.rar_single”暗示了这是一个关于单线程TCP扫描器的编程资源,可能是一个源代码包。描述确认了这一点,它指出这是使用C++编程语言实现的一个扫描器,主要关注单线程TCP扫描器...
标题中的“TCP/IP多线程web服务器实现,Multi-Threaded Web Server java实现多网页请求访问”揭示了我们要讨论的核心技术点,即如何使用Java语言实现一个基于TCP/IP协议的多线程Web服务器。Web服务器的主要任务是...
Java的Socket类可以方便地处理TCP连接,而线程则能确保服务器可以同时处理多个客户端请求,提高了服务效率。 多线程技术在本项目中扮演着关键角色。在并发环境中,多线程允许服务器同时处理来自多个客户端的请求,...
WCF提供了两种多线程并发模式:限制多线程和自由多线程。 - **限制多线程(Limited MultiThreading)**:服务实例可以并行处理一定数量的请求,超过这个限制的请求将被排队。通过调整`MaxConcurrentSessions`、`...
多线程入门,多线程基础知识,描述了Synchronized的线程互斥原理,和Single Threaded Execution模式,符合基础入门的用户可以好好学习,加深理解
- 多线程:利用操作系统提供的线程管理功能,使得程序能够并发执行多个任务。 - 通信优化:通过对数据传输方式进行改进,以提高程序运行效率的技术。 #### 引言 随着商用SMP架构的成功,采用商品化组件构建的SMP...
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-...
Java多线程编程是Java开发中的重要组成部分,它允许程序同时执行多个任务,从而提高效率。然而,多线程编程也带来了一些潜在的陷阱和挑战,尤其是...通过深入学习和实践,你可以更好地应对Java多线程编程中的各种挑战。
在IT领域,尤其是在Java编程中,多线程技术是实现高效并发处理的关键。"Multi-threaded-transfer.rar"这个压缩包中的资源显然与使用Java进行多线程文件传输有关。下面我们将详细探讨多线程和Java文件传输的相关知识...
正在进行中的一本书,着重介绍如何使用Java语言进行面向对象的多线程设计和编程。
- **单线程执行(Single Threaded Execution)** - **定义**: 确保在单一的线程中执行特定的操作。 - **应用场景**: 当需要确保特定操作不会被并发执行时。 - **缓存管理(Cache Management)** - **定义**: 一种用于...