JAVA 线程同步

银行取钱问题


public void draw (double drawAmount) {
    if (account.getBalance() >= drawAmount) { // (1)
        account.setBalance(account.getBalance() -= drawAmount); // (2)
        System.out.println ("余额为:" + account.getBalance() ) ;
    }
}

由于线程并发执行的不确定性,此时有可能发生当甲乙两条线程同时取钱时,甲线程运行完 (1) 语句还未执行 (2) 语句时,balance 此时还满足条件,然后乙线程也执行完 (1) 语句,并且执行完 (2) 语句,然后 balance 变成不满足条件了,但是甲线程已经执行过 (1) 语句了,它也接着执行 (2) 语句,balance 出现负数。但是银行怎么可以让你超额取钱呢,所以就要使用同步机制,限制同一个账户当有人在取钱时,别人便不能取钱。


同步代码块


public void draw (double drawAmount) {
    // 使用 synchronized 保证以下代码只有一个线程在执行,这里 account 对象是同步监听器
    synchronized (account) {
        if (account.getBalance() >= drawAmount) { 
            account.setBalance(account.getBalance() -= drawAmount); 
            System.out.println ("余额为:" + account.getBalance() ) ;
        }
    }
    // 同步代码块结束,释放同步锁
}


同步方法


// 使用 synchronized 修饰需要同步的方法,这里的同步监听器是调用该方法的对象,即 this
public synchronized void draw (double drawAmount) {
    if (account.getBalance() >= drawAmount) { 
        account.setBalance(account.getBalance() -= drawAmount); 
        System.out.println ("余额为:" + account.getBalance() ) ;
    }
}


同步锁


// 首先需要在类中定义一把锁
private final ReentrantLock lock = new ReentrantLock () ;
public void draw (double drawAmount) {
    // 对同步锁进行加锁,这里 lock 对象使用隐式的同步监听器
    lock.lock () {
        try {
            if (account.getBalance() >= drawAmount) { 
                account.setBalance(account.getBalance() -= drawAmount); 
                System.out.println ("余额为:" + account.getBalance() ) ;
            }
        // 使用 finally 块来确保释放锁
        } finally {
            lock.unlock () ;
        }
    }
}


关于同步机制


对象给自己建了一道门,门上有把锁,同步监听器就相当于这把锁,保护着门里的资源只能一条线程访问,每条线程都持有一把万能钥匙,当遇到门时,首先寻找门上的锁,若门上的锁不在,这门就成了墙,线程进不去,就阻塞了,直到别的线程将锁放回去,线程看到锁后就用自己的钥匙开启这把锁进去,并带走这把锁让别的线程进不来,运行结束后便会释放这把锁。
每个对象只有一把锁,但每条线程可以拥有很多锁,意思就是线程进入门内又发现一把锁,可以继续用自己的钥匙开这把锁并带走锁,那么该线程就拥有了两把锁。

线程同步会降低程序的运行效率。
不要对线程安全类的所有方法都同步,只对那些会改变竞争资源的方法进行同步。
可变类在单线程环境中使用线程非安全版以保证性能,在多线程环境中使用线程安全版。(例如:StringBuilder 、HashMap 和 ArrayList 属于线程非安全,它们分别有相对应的线程安全版 StringBuffer 、Hashtable 和 Vector )


线程通信


使用条件变量控制线程协调运行

对于 synchronized 有 wait() , notify() ,notifyAll() 这三种方法,这三种由同步监听器对象来调用。
对于 Lock 锁有 await() , signal() , signalAll() 这三种方法,这三种方法也由同步监听器 Condition 对象调用。

假设有甲乙两条线程,甲线程负责存钱,乙线程负责取钱,有一个定额账户,额度满时便不能存钱。

class Account {
    private String accountNum ;
    private double balance ;
    public Account(String accountNum, double balance) {
    	this.accountNum = accountNum ;
    	this.balance = balance ;
    }
    public double getBalance() {
    	return this.balance ;
    }
    public void setBalance (double balance) {
    	this.balance = balance ;
    }
    public synchronized void draw (double drawAmount) {
    	try {
    		if (balance < drawAmount) {
    			// 让当前线程等待,直到其他同步监听器的 notify() 方法或 notifyAll() 方法来唤醒该线程
    			wait();
    		} else {
    			balance -= drawAmount ;
    			System.out.println("取款后余额为:" + balance);
    			// 唤醒在此同步监听器上等待所有线程,只有当前线程放弃对该同步监听器的锁定后(使用 wait() 方法),才可以执行被唤醒的线程
    			notifyAll();
    		}
    	} catch (Exception e) {
    		e.printStackTrace() ;
    	}
    }
    public synchronized void deposit (double depositAmount) {
    	try {
    		if (balance == 5) {
    			wait();
    		} else {
    			balance += depositAmount ;
    			System.out.println("存款后余额为:" + balance);
    			notifyAll();
    		}
    	} catch (Exception e) {
    		e.printStackTrace();
    	}
    }
}
class DrawRunnable implements Runnable {
	private Account account;
	private double drawAmount;
	public DrawRunnable(Account account, double drawAmount){
		this.account = account;
		this.drawAmount = drawAmount;
	}
	public void run() {
		for (int i=0;i<20;i++) {
			account.draw(drawAmount);
		}
	}
}
class DepositRunnable implements Runnable {
	private Account account;
	private double depositAmount;
	public DepositRunnable(Account account, double depositAmount){
		this.account = account;
		this.depositAmount = depositAmount;
	}
	public void run() {
		for(int i=0;i<20;i++){
			account.deposit(depositAmount);
		}
	}
}
public class Test {
	public static void main(String[] args){
		Account acct = new Account ("1234567", 0);
		DrawRunnable drawer = new DrawRunnable(acct, 1);
		DepositRunnable depositer = new DepositRunnable(acct, 1);
		ExecutorService pool = Executors.newFixedThreadPool(2);
		pool.execute(drawer);
		pool.execute(depositer);
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值