您的位置:首页 > 编程语言 > Java开发

Java多线程设计(二)线程的基本知识(2)共享互斥

2016-12-18 16:31 429 查看

1. 概述

接着上一章内容Java多线程设计(二)线程的基本知识(1),接下来就要谈一下线程的共享互斥

2. 线程的共享互斥

多个线程在操作到同一个实例的时候,就可能会造成重大的灾难。最经典的就是银行取款的例子。

在去银行取款的时候,比如说你取100,程序先判断你的余额够不够100,不够就取款失败,够就给你100然后将你的余额减去100。模拟代码如下

public class Bank {

private int money;// 客户余额

/**
* 取款
*
* @param m
*            取款的数目
* @return 取款成功返回true,取款失败返回false
*/
public boolean withdraw(int m) {
if (money >= m) {// 判断余额是否大于等于取款数
money -= m;// 取款
return true;// 取款成功
} else {
return false;// 取款失败
}
}
// ...其它业务方法
}


现在问题来了,如果有两个线程在同时执行取款业务的这段代码也就是
withdraw
方法时,就有可能发生下面的问题。

假设现在余额就剩100,要取款的数目也是100。





上面的问题有点像是我们的交通,如果十字路口没有红绿灯的话,那么直向行车和横向行车就容易交错在一起,酿成事故。但是有了红绿灯就不同,当直向行车是绿灯的时候,横向行车一定是红灯,这样子如果大家都遵守交通规则的话,就不会发生直向的车和横向的车交错的情况。在Java的线程中,这个红绿灯就是
synchronized


3. synchronized方法

当一个方法加上关键字
synchronized
声明后,就可以让该方法1次只能被1个线程操作,这种方法就是synchronized方法,也称同步方法

那么现在就可以改写一下上面的银行取款的例子,在取款方法上加上synchronized关键字

public class Bank {

private int money;// 客户余额

/**
* 取款
*
* @param m
*            取款的数目
* @return 取款成功返回true,取款失败返回false
*/
public synchronized boolean withdraw(int m) {
if (money >= m) {
money -= m;
return true;
} else {
return false;
}
}
// ...其它业务方法
}


这样子一来,就不可能会出现上面讲到的两个线程都执行了取款操作导致余额变负数,因为当一个线程执行这个方法的时候,其它线程就不能执行这个方法了。

需要注意的是,当你调用一个实例的synchronized方法后,该实例的其它synchronized方法就不能被调用先了,换句话说就是不能同时调用一个实例中两个或两个以上的synchronized方法。比如说上面的
Bank
还有一个synchronized方法是存款方法
deposit()
,当取款方法
withdraw
在被执行的时候存款方法
deposit
就不能被执行。

这里面涉及到一个锁的概念,具体的我在后续内容会提到。大概的意思是,把一个实例中所有的synchronized方法都放在一个房间里,当一个线程进入房间去执行其中的某一个synchronized方法时,会把房间门锁上让别的线程执行不了房间里的synchronized方法。而每个实例,只有这么一个房间这么一个门(就是每个实例都会有一个锁定的意思)。

注意了,上面是讲一个实例内的synchronized方法,如果是多个这样子的实例,其中一个实例中的synchronized方法被执行时其它实例的synchronized方法也可以被执行,它们之间并不干扰。

还需要注意的是,上面说的都是synchronized方法,普通方法就没有说不能被两个或两个以上线程同时执行的限制。

4. synchronized代码块和synchronized类方法

如果是想让方法的某一部分代码不能同时被多个线程执行,则可以使用synchronized代码块

synchronized(表达式){
...
}


synchronized实例方法和synchronized代码块

假设现在有一个synchronized实例方法:

synchronized void method(){
....
}


在功能上,它跟下面这个synchronized代码块也称
同步代码块
有着异曲同工之妙:

void method(){
synchronized(this){
...
}
}


一个实例的synchronized方法和synchronized代码块都放在一个房间里,其中
synchronized(this)
中的
this
就代表那个门。无论是执行synchronized方法或则是synchronized代码块的线程都会把这扇门锁上,阻挡了其它想进入这个门所在的房间的线程。

这里有个地方要注意一下,
synchronized(this)
this
也可以是其它实例对象,比如说
new Object()
,这样一来的话,这个代码块和其它synchronized方法就不在一个房间里了,执行实例的synchronized方法同时也可以执行这个代码块。

synchronized类方法和synchronized代码块

假设现在有一个synchronized类方法:

class Something{
static synchronized void method(){
...
}
}


在功能上,它跟下面这个synchronized代码块有着异曲同工之妙:

class Something{
static void method(){
synchronized(Something.class){
...
}
}


说明synchronized的类方法和
synchronized(Something.class)
代码块是放在一个房间的,也是只允许一个线程在进入这个房间执行其中的synchronized的类方法或者
synchronized(Something.class)
代码块。

总结来说,一个实例负责一个房间的门,比如说
Something
类中,synchronized实例方法持有的实例是
this
,synchronized类方法持有的实例是
Something.class
,而其它代码块持有的实例是括号里面的表达式最终的值,如
synchronized(new Object())
持有的实例就是一个
Object
实例,持有同一个实例(也就是锁)的同步方法和同步代码块是在一个房间的。如下图所示



其中绿色的箭头代表线程,可以看见一个房间只能运行一个线程进入,其它被特定的“门”阻挡了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐