zoukankan      html  css  js  c++  java
  • 多线程总结四:线程同步(一)

    1、线程安全问题

    a、银行取钱问题:取钱时银行系统判断账户余额是否大于取款金额,如果是,吐出钞票,修改余额。这个流程在多线程并发的场景下就可能会出现问题。

     1 /**
     2  * @Title: Account.java 
     3  * @Package  
     4  * @author 任伟
     5  * @date 2014-12-8 下午5:35:27 
     6  * @version V1.0  
     7  */
     8 
     9 /**
    10  * @ClassName: Account
    11  * @Description: 账户类
    12  * @author 任伟
    13  * @date 2014-12-8 下午5:35:27
    14  */
    15 public class Account {
    16     private String accountNo; // 账户编号
    17     private double balance; // 账户余额
    18 
    19     public String getAccountNo() {
    20         return accountNo;
    21     }
    22 
    23     public void setAccountNo(String accountNo) {
    24         this.accountNo = accountNo;
    25     }
    26 
    27     public double getBalance() {
    28         return balance;
    29     }
    30 
    31     public void setBalance(double balance) {
    32         this.balance = balance;
    33     }
    34 
    35     public Account() {
    36         super();
    37     }
    38 
    39     public Account(String accountNo, double balance) {
    40         super();
    41         this.accountNo = accountNo;
    42         this.balance = balance;
    43     }
    44 
    45     public boolean equals(Object anObject) {
    46         if(this==anObject)
    47             return true;
    48         if(anObject!=null && anObject.getClass()==Account.class){
    49             Account target = (Account) anObject;
    50             return target.getAccountNo().equals(accountNo);
    51         }
    52         return false;
    53     }
    54 
    55     public int hashCode() {
    56         return accountNo.hashCode();
    57     }
    58 
    59 }
    Account
     1 /**
     2  * @Title: DrawThread.java 
     3  * @Package  
     4  * @author 任伟
     5  * @date 2014-12-8 下午5:41:46 
     6  * @version V1.0  
     7  */
     8 
     9 /**
    10  * @ClassName: DrawThread
    11  * @Description: 取钱类
    12  * @author 任伟
    13  * @date 2014-12-8 下午5:41:46
    14  */
    15 public class DrawThread extends Thread {
    16     private Account account; // 用户帐户
    17     private double drawAmount; // 希望取的钱数
    18 
    19     public DrawThread(String name, Account account, double drawAmount) {
    20         super(name);
    21         this.account = account;
    22         this.drawAmount = drawAmount;
    23     }
    24     
    25     //当多个线程修改同一共享数据时,将涉及线程安全问题
    26     /* (non-Javadoc)
    27      * @see java.lang.Thread#run()
    28      */
    29     @Override
    30     public void run() {
    31         if(account.getBalance()>=drawAmount){
    32             System.out.println(this.getName()+"取钱成功,吐出钞票:"+drawAmount);
    33             try {
    34                 Thread.sleep(1);
    35             } catch (InterruptedException e) {
    36                 // TODO Auto-generated catch block
    37                 e.printStackTrace();
    38             }
    39             account.setBalance(account.getBalance()-drawAmount);
    40             System.out.println("余额为:"+account.getBalance());
    41         }else{
    42             System.out.println(this.getName()+"取钱失败!余额不足!");
    43         }
    44     }
    45     
    46     public static void main(String[] args) {
    47         //创建一个账户
    48         Account acct = new Account("1234567", 1000);
    49         //模拟两个线程对同一个账户取钱
    50         new DrawThread("甲", acct, 800).start();
    51         new DrawThread("乙", acct, 800).start();
    52     }
    53 
    54 }
    DrawThread

    运行结果:

    2、同步代码块

    a、Java多线程引入了同步监视器来解决这个问题:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定,在同步代码块执行完以后,该线程会释放对该同步监视器的锁定。通常使用可能被并发访问的共享资源充当同步监视器。

     1 synchronized(obj){//obj同步监视器 2 //同步代码块 3 } 

     1 /**
     2  * @Title: SynchronizedBlockDrawThread.java 
     3  * @Package  
     4  * @author 任伟
     5  * @date 2014-12-8 下午5:59:06 
     6  * @version V1.0  
     7  */
     8 
     9 /**
    10  * @ClassName: SynchronizedBlockDrawThread
    11  * @Description: 使用Account作为同步监视器
    12  * @author 任伟
    13  * @date 2014-12-8 下午5:59:06
    14  */
    15 public class SynchronizedBlockDrawThread extends Thread {
    16     private Account account; // 用户帐户
    17     private double drawAmount; // 希望取的钱数
    18 
    19     public SynchronizedBlockDrawThread(String name, Account account,
    20             double drawAmount) {
    21         super(name);
    22         this.account = account;
    23         this.drawAmount = drawAmount;
    24     }
    25 
    26     // 当多个线程修改同一共享数据时,将涉及线程安全问题
    27     /*
    28      * (non-Javadoc)
    29      * 
    30      * @see java.lang.Thread#run()
    31      */
    32     @Override
    33     public void run() {
    34         synchronized (account) {
    35             if (account.getBalance() >= drawAmount) {
    36                 System.out.println(this.getName() + "取钱成功,吐出钞票:" + drawAmount);
    37                 try {
    38                     Thread.sleep(1);
    39                 } catch (InterruptedException e) {
    40                     // TODO Auto-generated catch block
    41                     e.printStackTrace();
    42                 }
    43                 account.setBalance(account.getBalance() - drawAmount);
    44                 System.out.println("余额为:" + account.getBalance());
    45             } else {
    46                 System.out.println(this.getName() + "取钱失败!余额不足!");
    47             }
    48         }
    49     }
    50 
    51     public static void main(String[] args) {
    52         // 创建一个账户
    53         Account acct = new Account("1234567", 1000);
    54         // 模拟两个线程对同一个账户取钱
    55         new SynchronizedBlockDrawThread("甲", acct, 800).start();
    56         new SynchronizedBlockDrawThread("乙", acct, 800).start();
    57     }
    58 
    59 }
    SynchronizedBlockDrawThread

    运行结果:

     

    3、同步方法
    a、使用synchronized关键字来修饰某一个方法,使用this作为同步监视器,也就是调用该方法的对象。
    将Account变为线程安全的类:

     1 /**
     2  * @Title: Account2.java 
     3  * @Package  
     4  * @author 任伟
     5  * @date 2014-12-8 下午6:50:34 
     6  * @version V1.0  
     7  */
     8 
     9 /** 
    10  * @ClassName: Account2 
    11  * @Description: 线程安全Account类
    12  * @author 任伟
    13  * @date 2014-12-8 下午6:50:34  
    14  */
    15 public class Account2 {
    16     private String accountNo; // 账户编号
    17     private double balance; // 账户余额
    18     
    19     public Account2() {
    20         super();
    21     }
    22 
    23     public Account2(String accountNo, double balance) {
    24         super();
    25         this.accountNo = accountNo;
    26         this.balance = balance;
    27     }
    28 
    29     public boolean equals(Object anObject) {
    30         if(this==anObject)
    31             return true;
    32         if(anObject!=null && anObject.getClass()==Account.class){
    33             Account target = (Account) anObject;
    34             return target.getAccountNo().equals(accountNo);
    35         }
    36         return false;
    37     }
    38 
    39     public int hashCode() {
    40         return accountNo.hashCode();
    41     }
    42     
    43     public synchronized void draw(double drawAmount){
    44         if (balance >= drawAmount) {
    45             System.out.println(Thread.currentThread().getName() + "取钱成功,吐出钞票:" + drawAmount);
    46             try {
    47                 Thread.sleep(1);
    48             } catch (InterruptedException e) {
    49                 // TODO Auto-generated catch block
    50                 e.printStackTrace();
    51             }
    52             balance -= drawAmount;
    53             System.out.println("余额为:" + balance);
    54         } else {
    55             System.out.println(Thread.currentThread().getName() + "取钱失败!余额不足!");
    56         }
    57     }
    58 }
    Account2
     1 /**
     2  * @Title: DrawThread2.java 
     3  * @Package  
     4  * @author 任伟
     5  * @date 2014-12-8 下午6:54:59 
     6  * @version V1.0  
     7  */
     8 
     9 /**
    10  * @ClassName: DrawThread2
    11  * @Description: 取钱类
    12  * @author 任伟
    13  * @date 2014-12-8 下午6:54:59
    14  */
    15 public class DrawThread2 extends Thread {
    16     private Account2 account; // 用户帐户
    17     private double drawAmount; // 希望取的钱数
    18 
    19     public DrawThread2(String name, Account2 account, double drawAmount) {
    20         super(name);
    21         this.account = account;
    22         this.drawAmount = drawAmount;
    23     }
    24 
    25     /*
    26      * (non-Javadoc)
    27      * 
    28      * @see java.lang.Thread#run()
    29      */
    30     @Override
    31     public void run() {
    32         account.draw(drawAmount);
    33     }
    34 
    35     public static void main(String[] args) {
    36         // 创建一个账户
    37         Account2 acct = new Account2("1234567", 1000);
    38         // 模拟两个线程对同一个账户取钱
    39         new DrawThread2("甲", acct, 800).start();
    40         new DrawThread2("乙", acct, 800).start();
    41     }
    42 }
    DrawThread2

    运行结果:

    4、延伸
    a、synchronized关键字。可以修饰方法,可以修饰代码块,但不能修饰构造器、成员变量等。

    b、Domain Driven Design:在面向对象里有一种设计方法:Domain Driven Design(领域驱动设计,DDD),这种方式认为每个类都应该是完备的领域对象,例如Account代表用户帐户,应该提供用户帐户的相关方法;通过draw()方法来执行取钱操作,而不是将setBalance()方法暴露出来任人操作,才能更好地保证Account对象的完整性和一致性。

    c、线程安全类特征:通过同步方法可以非常方便的实现线程安全的类,线程安全的类具有如下特征:
    .该类的对象可以被多个线程安全地访问;
    .每个线程调用该类的任意方法后都能得到正确的结果;
    .每个线程调用该类的任意方法后,该对象状态依然保持合理的状态;

    d、可变类和不可变类(Mutable and Immutable Objects):
    可变类:当你获得这个类的一个实例引用时,你可以改变这个实例的内容。
    不可变类:当你获得这个类的一个实例引用时,你不可以改变这个实例的内容。不可变类的实例一但创建,其内在成员变量的值就不能被修改。

    e、如何创建一个自己的不可变类:
    .所有成员都是private
    .不提供对成员的改变方法,例如:setXXXX
    .确保所有的方法不会被重载。手段有两种:使用final Class(强不可变类),或者将所有类方法加上final(弱不可变类)。
    .如果某一个类成员不是原始变量(primitive)或者不可变类,必须通过在成员初始化(in)或者get方法(out)时通过深度clone方法,来确保类的不可变。

    f、不可变类总是线程安全的,因为对象状态不可变;可变类需要额外的方法保证其线程安全。

    5、线程安全与运行效率
    可变类的线程安全是以降低程序的运行效率作为代价的,为了减少线程安全所带来的负面影响,可采用以下策略:
    a、不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源(共享资源)的方法同步;
    b、如果可变类有两种运行环境:单线程环境和多线程环境,这应该提供该类的线程不安全版本和线程安全版本;

  • 相关阅读:
    SQL Server游标的使用
    SQL函数说明大全
    基本数据结构:链表(list)
    QT+C++实现连连看
    printf按8进制、16进制输出
    关于数据库优化问题收集
    SQL Server 查询处理中的各个阶段(SQL执行顺序) 转
    MFC消息机制
    _T() 和_L() _TEXT __T,L区别与联系详解
    Windows 各种控件使用心得
  • 原文地址:https://www.cnblogs.com/renwei/p/4151753.html
Copyright © 2011-2022 走看看