线程的同步

  多线程编程常常容易突然出现一些“错误情况”,当多个线程共享资源时,经常会出现一些线程安全问题。以ATM为例,如果A/B两个人同时往一个账户(Account)中存钱,这个账户中本来有1000元,假如A取得了账户(Account)对象后,B也取得这个账户对象,然后A、B分别向该账户中存入1000元,查询账户余额会发现账户中只有2000元,而不是正常的3000元。我们先来看一下这段代码:

     首先,我们先定义了一个不可变类Account,来封装账户编号和余额两个属性:

 
然后提供一个存钱的线程类,
 

       运行这个线程类,就会出现前面说的情况。之所以会出现这种现象,其原因就是其中的run方法不具有同步安全性:程序中有两条线程在修改Account对象,而恰好在执行setBalance方法语句之前切换给另外一条线程。所以,就发生了问题。

     为了解决这个问题, java中引入了同步监视器(synchronized)。当用关键字sychronized修饰某个对象时,这个对象就是同步监视器。对于同步代码块来说,同步监视器就是指定的对象(synchronized(obj)中obj。为了体现同步监视器的作用,我们通常使用可能被并发共享的资源充当监视器。对于上面的代码,我们把Account对象当做同步监视器。然后,我们只要把run方法的执行体变成同步代码块就可以了。修改后的代码如下:

  上面的代码通过使用synchronized将run方法的执行体变成了同步代码块,这样符合“加锁---执行修改---解锁”的逻辑,任何线程想修改资源之前,首先要对该对象进行加锁,在解锁之前,其他线程是不能调用该资源的,当该线程修改完成,该线程释放对所用资源的锁定。

       synchronzied还支持对方法进行同步。当一个方法用synchronized来修饰时,它就是同步方法了。对于同步方法而言,无需显式的定义同步监视器,它的同步监视器是this对象,也就是类本身。

      使用同步方法可以非常方便的使某个可变类(线程不安全的类)变成线程安全类,这里就不再举例说明了。

       需要注意的是,可变类的线程安全是以降低程序的运行效率作为代价的,为了减少线程安全带来的负面影响,程序可以采取以下策略:

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

              2.如果可变类有两种运行环境:单线程和多线程。那么就应该为该可变类提供两种版本:线程不安全版本和线程安全版本。对于单线程,就使用线程不安全版本。

 

  任何线程进入同步代码块或同步方法,必须先获得对同步监视器的锁定。程序无法显式释放对同步监视器的锁定,线程会在如下几种情况时释放对同步监视器的锁定:

    1.当前线程的同步方法、同步代码块执行结束,当前线程即释放同步监视器。

    2.当线程在同步代码块、同步方法中遇到break/return终止了该代码块,该方法的继续执行,当前线程将会释放同步监视器。

               3.当线程在同步代码块、同步方法中出现了未处理的异常或错误,导致了该代码块、该方法异常结束时释放同步监视器。

               4.当线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait方法,则当前线程暂停,并释放同步监视器。

    在下面情况下,线程不会释放同步监视器:

           1.线程执行同步方法或同步代码块时,程序调用了Thread.sleep()、Thread.yield()方法来暂停当前线程,当前线程不会释放同步监视器。

            2.线程执行同步代码块时,其他线程调用了该线程的suspend方法将该线程挂起,该线程不会释放同步监视器。

  

 

         从JDK1.5之后,java提供了另外一种线程同步机制:它通过显式定义同步锁对象来实现同步,在这种情况下,同步锁应该由lock对象充当。使用lock对象来进行同步是,锁定和释放锁出现在不同范围中时,通常建议使用finnally块来确保必要时释放锁。lock锁比同步方法和同步不代码块更加灵活。lock提供了同步方法和同步代码块没有的其他功能,包括用于非块结构的trylock方法,以及试图获取可中断锁lockInterruptibly()方法,还有获取超时失效的tryLock(long,TimeUnit)方法。

       我们常用的lock锁ReentrantLock,它具有可重入性,也就是说线程可以对它已经加锁的ReentrantL锁再次加锁,ReentrantLock对象会维持一个计数器来追踪lock方法的嵌套调用,线程在每次调用lock()加锁后,必须显式的用unlock()释放锁,并且,释放多个锁时,必须要以相反的顺序释放,且必须在与所有锁被获取时相同的范围内释放所有锁。

        我们也可以用lock锁来是实现存钱的线程,我们先对竞争资源A从count类进行同步:

    

import java.util.concurrent.locks.ReentrantLock;
public class Account {
	private final ReentrantLock lock=new ReentrantLock();
	//封装银行账号、余额两个属性
	private String accountNo;
	private double balance;
	
	public Account(){}
	public Account(String accountNo,double balance){
		this.accountNo=accountNo;
		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;
	}
	
	public void save(double saveAccount){
		
		lock.lock();
		try{
			Thread.sleep(100);
		}catch(Exception e){
			e.printStackTrace();
		}
		finally{
			lock.unlock();
		}
		balance=balance+saveAccount;
		System.out.println(Thread.currentThread().getName()+"存钱成功,余额为:"+balance);
	}
	
	
	
	/**
	 * @return the accountNo
	 */
	public String getAccountNo() {
		return accountNo;
	}
	/**
	 * @param accountNo the accountNo to set
	 */
	public void setAccountNo(String accountNo) {
		this.accountNo = accountNo;
	}
	/**
	 * @return the balance
	 */
	public double getBalance() {
		return balance;
	}
	/**
	 * @param balance the balance to set
	 */
	public void setBalance(double balance) {
		this.balance = balance;
	}
}


然后,在线程类Thread类的执行体中直接调用Account类的save方法就行。

 

public class SaveThread extends Thread {
	private Account account;
	private double saveAccount;
	public SaveThread(String name,Account account,double saveAccount){
		super(name);
		this.account=account;
		this.saveAccount=saveAccount;
	}
	
	public void run(){
		account.save(saveAccount);
	}

	public static void main(String args[]){
		Account acc=new Account("1111",1000);
		new SaveThread("线程1",acc,1000).start();
		new SaveThread("线程2",acc,1000).start();
	}
}


        

        当两个线程相互等待对方释放同步监视器时就会发生死锁。这是因为在调用同步监视器时要先对对象进行加锁,如果一个同步线程对该对象加锁以后 ,cpu切换到另一个同步线程,这个同步线程也会对该对象加锁,但是由于前一个同步线程没有释放同步监视器,所以只能等待,而前一个线程又要等待这个线程释放同步监视器。所以就出现了相互僵持的局面,整个程序不会发生任何异常,也不会给出任何提示。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值