多线程的锁
来源:互联网 发布:香港中文大学gpa算法 编辑:程序博客网 时间:2024/04/30 12:30
一、在没有锁的情况下,举例下面的例子
(1)Account类,账户类,包括账户号码和余额
package cn.test.synchronization.one;public class Account { private String accountNo; private double balance; public Account(String accountNo, double balance) { this.accountNo = accountNo; this.balance = balance; } public String getAccountNo() { return accountNo; } public void setAccountNo(String accountNo) { this.accountNo = accountNo; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } public int hashCode(){ return accountNo.hashCode(); } public boolean equals(Object obj){ if(obj != null && obj.getClass() == Account.class){ Account target = (Account)obj; return target.getAccountNo().equals(accountNo); } return false; }}
(2)DrawThread 取钱类,包括账户实体和取钱金额
package cn.test.synchronization.one;public class DrawThread extends Thread{ private Account account; private double drawAmount; public DrawThread(String name, Account account, double drawAmount){ super(name); this.account = account; this.drawAmount = drawAmount; } public void run(){ if(account.getBalance() >= drawAmount){ System.out.println(getName()+"取钱成功!吐出钞票:"+drawAmount); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } //修改余额 account.setBalance(account.getBalance() - drawAmount); System.out.println("\t 余额为:"+account.getBalance()); }else{ System.out.println(getName() + "取钱失败!余额不足"); } }}
注意这个类中的run()方法实现了取钱的过程,按道理这不是面向对象的思想在编程,在面向对象中有一个设计模式叫领域驱动模式,强调把类的功能应该放在类里面。
(3)测试类:
package cn.test.synchronization.one;public class TestDraw { public static void main(String[] args) { //创建一个账户 Account account = new Account("12345", 1000); new DrawThread("甲", account, 800).start(); new DrawThread("乙", account, 800).start(); }}
执行出现的结果:
乙取钱成功!吐出钞票:800.0甲取钱成功!吐出钞票:800.0 余额为:200.0 余额为:-600.0
明显,由于run方法线程不安全的问题出现了以上异常的结果。
由此我们引出了线程锁的问题。关于锁的问题,有三种方式:
1、snychronize(){} :括号里面是对竞争资源的锁,这种方式是对属性加锁,而属性就是竞争资源
2、public snychronize void 方法名(){} : 这种是对this进行加锁
3、前面两种锁不能显示地加锁和显示地解锁,而这种方式就能完美的解决。同步锁(Lock),在实现多线程的安全控制中,通常喜欢用ReentrantLock(可重入锁)。使用该所进行显示加锁、显示释放锁。
二、snychronize(){}
(1)Account类,同上
(2)DrawThread,取钱类
package cn.test.synchronization.two;public class DrawThread extends Thread{ private Account account; private double drawAmount; public DrawThread(String name, Account account, double drawAmount){ super(name); this.account = account; this.drawAmount = drawAmount; } public void run(){ //synchronized可以修饰方法,可以修饰代码块,但不能修饰构造器、属性等等 synchronized (account) { if(account.getBalance() >= drawAmount){ System.out.println(getName()+"取钱成功!吐出钞票:"+drawAmount); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } //修改余额 account.setBalance(account.getBalance() - drawAmount); System.out.println("\t 余额为:"+account.getBalance()); }else{ System.out.println(getName() + "取钱失败!余额不足"); } } }}
(3)测试类,同上
执行的结果是:
甲取钱成功!吐出钞票:800.0 余额为:200.0乙取钱失败!余额不足
显然,没有出现异常。
三、public snychronize void 方法名(){}
(1)Account类,账户类
package cn.test.synchronization.three;public class Account { private String accountNo; private double balance; public Account(String accountNo, double balance) { this.accountNo = accountNo; this.balance = balance; } public String getAccountNo() { return accountNo; } public void setAccountNo(String accountNo) { this.accountNo = accountNo; } public double getBalance() { return balance; } public int hashCode(){ return accountNo.hashCode(); } public boolean equals(Object obj){ if(obj != null && obj.getClass() == Account.class){ Account target = (Account)obj; return target.getAccountNo().equals(accountNo); } return false; } //将draw()方法放在Account类中更符合面向对象的思想,在面向对象有一种设计模式叫 //领域驱动模式 //凡事具有两面性,加了synchronized方法使得可变类更加的安全,但是这种安全是有代价的, //它是以降低程序的运行效率作为代价,所以我们有以下两点建议: //1、不对不可变类进行线程同步,不需要;只对可变类的竞争资源进行同步,对可变类的其他资源不 // 进行同步,因为这会影响运行效率 //2、可变类运行在两种运行环境(1、单线程环境,2、多线程环境),那么最好为可变类提供两个版本 /// 这样可以保证性能 public synchronized void draw(double drawAmount){ if(this.getBalance() >= drawAmount){ System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票:"+drawAmount); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } //修改余额 this.balance = this.balance - drawAmount; System.out.println("\t 余额为:"+this.getBalance()); }else{ System.out.println(Thread.currentThread().getName() + "取钱失败!余额不足"); } }}
(2)DrawThread,取钱类
package cn.test.synchronization.three;public class DrawThread extends Thread{ private Account account; private double drawAmount; public DrawThread(String name, Account account, double drawAmount){ super(name); this.account = account; this.drawAmount = drawAmount; } public void run(){ account.draw(drawAmount); }}
(3)测试类,同上
运行结果与二相同。
=================================
可变类与不可变类讲解
凡事具有两面性,加了synchronized使得可变类更加的安全,但是这种安全是有代价的,它是以降低程序的运行效率作为代价,所以我们有以下两点建议:
1、不对不可变类进行线程同步,不需要;只对可变类的竞争资源进行同步,对可变类的其他资源不进行线程同步,因为这会影响运行效率
2、可变类运行在两种运行环境(1、单线程环境,2、多线程环境),那么最好为可变类提供两个版本,这样可以保证性能
前面两种线程同步的方式都无法显示地释放同步监视器,那么什么时候才会自动地释放同步监视器呢?
1、以下情况会自动释放同步监视器:
(1)同步方法或同步代码块已经执行完毕。包括(同步方法或同步代码块执行完最后一行、遇到break跳出同步方法或同步代码块、遇到return终止执行同步方法或同步代码块)
(2)当线程在同步方法或同步代码块遇到未处理的Error或Exception,导致异常结束时会释放同步监视器
(3)同步监视器监视的对象执行wait()方法,释放同步监视器
2、以下情况不会自动释放同步监视器:
(1)线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()(线程让步)方法来暂停当前线程的执行、当前线程不会释放同步监视器
(2)线程执行同步代码块或同步方法时,其他线程调用了该线程的suspend方法将该线程挂起,该线程不会释放同步监视器
=================================
三、同步锁
(1)Account类,账户类
package cn.test.synchronization.four;import java.util.concurrent.locks.ReentrantLock;public class Account { //定义锁对象,可重入锁 private final ReentrantLock lock = new ReentrantLock(); private String accountNo; private double balance; public Account(String accountNo, double balance) { this.accountNo = accountNo; this.balance = balance; } public String getAccountNo() { return accountNo; } public void setAccountNo(String accountNo) { this.accountNo = accountNo; } public double getBalance() { return balance; } public int hashCode(){ return accountNo.hashCode(); } public boolean equals(Object obj){ if(obj != null && obj.getClass() == Account.class){ Account target = (Account)obj; return target.getAccountNo().equals(accountNo); } return false; } //将draw()方法放在Account类中更符合面向对象的思想,在面向对象有一种设计模式叫 //领域驱动模式 //凡事具有两面性,加了synchronized方法使得可变类更加的安全,但是这种安全是有代价的, //它是以降低程序的运行效率作为代价,所以我们有以下两点建议: //1、不对不可变类进行线程同步,不需要;只对可变类的竞争资源进行同步,对可变类的其他资源不 // 进行同步,因为这会影响运行效率 //2、可变类运行在两种运行环境(1、单线程环境,2、多线程环境),那么最好为可变类提供两个版本 /// 这样可以保证性能 public void draw(double drawAmount){ //对同步锁进行加锁 lock.lock(); try{ if(this.getBalance() >= drawAmount){ System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票:"+drawAmount); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } //修改余额 this.balance = this.balance - drawAmount; System.out.println("\t 余额为:"+this.getBalance()); }else{ System.out.println(Thread.currentThread().getName() + "取钱失败!余额不足"); } }finally { lock.unlock(); } }}
(2)DrawThread类,取钱类
package cn.test.synchronization.four;public class DrawThread extends Thread{ private Account account; private double drawAmount; public DrawThread(String name, Account account, double drawAmount){ super(name); this.account = account; this.drawAmount = drawAmount; } public void run(){ account.draw(drawAmount); }}
(3)测试类,同上
===============
避免死锁的几个方法:
·避免一个线程同时获取多个锁。
·避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
·尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
·对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
- C#多线程的锁
- 【多线程】锁的类型
- 多线程-锁的类型
- 多线程的锁
- 多线程的自旋锁
- 多线程的C++锁
- 多线程的原子锁
- 多线程的读写锁
- 多线程的嵌套锁
- 多线程的顺序锁
- 多线程 : 锁的用法
- 多线程的锁
- 多线程的锁
- 多线程的应用 多线程
- 多线程(多线程的安全问题)
- 【多线程】多线程的使用
- iOS多线程 - NSThread锁的使用(多线程资源共享的问题)
- 多线程中锁的实现.
- Web服务器 - nginx for windows
- centos 安装ss-QT5
- spring MVC原理!
- pat1038. Recover the Smallest Number
- 将一段时间按照一星期一星期的切割开
- 多线程的锁
- 13.SharedPreference的使用
- Hive的使用之hwi
- 建议33:避免在泛型类型中声明静态成员
- Android10--Android之动态代理详解
- Service学习之路(03)之绑定本地Service并与之通信
- 13.13.用文件存储和MainServices实现页面跳转问题
- Android Studio 打包jar,aar
- 删除文件和文件夹