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


           当使用多个线程来訪问同一个数据时,就easy出现线程安全的问题。比如,银行取钱。当我们去自己主动取款机取钱时。正好还有一个人转账,即多个线程改动同一数据,这时就easy出现线程安全问题。


    线程安全

    /**
     * 账户类。该类封装了账户编号和剩余金额两个属性
     * @author Emily-T
     *
     */
    public class Account {
    
    	//账户编号
    	private String accountNo;
    	//剩余金额
    	private double balance;
    	public Account(){}
    	
    	//构造函数
    	public Account(String accountNo,double balance){
    		this.accountNo = accountNo;
    		this.balance = balance;
    	}
    	
    	//以下两个方法依据accountNo来计算Account的hashCode和推断equals
    	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;
    	}
    
    	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;
    	}
    	
    	
    	
    }
    
    /**
     * 取钱的线程类
     * 
     * @author Emily-T
     *
     */
    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("取钱成功!吐出钞票:" + drawAmount);
    
    //			try {
    //				Thread.sleep(1);
    //			} catch (InterruptedException e) {
    //				e.printStackTrace();
    //			}
    			// 改动剩余金额
    			account.setBalance(account.getBalance() - drawAmount);
    			System.out.println("	剩余金额为:" + account.getBalance());
    		} else {
    			System.out.println(getName() + "取钱失败!剩余金额不足!");
    		}
    	}
    }
    /**
     * 启动两个线程
     * @author Emily-T
     *
     */
    public class TestDraw {
    
    	public static void main(String[] args){
    		//创建一个账户
    		Account acct = new Account("1234567",1000);
    		//模拟两个线程对同一个账户取钱
    		new DrawThread("甲",acct,800).start();
    		new DrawThread("乙",acct,800).start();
    	}
    }
    
    结果:

            

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


    线程同步


    改动例如以下:加上同步代码块:

    // 当多条线程改动同一个共享数据时,将涉及数据安全问题
    	public void run() {
    
    		// 使用account作为同步监视器。不论什么线程进入以下同步代码块之前。必须先获得
    		// 对account账户的锁定——其它线程无法获得锁,也就是无法改动它
    		// 加锁——改动完毕——释放锁
    
    		synchronized (account) {
    			// 账户剩余金额大于取钱数目
    			if (account.getBalance() >= drawAmount) {
    
    				// 吐出钞票
    				System.out.println("取钱成功!

    吐出钞票:" + drawAmount); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } // 改动剩余金额 account.setBalance(account.getBalance() - drawAmount); System.out.println(" 剩余金额为:" + account.getBalance()); } else { System.out.println(getName() + "取钱失败。剩余金额不足!"); } } }


    结果:

           

            不论什么时刻仅仅能有一条线程能够获得对同步监视器的锁定,当同步代码块运行结束后,该线程自然释放了对该同步监视器的锁定。

     

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

     

            可变类的线程安全是以降低程序的执行效率为代价的。为了降低程序安全所带来的负面影响。程序能够採用例如以下策略:

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

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

  • 相关阅读:
    (WPF)实现DataGrid中当某一列的值显示为密码样式
    利用双栈实现撤销与恢复逻辑
    WPF 中ContextMenu 在mvvm模式中的绑定问题
    WPF使用StringFormat绑定
    不注册调用COM组件
    再谈WPF绑定
    WPF的动态资源和静态资源
    MDI窗体(Winform)
    mysql 查询json字符串中符合条件的值
    phpstudy 导入sql到phpmyadmin中sql过大导致失败解决办法
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/5139531.html
Copyright © 2020-2023  润新知