这个专题主要讨论并发编程的问题,所有的讨论都是基于JAVA语言的(因其独特的内存模型以及原生对多线程的支持能力),不过本文传达的是一种分析的思路,任何有经验的朋友都能很轻松地将其扩展到任何一门语言。
注:本文的主要参考资料为结城浩所著《JAVA多线程设计模式》。
线程的英文名Thread,原意指“细丝”。在多线程程序中,若要追踪各个线程的轨迹,就会派生出一系列错综复杂的乱线团。假设在运行过程中,如果有人问到“请问现在执行到代码的哪一部分了?”,你需要多个手指头才能指出正确的地方。
当应用程序的规模、复杂程度达到一定程度时,并发设计是一个必将考虑到的问题,以下是一些常见的应用:
- GUI:以word为例,我们正在编辑一份大型的文档,此时执行“查找”操作;当word进行查找时,同时会出现一个“停止查找”的按钮,用户可以随时停止。此时就用到了多线程,其中一个线程在后台执行查找,另一个线程显示“停止查找”的按钮,一旦按下,则立即停止查找。两个操作交由不同的线程来处理,各线程可以专心负责自己的功能,因此也是模块化设计思想的一种体现。
- 比较耗时的I/O处理:由于磁盘、网络的IO操作消耗的时间远大于内存处理,如果在此段时间内,程序仅仅是等待而无法执行其它处理,性能会大打折扣。如果事先能将I/O处理和非I/O处理区分开来,这样就能够利用进行I/O处理时CPU空闲的间隙,进行其它处理了。
- 一个Server与多个Client:大部分Server都要求能够同时服务于1个以上的Client。Server本身并不知道何时会有Client接入,并且在Server中直接引入多个Client的设计,并不是十分优雅的方案;因此不妨设计成一旦有Client连接到Server,就会生成自动出来迎接这个Client的线程。这样一来,Server端的程序就可以简单地设计成好像只服务一个Client。当然,从J2SE 1.4开始,已经加入了新的NIO类库,不必利用线程也能进行兼具性能和扩充性的I/O处理,详情可参考JDK。
至于JAVA中线程的编码方式,无非是继承自抽象类Thread或者实现Runnable接口,想必各位读者都很熟悉了,这里就不复述了。
在多线程程序里,多个线程既然可以自由操作,当然就可能同时操作到同一实例。这个情况又是会造成不必要的麻烦。例如经典的银行取款问题,其“确认可用余款”这一部分的代码应该该为:
if(可用余额大于欲提取金额)
{
从可用余额中减掉欲提取金额
}
这段逻辑本身并没有问题。先确认可用余额,再检查是否允许提取输入金额,如果系统决定可以领取,则从可用余额中减掉此金额,保证不会发生可用余额变为负数的情况。
但是,同时若有2个以上的线程执行这个程序的代码,可用余额可能会变成负数。比如:
初始化……
可用余额 = 1000元
欲提取金额 = 1000元
线程A——可用余额大于欲提取金额?
线程A——是
<<<此时切换成线程B>>>
线程B——可用余额大于欲提取金额?
线程B——是
线程B——从可用余额中减掉欲提取金额
线程B——可用余额变为0元
<<<此时切换成线程A>>>
线程A——从可用余额中减掉欲提取金额
线程A——可用余额变为-1000元
我们发现,由于时间差,可能会发生线程B夹在线程A的“确认可用余额”和“减去可用余额”之间的情况,这就会导致出现金额为负数的情况。
JAVA中使用synchronized来实现共享互斥,这就好比十字路口的红绿灯处理一样;当直向行车时绿灯时,另一边的横向车灯一定是红灯。synchronized也采用类似的“交通管制”的方式来实现线程间的互斥。
上述银行存取款的互斥实现如下
public class Bank
{
private int money;
private String name;
public Bank(String name, int money)
{
this.money = money;
this.name = name;
}
// 存款
public synchronized void deposit(int m)
{
money += m;
}
// 取款
public synchronized void withdraw(int m)
{
if (money >= m)
{
money -= m;
return true; // 已取款
}
else
{
return false; // 余额不足
}
}
public String getName()
{
return name;
}
}
当一个线程正在执行Bank实例的deposit或withdraw方法时,其他线程就不能执行同一实例的deposit以及withdraw方法。欲执行的线程必须排队等候。
也许会注意到,Bank类里还有一个非synchronized的方法——getName。无论其它线程是否正在执行同一实例的deposit、withdraw或者getName方法,都不妨碍它的执行。
synchronized阻挡的几种使用方式,如下
synchronized局部阻挡:如果需要“管制”的不是整个方法,而是方法的一部分,就使用此类阻挡,代码如下
synchronized(表达式)
{
……
}
synchronized实例方法阻挡:如果需要“管制”的是整个实例方法,而是方法的一部分,就使用此类阻挡,代码如下
synchronized void method()
{
……
}
这段代码在功能上与如下代码有异曲同工之妙
void method()
{
synchronized(this)
{
……
}
}
synchronized类方法阻挡:如果需要“管制”的是类方法,就使用此类阻挡,代码如下
class Something
{
static synchronized void method()
{
……
}
}
这段代码在功能上与如下代码有异曲同工之妙
class Something
{
static void method()
{
synchronized(Something.class)
{
……
}
}
}
从上面可以看出,synchronized类阻挡其实质是用类对象作为锁定的对象去进行互斥的。
线面讲讲线程的协调。上面所说的是最简单的共享互斥,只要有线程再执行就乖乖地等候;现实工作中,我们需要的往往不止于此,比如:
- 若空间有空闲则继续写入,若没有则等候。
- 空间有空闲时,及时通知等待线程。
线程协调的具体实现将在下一章中介绍。
JAVA中提供了wait、notify、notifyAll以支持此类条件处理。这里要注意到以下几点:
- 如欲执行某一实例的wait方法,则首先必须获得该实例的锁;一旦进入wait set(线程的休息间),则自动释放该锁。
- 使用notify方法时,会从锁定实例的wait set中唤醒一个线程。同样的,线程必须首先获得锁定,才能调用notyfy方法。被唤醒的线程并不是立即就可以执行的,因为在此刻,notify的线程还一直占有锁。另外,假设执行notify时,wait set里有多于一个的线程在等待,具体选择那个线程是无法得知的,因此应用程序最好不要写成会因所选线程而有所变动的方式。
- notifyAll与notify基本相同,唯一区别在于它是唤醒所有线程而非一个。一般来说,使用notifyAll写的程序会更健壮一点。
分享到:
相关推荐
多线程并发从,学习笔记,代码+注释,从线程创建开始到多线程并发,相关锁以及一些设计模式等
(1)如果设计正确,多线程程序可以通过提高处理器资源的利用率来提升系统吞吐率 (2)建模简单:通过使用线程可以讲复杂并且异步的工作流进一步分解成一组简单并且同步的工作流,每个工作流在一个单独的线程...
Linux面试专题及答案+ActiveMQ消息中间件面试专题+Java基础面试题+MySQL性能优化的21个最佳实践+微服务面试专题及答案+深入理解java虚拟机+设计模式面试专题及答案+开源框架面试专题及答案+并发编程及答案+Spring...
Java学习笔记包含JVM、spring、源码分析、多线程、offer题解、设计模式、面试宝典.zip Java学习笔记,内容包括JVM,spring,hashMap实现源码分析,多线程,剑指offer题解,设计模式。然后根据面试的重点,又将很多从...
学习线程介绍Java多线程学习PDF格式Java并发编程的艺术.pdf JAVA并发编程实践.pdf图解Java多线程设计模式-第2版.pdf代码code1是《 Java并发编程的艺术》的源码ThreadDesignPatterns是《图解Java多线程设计模式》第1...
本Jva(Java)学习笔记是一份深入且详尽的教程,不仅包括Java的基础知识,还融入了作者的个人见解和实际编程经验。内容涵盖: Java基础:数据类型、控制流、异常处理。 面向对象编程:类与对象、继承、多态、封装。...
软件工程师的学习笔记,包含网络、操作系统、设计模式、JVM、多线程与高并发、Spring、MySQL.....
Java架构面试笔试专题资料及经验(含答案)和学习笔记: ActiveMQ消息中间件面试专题.pdf Dubbo面试专题及答案(下).pdf Dubbo面试及答案(上).pdf java后端面试题答案.pdf Java基础面试题.pdf java多线程并发编程...
202面试题,Java面试题、JVM面试题、多线程面试题、并发编程、设计模式面试题、Spring面试题、MyBatis面试题、ZooKee 一、内容概览 本次分享的资源涵盖了Java面试的各个方面,从基础知识到高级技术,从数据库到...
《Java高级复习笔记-程序阅读题.docx》是一份宝贵的学习资源,特别适合那些希望深入理解和掌握Java高级编程概念的学习者和开发者。以下是对这个资源的详细描述: 深度理解编程概念:这份文档提供了一系列程序阅读...
MongoDB学习笔记.docx mybatis原理.docx MyBatis面试专题.docx MyBatis面试专题及答案.pdf Mybatis面试题(含答案).pdf MySQL性能优化的21个最佳实践.pdf mysql面试专题.docx MySQL面试题(含答案).pdf Netty面试...
多线程、锁、并发 框架 Spring , Mybatis , SpringBoot , SpringMVC ... 中间件 RPC , MQ , elasticsearch ... 操作系统linux 数据库 Mysql , MongoDB , HBase ... 优化 常用工具 git , svn , 效率 , aliyun , mac ,...
[Java多线程概览.md](./ profession / program / java /并发编程/ Java多线程概览.md) 测试 openCV Python Django的 烧瓶 [python基础学习.md](./ profession / program / python / python基础学习.md) ...
VS2005 ASP.NET本地化学习笔记&感受 在自定义Server Control中捆绑JS文件 Step by Step 深度解析Asp.Net2.0中的Callback机制 使用 Web 标准生成 ASP.NET 2.0 Web 站点 ASP.NET 2.0基于SQLSERVER 2005的aspnetdb.mdf...
xiaoshaDestiny:学习笔记,类库,面试QA learn-activemq 消息队列 ActiveMQ的使用,Spring整合ActiveMQ learn-data-tructure 数据结构 learn-design-pattern 设计模式 编码详解 learn-jedis Redis的客户端操作工具包 ...
1. 简述 private、 protected、 public、 internal 修饰符的访问权限。 答 . private : 私有成员, 在类的内部才可以访问。...47.当一个线程进入一个对象的一个synchronized方法后,其它线程是否可...
多线程与并发 ; ; ; 。 Java虚拟机 ; ; ; ; ; ; ; ; ; 。 4,Java Web Servlet ; ; ; ; 。 前端 ; ; ; ; ; ; ; 。 JDBC ; 。 MySQL ; ; ; ; ; ; 。 ...
《深入理解Java虚拟机》、《并发编程的艺术》、《Java多线程核心编程艺术》、《Java8函数式编程》、《Redis设计与实现》、《RocketMQ技术内幕》、《Spring技术内幕》、《Spring源码深度解析》、《剑指Offer》、...
并发/多线程 类加载 垃圾回收 Android UI 生命周期 启动模式 ActivityManagerService PMS Art/Dalvik Http Http WebSocket Udp Kotlin 算法 快速排序 链表 重新排版 RxJava2 Glide GreenDao 其他 反编译 Hook 动态...
随着Java学习的不断深入,发现很多知识在脑海里都是一个个碎片,建此仓库的目的希望把零碎的知识点都整合起来,提高自己的学习效率。欢迎志同道合的朋友,一起来维护该仓库 目录 网络基础 WEB TCP协议 HTTP/HTTPS ...