Java线程同步方式

本文部分内容来自http://www.cnblogs.com/XHJT/p/3897440.html
为什么要使用同步?
  Java与允许多线程并发控制,当多个线程同时操作共享变量时,将会导致数据的不准确,相互之间产生冲突,使用同步可以保证共享变量的准确性和唯一性。
1、同步代码块
  使用synchronized修饰的语句块。
  被修饰的语句块会自动加上内置锁,从而实现同步。

synchronized(obj){

}

其中obj是同步监视器,它的含义是:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。任何时刻只能有一个进程可以获得同步监视器的锁定,当同步代码被执行完毕后会自动释放同步监视器的锁定。
一般将可能被访问的共享资源作为同步监视器。

代码:
Account类

public class Account{
	private String accountNo;
	private double balance;
	public Account(){}
	public Account(String accountNo,double balance){
		this.accountNo = accounNo;
		this.balance = balance;
	}
	//getter和setter方法
    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;
	}
    //重写equals方法
    public boolean equals(Object o){
        if(this==o){
            return true;
        }
        if(o==null||getClass()!=o.getClass()){
            return false;
        }
        Account account = (Account)o;
        return accountNo.equals(account.accountNo);
    }
}

DrawThread类

public class DrawThread extends Thread{
	private Account account;
	private double drawAmount;
	public DrawThread(){}
	public DrawThread(String name,Account account,double drawAmount){
		super(name);
		this.account = account;
		this.drawAmount = drawAmount;
	}
	public void run(){
		//用account作为同步监视器
		synchronized(account){
			//如果余额大于等于去的钱数
			if(account.getBalance>=drawAmount){
				System.out.println(getName()+"取钱成功,吐出钞票:"+drawAccount);
				try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.getStackTrace();
                };
                //更改余额(实际上余额不应该随意更改)
                account.setBanlance(account.getBanlance()-drawAmount);
                System.out.println("余额为:"+account.getBanlance());
			}else{
				System.out.println(getName()+"取钱失败,余额不足");
			}
		}
	}
}

测试类

public class test{
	public static void main(Stringp[] args){
		Account acct = new Account("1234567",(double)1000);
		new DrawThread("甲",acct,800).start();
		new DrawThread("乙",acct,800).start();
	}
}

2、同步方法
  使用synchronized关键字修饰的方法
  由于Java对象有内置锁,因此使用此关键字修饰方法时内置锁会保护整个方法。在调用该方法时需取得内置锁,否则就处于阻塞状态。
  同步方法无须显式的指定同步监视器,可以很方便的实现线程安全的类。
  注:静态方法也可以用synchronized修饰,此时整个类会被锁住。
代码:
Account类

public class Account{
	String accountNo;
	double balance;
	public Account(){}
	public Account(String accountNo,double banlance){
		this.accountNo = accountNo;
		this.balance = balance;
	}
	
	//getter和setter方法
	 public String getAccountNo() {
        return accountNo;
	}
	public void setAccountNo(String accountNo) {
       this.accountNo = accountNo;
	}
   //账户余额不能随便更改,因此只需有get方法
	public Double getBalance() {
       return balance;
	}
	public boolean equals(Object o){
        if(this==o){
            return true;
        }
        if(o==null||getClass()!=o.getClass()){
            return false;
        }
        Account account = (Account)o;
        return accountNo.equals(account.accountNo);
    }
	//定义一个线程安全的类执行取钱操作
	public synchronized void draw(double drawAmount){
		if(balance>=drawAmount){
			System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票:"+drawAmount);
			try{
                Thread.sleep(1);
            }catch (InterruptedException ex){
                ex.printStackTrace();
            }
            balance -= drawAmount;
            System.out.println("余额为:"+balance);
		}else{
			System.out.println(Thread.currentThread().getName()+"取钱失败,余额不足");
		}
	}
}

DrawThread类

public class DrawThread extends Thread{
	private Account account;
	private double drawAmount;
	
	public DrawThread(){}
	public DrawThread(String name,Account account,double drawAmount){
		super(name);
		this.account = account;
		this.drawAmount = drawAmount;
	}

	public void run(){
		account.draw(drawAmount);
	}
}

测试类

public class test(){
	public static void main(String[] args){
		Account acct = new Account("1234556",(double)1000) ;
		new DrawThread("甲",acct,800).start();
		new DrawThread("乙",acct,800).start();
	}
}

注:synchronized关键字可以修饰方法、代码块,但是不可以修饰构造器、成员变量等。
在Account中定义draw()方法而不是在run()方法中实现取钱逻辑这样更符合面向对象规则。
3、使用volatile关键字
  volatile保证了多线程下的可见性。
  禁止指令重排序。
将Account类中余额balance用volatile修饰即可不用synchronized。

private volatile double balance;

4、使用重入锁实现线程同步
  JavaSE1.5引入java.util.concurrent包来支持同步。
  ReentrantLock是可重入,互斥,实现了Lock接口的锁,扩展了synchronized的功能。
代码:
Account类,其他类与之前类似。

public class Account{
	private final ReentrantLock lock = new ReentrantLock();
	private String accountNo;
	private double balance;
	
	public Account(){}
	public Account(String accountNo,double banlance){
		this.accountNo = accountNo;
		this.balance = balance;
	}
	
	//getter和setter方法
	 public String getAccountNo() {
        return accountNo;
	}
	public void setAccountNo(String accountNo) {
       this.accountNo = accountNo;
	}
   //账户余额不能随便更改,因此只需有get方法
	public Double getBalance() {
       return balance;
	}
	public boolean equals(Object o){
        if(this==o){
            return true;
        }
        if(o==null||getClass()!=o.getClass()){
            return false;
        }
        Account account = (Account)o;
        return accountNo.equals(account.accountNo);
    }
    //提供一个线程安全的方法实现取钱操作
    public void draw(double drawAmount){
    	//加锁
    	lock.lock();
    	try {
            if (balance >= drawAmount) {
                System.out.println(Thread.currentThread().getName() + "取钱成功!吐出钞票:" + drawAmount);
                try {
                    Thread.sleep(1);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
                balance -= drawAmount;
                System.out.println("\t余额为:" + balance);
            } else {
                System.out.println(Thread.currentThread().getName() + "取钱失败,余额不足");
            }
        }finally{
        	 //必须显示调用unlock()方法释放锁,一般放在finally里面
        	 lock.unlock();
        }
    }
}

synchronized和lock
  如果使用synchronized能满足用户需求则不用Lock,因为synchronized能简化代码。
  如果需要使用更高级的功能则使用Lock。线程在每次lock()加锁后,必须显示调用unlock()来释放锁。
5、使用局部变量(共享变量副本)实现线程同步
  ThreadLocal threadLocal = new ThreadLocal();
  使用ThreadLocal管理变量,每一个使用该变量的线程都能获得该变量的副本,每个副本之间相互独立,这样每一个线程都能随意修改自己的变量副本,不会对其他线程产生影响。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值