没有合适的资源?快使用搜索试试~ 我知道了~
java 线程同步 信号量控制同步
需积分: 19 16 下载量 188 浏览量
2008-11-16
14:24:31
上传
评论
收藏 209KB DOC 举报
温馨提示
试读
28页
关于线程同步 synchronize,需要牢牢记住的第一点是:线程同步就是线程排队。同步就是排队。线程同步的目的就是避免线程“同步”执行。
资源推荐
资源详情
资源评论
我们可以在计算机上运行各种计算机软件程序。每一个运行的程序可能包括多个独立运行
的线程(Thread)。
线程(Thread)是一份独立运行的程序,有自己专用的运行栈。线程有可能和其他线程共
享一些资源,比如,内存,文件,数据库等。
当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线
程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。
同步这个词是从英文 synchronize(使同时发生)翻译过来的。我也不明白为什么要用这个
很容易引起误解的词。既然大家都这么用,咱们也就只好这么将就。
线程同步的真实意思和字面意思恰好相反。线程同步的真实意思,其实是“排队”:几个线
程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。
因此,关于线程同步,需要牢牢记住的第一点是:线程同步就是线程排队。同步就是排队
线程同步的目的就是避免线程“同步”执行。这可真是个无聊的绕口令。
关于线程同步,需要牢牢记住的第二点是 “共享”这两个字。只有共享资源的读写访问才需
要同步。如果不是共享资源,那么就根本没有同步的必要。
关于线程同步,需要牢牢记住的第三点是,只有“变量”才需要同步访问。如果共享的资源
是固定不变的,那么就相当于“常量”,线程同时读取常量也不需要同步。至少一个线程修
改共享资源,这样的情况下,线程之间就需要同步。
关于线程同步,需要牢牢记住的第四点是:多个线程访问共享资源的代码有可能是同一份
代码,也有可能是不同的代码;无论是否执行同一份代码,只要这些线程的代码访问同一
份可变的共享资源,这些线程之间就需要同步。
为了加深理解,下面举几个例子。
有两个采购员,他们的工作内容是相同的,都是遵循如下的步骤:
(1)到市场上去,寻找并购买有潜力的样品。
(2)回到公司,写报告。
这两个人的工作内容虽然一样,他们都需要购买样品,他们可能买到同样种类的样品,但
是他们绝对不会购买到同一件样品,他们之间没有任何共享资源。所以,他们可以各自进
行自己的工作,互不干扰。
这两个采购员就相当于两个线程;两个采购员遵循相同的工作步骤,相当于这两个线程执
行同一段代码。
下面给这两个采购员增加一个工作步骤。采购员需要根据公司的“布告栏”上面公布的信息
安排自己的工作计划。
这两个采购员有可能同时走到布告栏的前面,同时观看布告栏上的信息。这一点问题都没
有。因为布告栏是只读的,这两个采购员谁都不会去修改布告栏上写的信息。
下面增加一个角色。一个办公室行政人员这个时候,也走到了布告栏前面,准备修改布告
栏上的信息。
如果行政人员先到达布告栏,并且正在修改布告栏的内容。两个采购员这个时候,恰好也
到了。这两个采购员就必须等待行政人员完成修改之后,才能观看修改后的信息。
如果行政人员到达的时候,两个采购员已经在观看布告栏了。那么行政人员需要等待两个
采购员把当前信息记录下来之后,才能够写上新的信息。
上述这两种情况,行政人员和采购员对布告栏的访问就需要进行同步。因为其中一个线程
(行政人员)修改了共享资源(布告栏)。而且我们可以看到,行政人员的工作流程和采
购员的工作流程(执行代码)完全不同,但是由于他们访问了同一份可变共享资源(布告
栏),所以他们之间需要同步。
同步锁
前面讲了为什么要线程同步,下面我们就来看如何才能线程同步。
线程同步的基本实现思路还是比较容易理解的。我们可以给共享资源加一把锁,这把锁只
有一把钥匙。哪个线程获取了这把钥匙,才有权利访问该共享资源。
生活中,我们也可能会遇到这样的例子。一些超市的外面提供了一些自动储物箱。每个储
物箱都有一把锁,一把钥匙。人们可以使用那些带有钥匙的储物箱,把东西放到储物箱里
面,把储物箱锁上,然后把钥匙拿走。这样,该储物箱就被锁住了,其他人不能再访问这
个储物箱。(当然,真实的储物箱钥匙是可以被人拿走复制的,所以不要把贵重物品放在
超市的储物箱里面。于是很多超市都采用了电子密码锁。)
线程同步锁这个模型看起来很直观。但是,还有一个严峻的问题没有解决,这个同步锁应
该加在哪里?
当然是加在共享资源上了。反应快的读者一定会抢先回答。
没错,如果可能,我们当然尽量把同步锁加在共享资源上。一些比较完善的共享资源,比
如,文件系统,数据库系统等,自身都提供了比较完善的同步锁机制。我们不用另外给这
些资源加锁,这些资源自己就有锁。
但是,大部分情况下,我们在代码中访问的共享资源都是比较简单的共享对象。这些对象
里面没有地方让我们加锁。
读者可能会提出建议:为什么不在每一个对象内部都增加一个新的区域,专门用来加锁呢
这种设计理论上当然也是可行的。问题在于,线程同步的情况并不是很普遍。如果因为这
小概率事件,在所有对象内部都开辟一块锁空间,将会带来极大的空间浪费。得不偿失。
于是,现代的编程语言的设计思路都是把同步锁加在代码段上。确切的说,是把同步锁加
在“访问共享资源的代码段”上。这一点一定要记住,同步锁是加在代码段上的。
同步锁加在代码段上,就很好地解决了上述的空间浪费问题。但是却增加了模型的复杂度
也增加了我们的理解难度。
现在我们就来仔细分析“同步锁加在代码段上”的线程同步模型。
首先,我们已经解决了同步锁加在哪里的问题。我们已经确定,同步锁不是加在共享资源
上,而是加在访问共享资源的代码段上。
其次,我们要解决的问题是,我们应该在代码段上加什么样的锁。这个问题是重点中的重
点。这是我们尤其要注意的问题:访问同一份共享资源的不同代码段,应该加上同一个同
步锁;如果加的是不同的同步锁,那么根本就起不到同步的作用,没有任何意义。
这就是说,同步锁本身也一定是多个线程之间的共享对象。
Java 语言的 synchronized 关键字
为了加深理解,举几个代码段同步的例子。
不同语言的同步锁模型都是一样的。只是表达方式有些不同。这里我们以当前最流行的
Java 语言为例。Java 语言里面用 synchronized 关键字给代码段加锁。整个语法形式表现为
synchronized(同步锁) {
// 访问共享资源,需要同步的代码段
}
这里尤其要注意的就是,同步锁本身一定要是共享的对象。
… f1() {
Object lock1 = new Object(); // 产生一个同步锁
synchronized(lock1){
// 代码段 A
// 访问共享资源 resource1
// 需要同步
}
}
上面这段代码没有任何意义。因为那个同步锁是在函数体内部产生的。每个线程调用这段
代码的时候,都会产生一个新的同步锁。那么多个线程之间,使用的是不同的同步锁。根
本达不到同步的目的。
同步代码一定要写成如下的形式,才有意义。
public static final Object lock1 = new Object();
… f1() {
synchronized(lock1){ // lock1 是公用同步锁
// 代码段 A
// 访问共享资源 resource1
// 需要同步
}
你不一定要把同步锁声明为 static 或者 public,但是你一定要保证相关的同步代码之间,一
定要使用同一个同步锁。
讲到这里,你一定会好奇,这个同步锁到底是个什么东西。为什么随便声明一个 Object 对
象,就可以作为同步锁?
在 Java 里面,同步锁的概念就是这样的。任何一个 Object Reference 都可以作为同步锁。我
们可以把 Object Reference 理解为对象在内存分配系统中的内存地址。因此,要保证同步代
码段之间使用的是同一个同步锁,我们就要保证这些同步代码段的 synchronized 关键字使
用的是同一个 Object Reference,同一个内存地址。这也是为什么我在前面的代码中声明
lock1 的时候,使用了 final 关键字,这就是为了保证 lock1 的 Object Reference 在整个系统
运行过程中都保持不变。
一些求知欲强的读者可能想要继续深入了解 synchronzied(同步锁)的实际运行机制。Java 虚
拟机规范中(你可以在 google 用“JVM Spec”等关键字进行搜索),有对 synchronized 关键
字的详细解释。synchronized 会编译成 monitor enter, … monitor exit 之类的指令对。Monitor
就是实际上的同步锁。每一个 Object Reference 在概念上都对应一个 monitor。
这些实现细节问题,并不是理解同步锁模型的关键。我们继续看几个例子,加深对同步锁
模型的理解。
public static final Object lock1 = new Object();
… f1() {
synchronized(lock1){ // lock1 是公用同步锁
// 代码段 A
// 访问共享资源 resource1
// 需要同步
}
}
… f2() {
synchronized(lock1){ // lock1 是公用同步锁
// 代码段 B
// 访问共享资源 resource1
// 需要同步
}
}
上述的代码中,代码段 A 和代码段 B 就是同步的。因为它们使用的是同一个同步锁
lock1。
如果有 10 个线程同时执行代码段 A,同时还有 20 个线程同时执行代码段 B,那么这 30 个
线程之间都是要进行同步的。
这 30 个线程都要竞争一个同步锁 lock1。同一时刻,只有一个线程能够获得 lock1 的所有权,
只有一个线程可以执行代码段 A 或者代码段 B。其他竞争失败的线程只能暂停运行,进入
到该同步锁的就绪(Ready)队列。
每一个同步锁下面都挂了几个线程队列,包括就绪(Ready)队列,待召(Waiting)队列
等。比如,lock1 对应的就绪队列就可以叫做 lock1 - ready queue。每个队列里面都可能有多
个暂停运行的线程。
注意,竞争同步锁失败的线程进入的是该同步锁的就绪(Ready)队列,而不是后面要讲述
的待召队列(Waiting Queue,也可以翻译为等待队列)。就绪队列里面的线程总是时刻准
备着竞争同步锁,时刻准备着运行。而待召队列里面的线程则只能一直等待,直到等到某
个信号的通知之后,才能够转移到就绪队列中,准备运行。
成功获取同步锁的线程,执行完同步代码段之后,会释放同步锁。该同步锁的就绪队列中
的其他线程就继续下一轮同步锁的竞争。成功者就可以继续运行,失败者还是要乖乖地待
在就绪队列中。
因此,线程同步是非常耗费资源的一种操作。我们要尽量控制线程同步的代码段范围。同
步的代码段范围越小越好。我们用一个名词“同步粒度”来表示同步代码段的范围。
同步粒度
在 Java 语言里面,我们可以直接把 synchronized 关键字直接加在函数的定义上。
比如。
… synchronized … f1() {
// f1 代码段
}
这段代码就等价于
… f1() {
synchronized(this){ // 同步锁就是对象本身
// f1 代码段
}
}
同样的原则适用于静态(static)函数
比如。
… static synchronized … f1() {
// f1 代码段
}
这段代码就等价于
…static … f1() {
synchronized(Class.forName(…)){ // 同步锁是类定义本身
// f1 代码段
}
}
但是,我们要尽量避免这种直接把 synchronized 加在函数定义上的偷懒做法。因为我们要
控制同步粒度。同步的代码段越小越好。synchronized 控制的范围越小越好。
我们不仅要在缩小同步代码段的长度上下功夫,我们同时还要注意细分同步锁。
比如,下面的代码
public static final Object lock1 = new Object();
… f1() {
synchronized(lock1){ // lock1 是公用同步锁
// 代码段 A
// 访问共享资源 resource1
// 需要同步
}
}
… f2() {
synchronized(lock1){ // lock1 是公用同步锁
剩余27页未读,继续阅读
资源评论
home_dog
- 粉丝: 0
- 资源: 3
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
最新资源
- 基于Matlab人脸肤色定理的教师人数统计+源代码+全部数据+文档说明+详细注释+使用说明+截图(高分课程设计)
- 基于Matlab霍夫曼变换的表盘读数识别+源代码+全部数据+文档说明+详细注释+使用说明+截图(高分课程设计)
- 基于Matlab火灾烟雾检测源码带GUI界面+源代码+全部数据+文档说明+详细注释+使用说明+截图(高分课程设计)
- 基于Matlab的恶劣天气交通标志识别系统+源代码+全部数据+文档说明+详细注释+使用说明+截图(高分课程设计)
- 基于MATLAB的霍夫曼变换的表盘示数识别+源代码+全部数据+文档说明+详细注释+使用说明+截图(高分课程设计)
- 基于Matlab的车道线识别系统 +源代码+全部数据+文档说明+详细注释+使用说明+截图(高分课程设计)
- 基于MATLAB的教室人数统计系统带Gui界面+源代码+全部数据+文档说明+详细注释+使用说明+截图(高分课程设计)
- 基于MATLAB的教室人数统计系统带Gui界面+源代码+全部数据+文档说明+详细注释+使用说明+截图(高分课程设计)
- 基于MATLAB 的霍夫曼变换答题卡识别源码+全部数据+文档说明+详细注释+使用说明+截图(高分课程设计)
- 基于Matlab+bp神经网络的神经网络汉字识别系统+源代码+全部数据+文档说明+详细注释+使用说明+截图(高分课程设计)
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功