转自 http://agrael.iteye.com/blog/685840
本文是讲述ReentrantLock类与synchronized关键字同时使用的问题,不是ReentrantLock类与synchronized关键字的教程。
synchronized关键字作为java多线程编程中非常重要的关键字之一,它维护这线程并发中的安全。通常使用synchronized有2种方式。
锁定当前实例
Java代码
- //通过方法上使用synchronized达到锁定效果
- public synchronized void xxx() {
- //...
- }
- //通过锁定指定的实例达到锁定效果
- public void yyy(){
- synchronized (this) {
- //...
- }
- }
- public void zzz(){
- synchronized (xObject) {
- //...
- }
- }
其中第一种和第二种都是对当前方法属于的对象实例的琐定,而第三种为锁定指定的实例。
本文不打算详细讲解synchronized关键字,有关synchronized的详细说明请参考其他资料。
java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。ReentrantLock作为Lock接口的实现,定义了可重入锁。根据API的说明:“一个可重入的互斥锁定 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大。”可以发现,ReentrantLock的最基本的作用就是实现了使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义。
ReentrantLock提供的方法比较多,但这里我们只讨论实现synchronized相同功能的方式。
API中所使用的示例为:
Java代码
- class X {
- private final ReentrantLock lock = new ReentrantLock();
- // ...
- public void m() {
- lock.lock(); // block until condition holds
- try {
- // ... method body
- } finally {
- lock.unlock()
- }
- }
- }
可以看出,如果上面代码换成synchronized的话,应该是:
Java代码
- public synchronized void m() {
- // ... method body
- }
或者
Java代码
- public void m() {
- synchronized (this) {
- // ... method body
- }
- }
锁定的是当前的实例。这也是本文的重点。(关于ReentrantLock的更多信息,请参考其他资料)
既然ReentrantLock和synchronized都提供了相同的行为(这里不讨论性能问题),那么在使用过程中,对于线程并发编程,使用ReentrantLock与synchronized都是可以的,他们也都可以工作得很好。但是,如果同时使用它们两个呢?结果又会是怎么样呢?
看如下代码:
Java代码
- package cn.agrael.test.thread;
- import java.util.concurrent.locks.ReentrantLock;
- public class ReentrantLockAndSynchronized {
- private final ReentrantLock lock = new ReentrantLock();
- private volatile int i = 0;
- public void lockAdd() throws Exception {
- lock.lock();
- try {
- check("lockAdd");
- i++;
- i++;
- } finally {
- lock.unlock();
- }
- }
- public synchronized void synchronizedAdd() throws Exception {
- check("synchronizedAdd");
- i++;
- i++;
- }
- // public void synchronizedAdd() throws Exception {
- // lock.lock();
- // try {
- // check("lockAdd");
- // i++;
- // i++;
- // } finally {
- // lock.unlock();
- // }
- // }
- private void check(String methodName) throws Exception {
- if (i % 2 != 0) {
- throw new Exception(methodName + " : " + i);
- }
- }
- public static void main(String[] args) throws Exception {
- final ReentrantLockAndSynchronized add = new ReentrantLockAndSynchronized();
- Thread thread1 = new Thread(new Runnable() {
- public void run() {
- try {
- while (true) {
- add.lockAdd();
- }
- } catch (Exception e) {
- e.printStackTrace();
- System.exit(0);
- }
- }
- });
- Thread thread2 = new Thread(new Runnable() {
- public void run() {
- try {
- while (true) {
- add.synchronizedAdd();
- }
- } catch (Exception e) {
- e.printStackTrace();
- System.exit(0);
- }
- }
- });
- thread1.start();
- thread2.start();
- }
- }
其中有个int型的i变量,并提供了使用ReentrantLock锁定的lockAdd方法与使用synchronized锁定的synchronizedAdd方法,这2个方法都提供相同的操作,先验证i是否为偶数,如果不是则抛出异常,并且提供2次i++的操作。java中的i++并非原子性的操作,会涉及读和写,再者提供2次i++,如果是这样的话,会出现并发问题,所以我们提供了ReentrantLock以及synchronized来锁定,保证线程安全。如果我们的想法可行,那么i永远被读到的结果都是偶数,也就不永远不会抛我们所指定的异常。但是结果却不是这样,运行一会后,就抛出了异常,证明我们的想法失败了。因为ReentrantLock与synchronized所提供的机制不同,导致了他们是相对独立的,相当于是两把锁,各自锁定各自的。
所以最后我们下的结论就是不要同时使用ReentrantLock类与synchronized关键字锁定会修改同一个资源的不同方法。