t1: 2 t1: 3 t1: 4 t1: 5 t1: 6 t1: 7 t1: 8 t1: 9
6.5线程同步互斥
1、线程同步互斥的一个示例
多个线程同时访问或操作同一资源时,很容易出现数据前后不一致的问题。请看下面的例子:
男孩拿着折子去北京银行海淀分行取钱
女孩拿着男孩的银行卡去西单百货疯狂购物
男孩走到柜台钱询问帐户余额
银行的业务员小姐亲切地告诉他:\您还有10000元!\。
女孩看上了一件时髦的衣裳,准备买下 女孩到收银台准备刷卡消费
收银台刷卡机读取银行卡余额为10000元 女孩买衣服刷卡消费5000元
消费清单打印出来,消费:5000元 余额:女孩离开商场
男孩在思考要取多少钱呢?
5000元
男孩思考了1毫秒 男孩决定取5000元
银行的业务员小姐为男孩办理相关业务手续 交易完成
银行的业务员小姐告诉男孩:\您的余额为5000元\。 男孩离开银行
男孩帐户中一共有10000元,男孩拿着存折从银行取走5000元,女孩拿着男孩的银行卡购物刷卡消费5000元,最后男孩的帐户里却还剩5000元。显然这是不正确的,但是为什么会发生这样的情况呢?我们可以这样分析:男孩可以看作是一条线程,女孩也可以看作是一条线程,在同一时刻,两个线程都操作了同一个资源,那就是男孩的帐户。男孩从查看帐户余额到取走现金应该被看作是个原子性操作,是不可再分的,然而当男孩查看完余额正思考取多少钱的时候,女孩购物消费了5000元,也就是说女孩这条线程打断了男孩这条线程所要执行的任务。所以男孩刚查看完的余额10000元就不正确了,最终导致帐户中少减了5000元。
为了避免这样的事情发生,我们要保证线程同步互斥,所谓同步互斥就是:并发执行的多个线程在某一时间内只允许一个线程在执行以访问共享数据
2、Java中线程互斥的实现机制
由多线程带来的性能改善是以可靠性为代价的,所以编程出线程安全的类代码是十分必要的。当多个线程可以访问共享资源(调用单个对象的属性和方法,对数据进行读、写、修改、删除等操作)时,应保证同时只有一个线程访问共享数据,Java对此提出了有效的解决方案—同步锁。任何线程要进入同步互斥方法(访问共享资源的方法或代码段)时,就必须得到这个共享资源对象的锁,线程进入同步互斥方法后其它线程则不能再进入同步互斥方法,直到拥有共享资源对象锁的线程执行完同步互斥方法释放了锁,下一个线程才能进入同步互斥方法被执行。
Java的这一线程互斥的实现机制可以用一个最通俗的比方来说明:比如公共卫生间就是一个共享资源,每个人都可以使用,但又不能同时使用,所以卫生间里有一把锁。一个人进去了,会把门锁上,其他人就不能进去。当Ta出来的时候,要打开锁,下一个人才能继续使用。
3、利用Synchronized关键字用于修饰同步互斥方法
(1)同步互斥方法
public synchronized void method(){ //允许访问控制的代码 }
(2)同步互斥代码块
synchronized(syncObject){ //允许访问控制的代码
}
(3)锁定整个类
public synchronized class SyncObject{ }
由于synchronized 块可以针对任意的代码块,且可任意指定上锁的对象,因此灵活性较高。但要注意:
? synchronized可以用来限定一个方法或一小段语句或整个类(该类中的所有方法都
是synchronized方法)
? 将访问共享数据的代码设计为synchronized方法
? 由于可以通过 private 关键字来保证数据对象只能被方法访问,所以只需针对方法
提出一套同步锁定机制。通过synchronized 方法来控制对类中的成员变量(共享数据)的访问。
? 编写线程安全的代码会使系统的总体效率会降低,要适量使用
? 只有某一个线程的synchronized方法执行完后其它线程的synchronized方法才能被
执行。
? 当前时间,只有一个线程访问被锁定的代码段,但不能保证其他线程去访问其他没
有被锁定的代码段。因此所有对共享资源进行操作的代码段都应该加锁。 ? 对数据库操作时,修改数据的线程要加锁,而读数据的线程可以不加锁
有了这种解决方案,我们用线程安全的代码来重新实现一下男孩和女孩取钱的故事。以下是核心代码:
package com.px1987.j2se.thread.synchronous.v2; /** 帐户类 */
public class Account {
}
/** 余额 */
private int balance;
public Account(int balance) { }
this.balance = balance;
package com.px1987.j2se.thread.synchronous.v2; /** 男孩类,实现Runnable接口*/
public class Boy implements Runnable { }
package com.px1987.j2se.thread.synchronous.v2; /** 女孩类,实现runnable接口*/
/** 银行帐户*/ Account account; }
this.account = account;
public Boy(Account account) {
/** 男孩拿着折子去北京银行海淀分行取钱*/ public void run() { }
System.out.println(\男孩拿着折子去北京银行海淀分行取钱\synchronized (account) { }
System.out.println(\男孩离开银行\
System.out.println(\男孩走到柜台钱询问帐户余额\int balance = account.getBalance();
System.out.println(\银行的业务员小姐亲切地告诉他:\\\您还有\
balance + \元!\\\。\
try { }
catch (InterruptedException e) { }
int money = 5000;
System.out.println(\男孩决定取\元\
System.out.println(\银行的业务员小姐为男孩办理相关业务手续\account.setBalance(balance - money); System.out.println(\交易完成\
System.out.println(\银行的业务员小姐告诉男孩:\\\您的余额为\
account.getBalance()+ \元\\\。\
e.printStackTrace();
System.out.println(\男孩在思考要取多少钱呢?\Thread.sleep(1);
System.out.println(\男孩思考了1毫秒\
public class Girl implements Runnable {
/** 女孩持有男孩的银行卡*/ Account account;
public Girl(Account account) { }
/*** \女孩拿着小军的银行卡去西单百货疯狂购物*/ public void run() {
String tabs = \
System.out.println(tabs + \女孩拿着小军的银行卡去西单百货疯狂购物\System.out.println(tabs + \女孩看上了一件时髦的衣裳,准备买下\synchronized (account) {
System.out.println(tabs + \女孩到收银台准备刷卡消费\int balance = account.getBalance();
System.out.println(tabs + \收银台刷卡机读取银行卡余额为\元int payout = 5000;
System.out.println(tabs + \女孩买衣服刷卡消费\元\account.setBalance(balance - payout);
System.out.println(tabs + \消费清单打印出来,消费:\元\
+ account.getBalance() + \元\
}
package com.px1987.j2se.thread.synchronous.v2; public class Bank { }
public static void main(String[] args) { }
Account account=new Account(10000);
Thread boyThread=new Thread(new Boy(account)); Thread girlThread=new Thread(new Girl(account)); boyThread.start(); girlThread.start(); }
}
System.out.println(tabs + \女孩离开商场\
this.account = account;
\
余额:\
修改后的代码运行结果如下图:
男孩拿着折子去北京银行海淀分行取钱
女孩拿着小军的银行卡去西单百货疯狂购物 女孩看上了一件时髦的衣裳,准备买下 女孩到收银台准备刷卡消费
收银台刷卡机读取银行卡余额为10000元 女孩买衣服刷卡消费5000元
消费清单打印出来,消费:5000元 余额:女孩离开商场
5000元
男孩走到柜台钱询问帐户余额
银行的业务员小姐亲切地告诉他:\您还有5000元!\。 男孩在思考要取多少钱呢? 男孩思考了1毫秒 男孩决定取5000元
银行的业务员小姐为男孩办理相关业务手续 交易完成
银行的业务员小姐告诉男孩:\您的余额为0元\。 男孩离开银行
从结果中可以看出来,男孩从查看余额到取钱,女孩没有操作帐户,所以最后的余额是正确的。 4、线程死锁
使用互斥锁容易产生死锁问题。比如:一个线程需要锁定两个对象才能完成,线程1拥有对象A的锁,线程1如果再拥有对象B的锁就能完成操作,线程2拥有对象B的锁,线程2如果再拥有对象A的锁就能完成操作。
很不幸的是线程1执行不下去了,因为线程1等待的资源对象B被线程2锁住了,线程2也执行不下去了,因为线程2等待的资源对象A被线程1锁住了,这样就造成了死锁。
阅读一段文字:由多线程带来的性能改善是以可靠性为代价的,主要是因为有可能产生线程死锁。死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不能正常运行。简单的说就是:线程死锁时,第一个线程等待第二个线程释放资源,而同时第二个线程又在等待第一个线程释放资源。这里举一个通俗的例子:如在人行道上两个人迎面相遇,为了给对方让道,两人同时向一侧迈出一步,双方无法通过,又同时向另一侧迈出一步,这样还是无法通过。假设这种情况一直持续下去,这样就会发生死锁现象。
导致死锁的根源在于不适当地运用“synchronized”关键词来管理线程对特定对象的访问。“synchronized”关键词的作用是,确保在某个时刻只有一个线程被允许执行特定的代码块,因此,被允许执行的线程首先必须拥有对变量或对象的排他性访问权。当线程访问对象时,线程会给对象加锁,而这个锁导致其它也想访问同一对象的线程被阻塞,直至第一个线程释放它加在对象上的锁。
(1)死锁问题的一个代码示例
package com.px1987.j2se.thread.DeadLock; class Thread1 implements Runnable {
private Object a; private Object b;
public Thread1(Object a, Object b) { }
public void run() {
super(); this.a = a; this.b = b;
百度搜索“77cn”或“免费范文网”即可找到本站免费阅读全部范文。收藏本站方便下次阅读,免费范文网,提供经典小说综合文库Java线程及多线程技术及应用(3)在线全文阅读。
相关推荐: