• 不要同时使用ReentrantLock类与synchronized关键字锁定会修改同一个资源的不同方法


    转自 http://agrael.iteye.com/blog/685840

    本文是讲述ReentrantLock类与synchronized关键字同时使用的问题,不是ReentrantLock类与synchronized关键字的教程。 
        synchronized关键字作为java多线程编程中非常重要的关键字之一,它维护这线程并发中的安全。通常使用synchronized有2种方式。 
    锁定当前实例 

    Java代码  收藏代码
    1. //通过方法上使用synchronized达到锁定效果  
    2. public synchronized void xxx() {  
    3.     //...  
    4. }  
    5.   
    6. //通过锁定指定的实例达到锁定效果  
    7. public void yyy(){  
    8.    synchronized (this) {  
    9.       //...  
    10.    }  
    11. }  
    12.   
    13. public void zzz(){  
    14.    synchronized (xObject) {  
    15.       //...  
    16.    }  
    17. }  


    其中第一种和第二种都是对当前方法属于的对象实例的琐定,而第三种为锁定指定的实例。 
    本文不打算详细讲解synchronized关键字,有关synchronized的详细说明请参考其他资料。 


    java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。ReentrantLock作为Lock接口的实现,定义了可重入锁。根据API的说明:“一个可重入的互斥锁定 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大。”可以发现,ReentrantLock的最基本的作用就是实现了使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义。 
    ReentrantLock提供的方法比较多,但这里我们只讨论实现synchronized相同功能的方式。 
    API中所使用的示例为: 

    Java代码  收藏代码
    1. class X {  
    2.   private final ReentrantLock lock = new ReentrantLock();  
    3.   // ...  
    4.   
    5.   public void m() {   
    6.     lock.lock();  // block until condition holds  
    7.     try {  
    8.       // ... method body  
    9.     } finally {  
    10.       lock.unlock()  
    11.     }  
    12.   }  
    13. }  


    可以看出,如果上面代码换成synchronized的话,应该是: 

    Java代码  收藏代码
    1. public synchronized void m() {   
    2.     // ... method body  
    3. }  


    或者 

    Java代码  收藏代码
    1. public void m() {   
    2.    synchronized (this) {  
    3.     // ... method body  
    4.    }  
    5. }  


    锁定的是当前的实例。这也是本文的重点。(关于ReentrantLock的更多信息,请参考其他资料) 
        既然ReentrantLock和synchronized都提供了相同的行为(这里不讨论性能问题),那么在使用过程中,对于线程并发编程,使用ReentrantLock与synchronized都是可以的,他们也都可以工作得很好。但是,如果同时使用它们两个呢?结果又会是怎么样呢? 
    看如下代码: 

    Java代码  收藏代码
    1. package cn.agrael.test.thread;  
    2.   
    3. import java.util.concurrent.locks.ReentrantLock;  
    4.   
    5. public class ReentrantLockAndSynchronized {  
    6.   
    7.     private final ReentrantLock lock = new ReentrantLock();  
    8.       
    9.     private volatile int i = 0;  
    10.       
    11.     public void lockAdd() throws Exception {  
    12.         lock.lock();  
    13.         try {  
    14.             check("lockAdd");  
    15.             i++;  
    16.             i++;  
    17.         } finally {  
    18.             lock.unlock();  
    19.         }  
    20.     }  
    21.       
    22.     public synchronized void synchronizedAdd() throws Exception {  
    23.         check("synchronizedAdd");  
    24.         i++;  
    25.         i++;  
    26.     }  
    27.       
    28. //  public void synchronizedAdd() throws Exception {  
    29. //      lock.lock();  
    30. //      try {  
    31. //          check("lockAdd");  
    32. //          i++;  
    33. //          i++;  
    34. //      } finally {  
    35. //          lock.unlock();  
    36. //      }  
    37. //  }  
    38.       
    39.     private void check(String methodName) throws Exception {  
    40.         if (i % 2 != 0) {  
    41.             throw new Exception(methodName + " : " + i);  
    42.         }  
    43.     }  
    44.       
    45.     public static void main(String[] args) throws Exception {  
    46.         final ReentrantLockAndSynchronized add = new ReentrantLockAndSynchronized();  
    47.         Thread thread1 = new Thread(new Runnable() {  
    48.               
    49.             public void run() {  
    50.                 try {  
    51.                     while (true) {  
    52.                         add.lockAdd();  
    53.                     }  
    54.                 } catch (Exception e) {  
    55.                     e.printStackTrace();  
    56.                     System.exit(0);  
    57.                 }  
    58.             }  
    59.         });  
    60.         Thread thread2 = new Thread(new Runnable() {  
    61.               
    62.             public void run() {  
    63.                 try {  
    64.                     while (true) {  
    65.                         add.synchronizedAdd();  
    66.                     }  
    67.                 } catch (Exception e) {  
    68.                     e.printStackTrace();  
    69.                     System.exit(0);  
    70.                 }  
    71.             }  
    72.         });  
    73.         thread1.start();  
    74.         thread2.start();  
    75.     }  
    76. }  


    其中有个int型的i变量,并提供了使用ReentrantLock锁定的lockAdd方法与使用synchronized锁定的synchronizedAdd方法,这2个方法都提供相同的操作,先验证i是否为偶数,如果不是则抛出异常,并且提供2次i++的操作。java中的i++并非原子性的操作,会涉及读和写,再者提供2次i++,如果是这样的话,会出现并发问题,所以我们提供了ReentrantLock以及synchronized来锁定,保证线程安全。如果我们的想法可行,那么i永远被读到的结果都是偶数,也就不永远不会抛我们所指定的异常。但是结果却不是这样,运行一会后,就抛出了异常,证明我们的想法失败了。因为ReentrantLock与synchronized所提供的机制不同,导致了他们是相对独立的,相当于是两把锁,各自锁定各自的。 
        所以最后我们下的结论就是不要同时使用ReentrantLock类与synchronized关键字锁定会修改同一个资源的不同方法。

  • 相关阅读:
    CSPS模拟 49
    StrGame
    CSPS模拟 48
    [没有证明]原根求法
    CSPS模拟 47
    CSPS模拟 46
    CSPS模拟 45 乔迁之喜
    CSPS模拟 44
    平衡二叉树
    go语言学习--指针数组和数组指针
  • 原文地址:https://www.cnblogs.com/hkzws/p/4828553.html
Copyright © 2020-2023  润新知