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日记于蓝杰公司
相关推荐
使用方法 自己先安装Curl 检测命令bash <...备用命令bash <...特性 支持双栈测试 支持输出解锁的国家 解锁类型 不服务此地区(浏览器打开Netflix提示不可用) ...就是闲得无聊整的玩意,也有很多带佬反馈,
使用方法自己先安装Curl和JQ检测命令bash <...我真是厚颜无耻啊关于就是闲得无聊整的玩意,也有很多带佬反馈,有问题也会及时更新的
所以顺手做了这么个小玩意,打开一个空白透明的悬浮窗,使屏幕不可点击(有特殊情况,比如说我用的小米,按清理键(虚拟键最侧的那个键)清理内存是可以把app杀死的,这时候就GG了,对策就是将app锁住,看图: ...
* Graviboard:图标生气了,需要配合 Activator 使用,骗小 MM 很好用的一个小玩意。 * Icon Renamer:给程序改一个你喜欢的名字,比如把 QQ 改成 OO。 四、快捷方式类插件 * iFile:修改系统文件必备程序,比起 ...
本人以人格担保不会在其中添加rootkit,木马,病毒一类的无聊玩意!!!请放心运行。 请首先检查unlocker.zip文件的MD5值 : B35968822E46BC8AEE5AD4A7C859DECB 无误后可以解压运行unlocker软件。 2个OCX文件是VB6的...