基础篇:同步机制之synchronized与ReentrantLock(四)

什么是同步呢?同步就是指串行访问,在单线程的情况下,不用考虑同步的问题,因为不存在并行访问,而在多线程情况下,假设我们有个取款的方法, 它可能是这样的:

public static void 取款( int 款数 ){
	int 余额 = get余额(); 

	if( 余额>款数 ){ //代码1处
	
		吐钱(); //代码2处
		更新数据库余额();
	}else{
		System.out.println("余额不足!");
	}

}


假设余额为100,款数为90,  当我们的线程A,B ,C同时进入代码1处时,得到的条件都将是true,于是在代码2处取款机吐了270元出去...这当然是有问题的,我们希望在A线程处理完直到数据库余额更新成功后,才允许B线程进来,此时B线程必然是不满足条件,这才能避免多吐钱的BUG;

要达到这种效果,就需要采用同步机制了,目的就是让方法内的一系列操作具备原子性,让线程由并行改为串行一个接一个的进入取款方法;


接下我们来举一个通用的例子,用来示范多种同步机制的使用:

//一个数字生成器
abstract class AbstractNumberGenerator{
	//是否已停止生成
	protected boolean isStop = false;
	//获得下一个数字
	protected abstract int next();
}


创建一个数字检查器,它是一个线程类,它不断的调用数字生成器的next()方法生成数字, 然后检查生成的数字,一旦发现数字不是偶数,则设置isStop为true,线程停止运行;

//检查生成的数字
class NumberChecker implements Runnable{
	private AbstractNumberGenerator ang ;
	private int id ;
	public NumberChecker(AbstractNumberGenerator ang,int id) {
		this.ang = ang;
		this.id = id;
	}
	@Override
	public void run() {
		while(!ang.isStop ){
			int number = ang.next();
			if(number%2!=0){
				ang.isStop = true;//停止继续生成数字
			}else{
				System.out.println(this.id+":"+number+" ");
			}
		}
	}
}


再创建一个测试方法:

//演示锁机制
	public static void testLock(AbstractNumberGenerator ang){
		ExecutorService exec = Executors.newCachedThreadPool();
		//开启5个线程去测试数字
		for (int i = 0; i < 5; i++) {
			exec.execute(new NumberChecker(ang,i));
		}
		exec.shutdown();
	}
	
	public static void main(String[] args) throws InterruptedException {
		testLock(new EvenNumberGenerator());
//		testLock(new EvenNumberGeneratorBySyn());
//		testLock(new EvenNumberGeneratorByLock());
		
	}



创建一个普通的偶数生成器;
class EvenNumberGenerator extends AbstractNumberGenerator{
	private int number = 0;
	@Override
	protected int next() {
		number++;
		Thread.yield();
		number++;
		return number;
	}
}


我们启动测试,看看输出结果:

输出******************************************************************

0:2 
2:8 
1:4 

***********************************************************************

很遗憾,测试是不通过的,因为当一个线程在返回number前,很有可能另一个线程已经执行了一次number++,所以有可能不是偶数!


我们再创建一个采用synchronized加锁的偶数生成器

class EvenNumberGeneratorBySyn extends AbstractNumberGenerator{
	private int number = 0;
	@Override
	protected synchronized int next() {
		number++;
		Thread.yield();
		number++;
		return number;
	}
//	上面的写法等同于下面的写法(称之为同步代码块或者临界区),上面在方法上写关键字,就是给当前对象上锁,
//	下面synchronized(this)这种写法也是给当前对象上锁
//	protected int next() {
//		synchronized (this) {
//			number++;
//			Thread.yield();
//			number++;
//			return number;
//		}
//	}
	
}
你可以启动测试类注释掉的代码试试,测试是没问题的,因为next()方法采用了synchronized关键字,该方法将不在存在并发访问的问题,无论何时都只可能有一个线程能进入方法内部;


我们再创建一个采用Lock加锁的偶数生成器

class EvenNumberGeneratorByLock extends AbstractNumberGenerator{
	private int number = 0;
	ReentrantLock lock = new ReentrantLock();//互斥锁,除此之外还有ReadLock以及WriteLock
	@Override
	protected int next() {
		//获取锁
		lock.lock();
		try {
			number++;
			Thread.yield();
			number++;
			return number;
		} finally{
			//释放锁
			lock.unlock();
		}
	}
}
通过声明一把互斥锁,调用它的lock方法将拿到锁,当锁已被持有时,其它线程再调用Lock方法将会阻塞,直到持有锁的线程调用unlock方法释放锁才得以继续运行;

注意:我们总是会将unlock()的调用放在finally中执行,这是为了确保锁一定会被释放从而避免死锁的产生;


我们比较一下两种同步方法,synchronized简单明了,但可控制粒度没有lock这么细,lock虽然控制粒度很细,但却引入了编码的复杂度,不过lock还有些其它有用的方法,这些在下篇文章中再做介绍,看完下篇文章,相信你对何时使用哪种同步机制已经能够有点想法了;





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值