`
中南大宝
  • 浏览: 33686 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

“锁”这玩意

阅读更多

       Java中的“锁”经常用于处理多线程编程中不同线程对同一个变量进行处理时造成的不同步问题。

 

       举个例子,如果一个Boy和他的Girl Friend分别持有同一账户的银行卡和存折,如果银行的后台取款程序没有解决好同步问题,那么就可能出现,Boy在A地,Girl在B地,两人同时取款,虽然账户中仅有1000元,可是因为程序的不同步,两人可以取得到2000元(每人分别取了1000)。当然,现实中,这样的情况是不会出现的,因为在银行的系统中,一个时间段,只会允许一个人操作,当他操作完后,账户的金额也就实时更新了,不会出现让银行自己亏钱的事情。

 

       那么,如果要我们来模拟这件事情的话,我们可以怎么做呢?有以下的几种方案可以采用:

       1) 我控制这个账户在一段时间内只允许一个人登陆并使用

       2) 可以有几个人同时登陆这个账户,可以执行"查询金额"等不涉及金钱交易的操,但是对金钱交易这个动作加"锁",如果有一个人进行取款或者转帐等操作,那么在一个时间段内就只允许这一个人操作,并且实时更新数据.

      

       在Java中,实现锁的功能可以由同步关键字synchronized或者lock类来实现.我们先看下面一段代码:

package SynchronizedTest;
/**
 * Synchronized测试类
 * @author hadoop
 */
public class SynchronizedTest2 {
	
	static int account = 0;//设置初始账户金额为0,这里是全局变量
	public static void main(String[] args) {

		final SynchronizedTest2 test1 = new SynchronizedTest2();
		account = 5000;
		System.out.println("账户总款:" + account);
		
		Thread th1 = new Thread(new Runnable() {
			public void run() {
				 test1.getcash("A",  4000);
			}
		});		
		th1.start();
		Thread th2 = new Thread(new Runnable() {
			public void run() {
				test1.getcash("B", 3000);
			}
		});
		th2.start();
	}
	
	private synchronized  void getcash(String name,  int money) {
		
		if (money < account) {
			try {
				Thread.sleep(2000);//这里给个延时,这样会导致不同步问题的出现
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			account -= money;
			System.out.println(name+"客户取款:"+money);
			System.out.println("账户余额:"+account);
		} else {
			System.out.println("账户金额不足"+money+","+name+"客户没取到钱");
		}
	}
}

 输出结果是:(直接copy自控制台)

        账户总款:5000
        A客户取款:4000
        账户余额:1000
        账户金额不足3000,B客户没取到钱

 

如果不加synchronized会有如下效果:(直接copy自控制台)

        账户总款:5000

        A客户取款:4000

        账户余额:1000

        B客户取款:3000

        账户余额:-2000

 

 

        在测试类中我们使用Synchronized关键字对取款方法进行加锁,这样当同一个对象的不同线程共同调用getcash()方法时,一个时间段内只会被一个线程调用了。这里注意我们的账户account是一个全局变量。

 

        下面来对synchronized关键字的用法进行归纳一下:

         synchronized的用法一个是定义方法,一个是定义块

         1. 定义方法有两个范围
             1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象(这里务必注意是同一个对象的多个线程,通常用实现Runnable的方法实现)的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的 synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法,上面的例子就是实现了这个东东。
             2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。

         2. 除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象.

 

         另外一种是通过实例化实现Lock接口的类来实现,我们用一段代码来说明一下:

         (这里我们用可重入互斥锁ReentrantLock类来实现)

 

package LockTest;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Lock测试类
 * @author 赵广超
 */
public class LockTest2 {

	static int account = 0;// 设置初始账户金额为0,这里是全局变量
	private static Lock lock = new ReentrantLock();//reentrant的意思是可重入

	public static void main(String[] args) {

		final LockTest2 test = new LockTest2();
		account = 5000;
		System.out.println("账户总款:" + account);

		Thread th1 = new Thread(new Runnable() {
			public void run() {
				test.getcash("A", 4000);
			}
		});
		th1.start();
		
		Thread th2 = new Thread(new Runnable() {
			public void run() {
				test.getcash("B", 3000);
			}
		});
		th2.start();
	}

	private void getcash(String name, int money) {

		// 开始锁定,仅有一个线程可以执行此段代码
		lock.lock();
		try {
			if (money < account) {
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(name + "客户取款:" + money);
				account -= money;
				System.out.println("账户余额:" + account);
			} else {
				System.out.println("账户金额不足" + money + "," + name + "客户没取到钱");
			}
		} finally {
			// 释放锁
			lock.unlock();
		}
	}
}

 控制台输出:

        账户总款:5000

        A客户取款:4000

        账户余额:1000

        账户金额不足3000,B客户没取到钱

 

 

	private void getcash(String name, int money) {

		// 开始锁定,仅有一个线程可以执行此段代码
//		lock.lock();
//		try {
			if (money < account) {
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(name + "客户取款:" + money);
				account -= money;
				System.out.println("账户余额:" + account);
			} else {
				System.out.println("账户金额不足" + money + "," + name + "客户没取到钱");
			}
//		} finally {
//			// 释放锁
//			lock.unlock();
//		}
	}

 控制台输出:

        账户总款:5000

        A客户取款:4000

        账户余额:1000

        B客户取款:3000

        账户余额:-2000

 

        从上面的输出的不同结果可以看出,实现了Lock接口的ReenTrantLock对象确实是控制了不同线程在不同的时刻访问同一代码块。

        ReentrantLock类中的lock()方法是给下面的代码块加锁,而unlock方法则是释放锁。因为锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。 finally的意思是无论是否有异常被抛出,程序都需要执行完finally中的语句。

 

        ReentrantLock获取锁定与三种方式:
        a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁

        b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;

        c) tryLock(long timeout, unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;

        d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断

 

       synchronized和ReentrantLock的功能相近,使用两者有什么区别呢?

       1. ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。

       2. ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断。如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情。

       3. synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中。

 

       以上是对锁的一些初步的认识。

——2013年1月28日记于蓝杰公司

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics