ITKeyword,专注技术干货聚合推荐

注册 | 登录

JAVA 多线程--线程同步安全

YSC1123 分享于 2016-07-31

推荐:Java多线程安全

package SwingExample;public class MultiThreadSafe implements Runnable{ private int nTicketCount = 10; // 剩余火车票数量 private String strPurchas

  每当我们在项目中使用多线程的时候,我们就不得不考虑线程的安全问题,而与线程安全直接挂钩的就是线程的同步问题。而在java的多线程中,用来保证多线程的同步安全性的主要有三种方法:同步代码块,同步方法和同步锁。下面就一起来看:

一、引言        

   最经典的线程问题:去银行存钱和取钱的问题,现在又甲乙两个人去同一个账户中取款,每人取出800,但是账户中一共有1000元,从逻辑上来讲,如果甲取走800,那么乙一定取不出来800:

   首先定义一个account.java  个人账户的实体类:

复制代码
 1 /**  2  * 模仿银行取钱的经典问题:在当前的账户类中封装账户编号和余额两个属性  3  *  4  * @author root  5  *  6 */  7 public class Account {  8  9 private String accountNo; 10 private double balance; 11 12 public Account() { 13  } 14 15 // 构造器 16 public Account(String accountNo, double balance) { 17 this.accountNo = accountNo; 18 this.balance = balance; 19  } 20 21 public String getAccountNo() { 22 return accountNo; 23  } 24 25 public void setAccountNo(String accountNo) { 26 this.accountNo = accountNo; 27  } 28 29 public double getBalance() { 30 return balance; 31  } 32 33 public void setBalance(double balance) { 34 this.balance = balance; 35  } 36 37 // 下面两个方法根据accountNo来计算account的hashcode和判断equals 38 public int hashCode() { 39 return accountNo.hashCode(); 40  } 41 42 public boolean equals(Object obj) { 43 if (obj != null && obj.getClass() == Account.class) { 44 Account target = (Account) obj; 45 return target.getAccountNo().equals(accountNo); 46  } 47 return false; 48  } 49 50 // 使用同步方法提供一个线程安全的draw方法来完成取钱的操作 51 public synchronized void draw(double drawAmount) { 52 // 账户余额大于取钱的数目 53 if (balance >= drawAmount) { 54 // 吐出钞票 55 System.out.println(Thread.currentThread().getName() + "取钱成功,吐出钞票!" + drawAmount); 56 57 try { 58 Thread.sleep(1); 59 } catch (InterruptedException e) { 60  e.printStackTrace(); 61  } 62 // 修改余额 63 balance -= drawAmount; 64 System.out.println("\t 余额为: " + balance); 65 } else { 66 System.out.println(Thread.currentThread().getName() + "取钱失败,余额不足"); 67  } 68  } 69 70 }
复制代码

  之后写一个模仿取钱的线程类:

复制代码
 1 package thread.threadInBank;  2  3 /**  4  * 取钱的线程类,该线程类根据执行账户,取钱的数量进行取钱操作,取钱的逻辑是当其余额不足时无法提取现金,当余额足够时系统吐出  5  * 钞票,余额对应的减少  6  * @author root  7  *  8 */  9 public class DrawThread extends Thread{ 10 11 //模拟用户账户 12 private Account account; 13 14 //当前取钱线程所希望取出的的钱数 15 private double drawAmount; 16 17 public DrawThread(String name,Account account,double drawAmount){ 18 super(name); 19 this.account = account; 20 this.drawAmount= drawAmount; 21  } 22 23 //当多条线程修改同一个共享数据时,将涉及数据安全问题 24 public void run(){ 25 //账户余额大于取钱的数目 26 if (account.getBalance() >= drawAmount) { 27 //吐出钞票 28 System.out.println(getName()+ "取钱成功,吐出钞票!" + drawAmount); 29 30 // try { 31 // Thread.sleep(1); 32 // } catch (InterruptedException e) { 33 // e.printStackTrace(); 34 // } 35 //修改余额 36 account.setBalance(account.getBalance() - drawAmount); 37 System.out.println("\t 余额为: " + account.getBalance()); 38 }else { 39 System.out.println(getName() + "取钱失败,余额不足"); 40  } 41  } 42 }
复制代码

  写一个测试方法,来测试当前的取钱操作:

复制代码
 1 package thread.threadInBank;  2  3 public class testDraw {  4 public static void main(String[] args) {  5 //创建一个用户  6 Account acct = new Account("1234567",1000);  7 //模拟两个线程对同一个账户取钱  8 new DrawThread("甲", acct, 800).start();  9 new DrawThread("乙", acct, 800).start(); 10 11  } 12 }
复制代码

  乍一看,上面的程序好像也没有什么问题,但是多次运行之后会出现下面两种结果:

         

     所以这样的程序肯定存在问题,那么我们应该如何对上述程序进行更改,使得当前的账户不能取第二次800;这就必须提到java提供的线程同步的第一种方法:同步代码块

二、同步代码块

   我们很容易发现上述程序出现问题是因为:当前run方法的方法体不具备同步安全性,而程序中的两个并发线程(甲乙两次取钱)都在修改该账户,所以为了解决这个问题,我们需要使用java多线程引入的同步监视器来解决,也就是将当前取钱的那段代码使用synchronized 关键字修饰,也就是使之成为同步代码块:

推荐:Java Tread多线程(2)多线程安全问题

作者 :卿笃军 原文地址:http://blog.csdn.net/qingdujun/article/details/39348093 本文演示,Tread多线程安全问题,以及几种解决多线程安全方式(线程同步)

复制代码
package thread.threadInBank; /** * 取钱的线程类,该线程类根据执行账户,取钱的数量进行取钱操作,取钱的逻辑是当其余额不足时无法提取现金,当余额足够时系统吐出 钞票,余额对应的减少 * * @author root * */ 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作为同步监视器,任何线程进入下面的同步代码之前 // 必须Ian获得对account账户的锁定--其他线程无法获得锁,也就无法修改它 // 这种做法符合:加锁--》 修改完成 -- 》释放锁 的逻辑 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() + "取钱失败,余额不足"); } } //同步代码块结束,该线程释放同步锁  } }
复制代码

   这样无论何时都会保证当前账户中无法由乙提取出第二个800,:

  所以这种方法保证了当前只有一个线程可以处于临界区(修改共享资源的代码区),从而保证了线程的安全性;

三、同步方法

  其实同步方法和同步代码块类似,都是使用sychronized关键字。只是这次是修饰整个方法,而这个被修饰的方法就被称为同步方法:

复制代码
 1 // 使用同步方法提供一个线程安全的draw方法来完成取钱的操作  2 public synchronized void draw(double drawAmount) {  3 // 账户余额大于取钱的数目  4 if (balance >= drawAmount) {  5 // 吐出钞票  6 System.out.println(Thread.currentThread().getName() + "取钱成功,吐出钞票!" + drawAmount);  7  8 try {  9 Thread.sleep(1); 10 } catch (InterruptedException e) { 11  e.printStackTrace(); 12  } 13 // 修改余额 14 balance -= drawAmount; 15 System.out.println("\t 余额为: " + balance); 16 } else { 17 System.out.println(Thread.currentThread().getName() + "取钱失败,余额不足"); 18  } 19 }
复制代码

   这样我们直接在测试类中调用上面的同步方法就可以了,这样可以保证多条线程并发调用draw方法且不会出现问题;

四、同步锁:Lock

   其实同步锁就是显式地对当前程序加锁,而且每次只能有一个线程对lock对象加锁。所以我们使用同步锁的方法改善银行取钱问题:

复制代码
 1 package thread.threadInBank;  2  3 import java.util.concurrent.locks.ReentrantLock;  4  5 /**  6  * 模仿银行取钱的经典问题:在当前的账户类中封装账户编号和余额两个属性  7  *  8  * @author root  9  * 10 */ 11 public class Account_lock { 12 13 //定义锁对象 14 private final ReentrantLock lock= new ReentrantLock(); 15 private String accountNo; 16 private double balance; 17 18 public Account_lock() { 19  } 20 21 // 构造器 22 public Account_lock(String accountNo, double balance) { 23 this.accountNo = accountNo; 24 this.balance = balance; 25  } 26 27  省略了属性的get和set方法 28  省略了equals和hashCode两个方法 29 30 // 使用同步方法提供一个线程安全的draw方法来完成取钱的操作 31 public void draw(double drawAmount) { 32 //对同步锁进行加锁 33  lock.lock(); 34 try { 35 // 账户余额大于取钱的数目 36 if (balance >= drawAmount) { 37 // 吐出钞票 38 System.out.println(Thread.currentThread().getName() + "取钱成功,吐出钞票!" + drawAmount); 39 40 try { 41 Thread.sleep(1); 42 } catch (InterruptedException e) { 43  e.printStackTrace(); 44  } 45 // 修改余额 46 balance -= drawAmount; 47 System.out.println("\t 余额为: " + balance); 48 } else { 49 System.out.println(Thread.currentThread().getName() + "取钱失败,余额不足"); 50  } 51 //使用finally块来确保释放锁 52 } finally{ 53  lock.unlock(); 54  } 55 56  } 57 }

      添加一个测试类,测试当前的取钱操作:

 1 package thread.threadInBank;  2  3 /**  4  * 取钱的线程类,该线程类根据执行账户,取钱的数量进行取钱操作,取钱的逻辑是当其余额不足时无法提取现金,当余额足够时系统吐出 钞票,余额对应的减少  5  *  6  * @author root  7  *  8 */  9 public class DrawThread extends Thread { 10 private Account_lock account_lock; 11 12 // 当前取钱线程所希望取出的的钱数 13 private double drawAmount; 14 15 public DrawThread(String name, Account_lock account_lock, double drawAmount) { 16 super(name); 17 this.account_lock = account_lock; 18 this.drawAmount = drawAmount; 19  } 20 21 // 当多条线程修改同一个共享数据时,将涉及数据安全问题 22 public void run() { 23 //直接调用使用同步锁的方法 24  account_lock.draw(drawAmount); 25 //同步代码块结束,该线程释放同步锁 26  } 27 }

  可以发现,当前的方法依旧可以实现取钱成功的操作;

  但是可能会有人担心,线程的同步会不会影响程序的性能?

  所以我们在使用多线程的时候,要尽可能的只对会改变竞争资源的方法进行同步,并且在多线程环境中使用线程安全的版本,这样尽可能地减少多线程安全给我们带来的负面影响;

推荐:Java线程同步与多线程

  20120408 交通灯、银行项目都涉及到了多线程。先搞懂线程里大概的知识点吧。 一、进程与线程 首先就是进程与线程的区别。我们在操作系统中运行一个程序,都会

  每当我们在项目中使用多线程的时候,我们就不得不考虑线程的安全问题,而与线程安全直接挂钩的就是线程的同步问题。而在java的多线程中,用来保证多线程的同步安全性的主要有三种方法:同步代

相关阅读排行


用户评论

游客

相关内容推荐

最新文章

×

×

请激活账号

为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。

您的注册邮箱: 修改

重新发送激活邮件 进入我的邮箱

如果您没有收到激活邮件,请注意检查垃圾箱。