多线程编程(四)--线程同步

 当使用多个线程来访问同一个数据时,就容易出现线程安全的问题。例如,银行取钱。当我们去自动取款机取钱时,正好另一个人转账,即多个线程修改同一数据,这时就容易出现线程安全问题。


线程安全

[java]  view plain  copy
  1. /** 
  2.  * 账户类,该类封装了账户编号和余额两个属性 
  3.  * @author Emily-T 
  4.  * 
  5.  */  
  6. public class Account {  
  7.   
  8.     //账户编号  
  9.     private String accountNo;  
  10.     //余额  
  11.     private double balance;  
  12.     public Account(){}  
  13.       
  14.     //构造函数  
  15.     public Account(String accountNo,double balance){  
  16.         this.accountNo = accountNo;  
  17.         this.balance = balance;  
  18.     }  
  19.       
  20.     //下面两个方法根据accountNo来计算Account的hashCode和判断equals  
  21.     public int hashCode(){  
  22.         return accountNo.hashCode();  
  23.     }  
  24.       
  25.     public boolean equals(Object obj){  
  26.         if (obj != null && obj.getClass() == Account.class) {  
  27.             Account target = (Account) obj;  
  28.             return target.getAccountNo().equals(accountNo);  
  29.         }  
  30.         return false;  
  31.     }  
  32.   
  33.     public String getAccountNo() {  
  34.         return accountNo;  
  35.     }  
  36.   
  37.     public void setAccountNo(String accountNo) {  
  38.         this.accountNo = accountNo;  
  39.     }  
  40.   
  41.     public double getBalance() {  
  42.         return balance;  
  43.     }  
  44.   
  45.     public void setBalance(double balance) {  
  46.         this.balance = balance;  
  47.     }  
  48.       
  49.       
  50.       
  51. }  
  52.   
  53. /** 
  54.  * 取钱的线程类 
  55.  *  
  56.  * @author Emily-T 
  57.  * 
  58.  */  
  59. public class DrawThread extends Thread {  
  60.   
  61.     // 模拟用户账户  
  62.     private Account account;  
  63.   
  64.     // 当前取钱线程所希望取的钱数  
  65.     private double drawAmount;  
  66.   
  67.     public DrawThread(String name, Account account, double drawAmount) {  
  68.         super(name);  
  69.         this.account = account;  
  70.         this.drawAmount = drawAmount;  
  71.     }  
  72.   
  73.     // 当多条线程修改同一个共享数据时,将涉及数据安全问题  
  74.     public void run() {  
  75.         // 账户余额大于取钱数目  
  76.         if (account.getBalance() >= drawAmount) {  
  77.   
  78.             // 吐出钞票  
  79.             System.out.println("取钱成功!吐出钞票:" + drawAmount);  
  80.   
  81. //          try {  
  82. //              Thread.sleep(1);  
  83. //          } catch (InterruptedException e) {  
  84. //              e.printStackTrace();  
  85. //          }  
  86.             // 修改余额  
  87.             account.setBalance(account.getBalance() - drawAmount);  
  88.             System.out.println("\t余额为:" + account.getBalance());  
  89.         } else {  
  90.             System.out.println(getName() + "取钱失败!余额不足!");  
  91.         }  
  92.     }  
  93. }  
  94. /** 
  95.  * 启动两个线程 
  96.  * @author Emily-T 
  97.  * 
  98.  */  
  99. public class TestDraw {  
  100.   
  101.     public static void main(String[] args){  
  102.         //创建一个账户  
  103.         Account acct = new Account("1234567",1000);  
  104.         //模拟两个线程对同一个账户取钱  
  105.         new DrawThread("甲",acct,800).start();  
  106.         new DrawThread("乙",acct,800).start();  
  107.     }  
  108. }  
结果:

        

      从结果看来,账户余额只有1000,取出了1600元,剩下-200元。出现这种结果是因为run方法的方法体不具有同步安全性,程序中有两条并发线程在修改Account对象。


线程同步


修改如下:加上同步代码块:

[java]  view plain  copy
  1. // 当多条线程修改同一个共享数据时,将涉及数据安全问题  
  2.     public void run() {  
  3.   
  4.         // 使用account作为同步监视器,任何线程进入下面同步代码块之前,必须先获得  
  5.         // 对account账户的锁定——其他线程无法获得锁,也就是无法修改它  
  6.         // 加锁——修改完成——释放锁  
  7.   
  8.         synchronized (account) {  
  9.             // 账户余额大于取钱数目  
  10.             if (account.getBalance() >= drawAmount) {  
  11.   
  12.                 // 吐出钞票  
  13.                 System.out.println("取钱成功!吐出钞票:" + drawAmount);  
  14.   
  15.                 try {  
  16.                     Thread.sleep(1);  
  17.                 } catch (InterruptedException e) {  
  18.                     e.printStackTrace();  
  19.                 }  
  20.                 // 修改余额  
  21.                 account.setBalance(account.getBalance() - drawAmount);  
  22.                 System.out.println("\t余额为:" + account.getBalance());  
  23.             } else {  
  24.                 System.out.println(getName() + "取钱失败!余额不足!");  
  25.             }  
  26.         }  
  27.     }  


结果:

       

        任何时刻只能有一条线程可以获得对同步监视器的锁定,当同步代码块执行结束后,该线程自然释放了对该同步监视器的锁定。

 

        同步监视器的目的:阻止两条线程对同一个共享资源进行并发访问。因此通常推荐使用可能被并发访问的共享资源充当同步监视器。

 

        可变类的线程安全是以降低程序的运行效率为代价的,为了减少程序安全所带来的负面影响,程序可以采用如下策略:

          1、不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源的方法进行同步

          2、如果可变类有两种运行环境:单线程和多线程环境,则应该为该可变类提供两种版本:线程不安全版本和线程安全版本。在单线程环境中使用线程不安全版本以保证性能,在多线程环境中使用线程安全版本。

猜你喜欢

转载自blog.csdn.net/s297485987/article/details/80659438