Java并发显式锁和显式条件队列

简介:

一 显式锁

   在类中利用synchronized修饰的方法或者this代码块,均使用的是类的实例锁或者类的锁。这些锁都称为内置锁。

   可以利用显式锁进行协调对象的访问。即ReentrantLock。这是一种可以提供无条件,可轮询,定时以及可中断的锁获取操作。对于锁的所有加锁和解锁都是显式的。常规的内置锁是无法中断一个正在等待获取锁的线程,以及无法在请求获取一个锁的无限等待下去。

ReentrantLock标准使用方式

1
2
3
4
5
6
7
Lock lock= new  ReentrantLock();
lock.lock();
try {
//具体任务
} finally {
  lock.unlock();
}

   由于内置锁中防止死锁的发生就是避免出现锁的顺序不当导致的。利用这显式锁可以避免死锁的发生。当不能要获取所有的锁的时候,那么就可以利用显式的方法来释放已经有的锁。然后再轮询的重新获取锁的控制权。

几个关键的方法说明:

lock():阻塞的方法

  如果该锁没有被任何线程所拥有,则获取锁,立即返回,锁计数器加1.

  如果该锁已被当前线程所持有,则立即返回,锁计数器加1.

  如果该锁已被其他线程所拥有,则禁用当前线程,使得其休眠阻塞。


tryLock(time):可阻塞可中断的方法

  如果该锁在给定时间内没有被任何线程所持有,且当前线程没有被中断,则获取锁,立即返回true,锁计数器加1.

  如果该锁已被当前线程所持有,则立即返回true,锁计数器加1.

  如果该锁已被其他线程所拥有,则禁用当前线程,使其休眠阻塞,直到发生以下情况:

  当前线程获取锁,当前线程被中断,时间到了  

返回状态:成功获取锁或者已经持有,则返回true。时间到了还没有获取锁,则返回false


  lockInterruptibly():可阻塞可中断的方法

  这种锁的获取必须在当前线程没有被中断的时候才能尝试获取锁

  如果该锁没有被任何线程所拥有,则获取锁,立即返回,锁计数器加1.

  如果该锁已被当前线程所持有,则立即返回,锁计数器加1.

  如果该锁已被其他线程所拥有,则禁用当前线程,使得其休眠阻塞,直到发生以下情况:

  当前线程获取锁,当前线程被中断


tryLock():非阻塞的方法

如果该锁没有被任何线程保持,则获取锁,立即返回true,锁计数器加1.

  如果该锁已被当前线程所持有,则立即返回true,锁计数器加1.

  如果该锁已被其他线程所拥有,则立即返回false。


unlock():释放锁

如果当前线程是此锁所有者,则将保持计数减 1。如果保持计数现在为 0,则释放该锁

  在利用ReentrantLock构造函数的时候,可以传递一个boolean,当不传入模式是false,即创建一个非公平锁,当传入true的时候,创建一个公平的锁。

所谓的公平的锁,线程按照它们发出请求的顺序来获得锁。但在非公平的锁上,允许刚请求的锁直接马上获取锁。一般非公平锁的性能要高于公平锁的性能,这是因为恢复一个被挂起的线程以及真正开始运行需要一点时间,如果在此刻能将这段时间让立刻要获取锁的线程,则就会提高吞吐量。

synchronized与ReentrantLock的选择

一般直接使用synchronized,只有需要使用到可定时的,可轮询的,可中断的锁获取操作,公平队列的时候才可以考虑使用ReentrantLock。

 在对于读写操作中,也可以利用显式锁中的读-写锁进行,即ReentrantReadWriteLock.


二 显式条件队列

   可以利用wait,notify,notifyAll实现自定义同步的过程(详见我的系列的多线程设计模式)。一般利用这种方式将内置锁与内置条件队列统一在一个类中,即一个类即是内置锁,又充当了条件队列了。凡是对于该类的wait都处于这个类的专有wait set中,这里将wait set称为条件队列。

   条件队列指的就是它使得一组线程集合能够通过某种方式来等待特定的条件成真。这里的条件队列里存放的是一个个正在等待条件成真的线程。

   所有等待的线程都必须要等待某种条件成真才进而执行。对于这种条件称作为条件谓词(或警戒条件)。条件谓词是依赖于许多个状态变量的,对于这些状态变量必须要由一个锁来保护,在每次测试条件谓词的时候必须先获取该锁,则就要求了锁对象和条件队列对象必须是同一个对象。

状态依赖的标准形式:这里和多线程设计模式中的Guarded Suspension Pattern一样的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
<pre  class = "brush:java;toolbar:false;" >
         public  synchronized  void  concreteWork()
{
    while (!condition)
    {
       try {
          wait(); //此时当前线程就进入了该this对象的条件队列中等候
        } catch (InterruptedException e)
        {  }
    }
   //具体的工作,当条件谓词成功后执行的
}
//必须要保证在调用wait之前和从wait唤醒后都要对于条件谓词测试

状态通知的形式:

每当在等待一个条件的时候,一定要保证在条件谓词变成真的时候通过某种方式发出通知。一般就是利用Object的notifyAll和notify来实现。一般采用notifyAll来实现,虽然可能会导致上下文切换等性能。但是这种能保证所有的线程都能进行公平竞争,防止信号丢失。

   一般多个线程可能会在同一个对象的同一个条件队列上等待多个不同的添加谓词。使用notify可能会唤醒本不该唤醒的线程。譬如,当线程A在条件队列中等待条件谓词PA,线程B在条件队列中等待条件谓词PB,若线程C执行了操作,使得PB变成真,此时利用notify,可能会唤醒线程A,A被唤醒发现PA还是不为真,则继续等待,此时线程B则以为PB没有真,就会彻底的丢失了信号,从而再次等待下去


   使用notify的情况仅仅同时满足“所有等待线程都是等待相同的条件谓词”和“每次最多只能唤醒一个线程来执行”。

   ReentrantLock是Lock的子类,它是一种广义的内置锁,Condition则是一种广义的内置条件队列。由于多个线程可能会在同一个条件队列中等待不同的条件谓词,有时候为了方便的进行管理,可以针对不同的条件谓词设置不同的条件队列来等待。

   一般一个Condition和一个Lock关联在一起,就是如同一个内置锁和内置条件队列关联在同一个对象中。一般创建Condition,就是在需要关联的Lock上调用Lock.newCondition方法。一个Lock则可以对应多个Condition对象。在Condition中,与Object中wait,notify,notifyAll所对应的方法分别为await,signal,signalAll。

显式的Condition和内置条件队列的选择:

一般还是选择内置条件队列,当需要使用公平队列或在每个锁上对应多个等待线程集的时候,就可以运用Condition了

代码实例:一个利用Condition和Lock来实现的有界缓存,里面有两种条件队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package  whut.usersynchrnoized;
import  java.util.concurrent.locks.Condition;
import  java.util.concurrent.locks.Lock;
import  java.util.concurrent.locks.ReentrantLock;
//利用Condition和Lock来实现有界缓存
public  class  ConditionBoundedBuffer<T> {
     private  final  Lock lock =  new  ReentrantLock();
     private  final  Condition full = lock.newCondition();
     private  final  Condition empty = lock.newCondition();
     private  final  T[] items;
     private  int  tail, head, count;
     public  ConditionBoundedBuffer( int  size) {
         items = (T[])  new  Object[size];
     }
     // 阻塞直到notfull
     public  void  put(T x)  throws  InterruptedException {
         lock.lock();
         try  {
             while  (count == items.length)
                 full.await(); // 满了,则要在full条件队列中等待
             items[tail] = x;
             if  (++tail == items.length)
                 tail =  0 ;
             ++count;
             empty.signal(); // 每次唤醒一个线程
         finally  {
             lock.unlock();
         }
     }
     // 阻塞直到notEmpty
     public  T take()  throws  InterruptedException {
         lock.lock();
         try  {
             while  (count ==  0 )
                 empty.await(); // 空了,则要在empty条件队列中等待
             T x = items[tail];
             items[tail] =  null ;
             if  (++head == items.length)
                 head =  0 ;
             --count;
             full.signal(); // 每次唤醒一个线程
             return  x;
         finally  {
 



本文转自 zhao_xiao_long 51CTO博客,原文链接:http://blog.51cto.com/computerdragon/1216434
相关文章
|
7天前
|
Java
Java中ReentrantLock释放锁代码解析
Java中ReentrantLock释放锁代码解析
24 8
|
7天前
|
Java 调度
Java中常见锁的分类及概念分析
Java中常见锁的分类及概念分析
13 0
|
1天前
|
Java
浅谈Java的synchronized 锁以及synchronized 的锁升级
浅谈Java的synchronized 锁以及synchronized 的锁升级
5 0
|
1天前
|
存储 缓存 安全
Java并发基础之互斥同步、非阻塞同步、指令重排与volatile
在Java中,多线程编程常常涉及到共享数据的访问,这时候就需要考虑线程安全问题。Java提供了多种机制来实现线程安全,其中包括互斥同步(Mutex Synchronization)、非阻塞同步(Non-blocking Synchronization)、以及volatile关键字等。 互斥同步(Mutex Synchronization) 互斥同步是一种基本的同步手段,它要求在任何时刻,只有一个线程可以执行某个方法或某个代码块,其他线程必须等待。Java中的synchronized关键字就是实现互斥同步的常用手段。当一个线程进入一个synchronized方法或代码块时,它需要先获得锁,如果
15 0
|
4天前
|
Java 程序员 编译器
Java中的线程同步与锁优化策略
【4月更文挑战第14天】在多线程编程中,线程同步是确保数据一致性和程序正确性的关键。Java提供了多种机制来实现线程同步,其中最常用的是synchronized关键字和Lock接口。本文将深入探讨Java中的线程同步问题,并分析如何通过锁优化策略提高程序性能。我们将首先介绍线程同步的基本概念,然后详细讨论synchronized和Lock的使用及优缺点,最后探讨一些锁优化技巧,如锁粗化、锁消除和读写锁等。
|
4天前
|
Java 编译器
Java并发编程中的锁优化策略
【4月更文挑战第13天】 在Java并发编程中,锁是一种常见的同步机制,用于保证多个线程之间的数据一致性。然而,不当的锁使用可能导致性能下降,甚至死锁。本文将探讨Java并发编程中的锁优化策略,包括锁粗化、锁消除、锁降级等方法,以提高程序的执行效率。
11 4
|
12天前
|
安全 Java 调度
深入理解Java中的线程安全与锁机制
【4月更文挑战第6天】 在并发编程领域,Java语言提供了强大的线程支持和同步机制来确保多线程环境下的数据一致性和线程安全性。本文将深入探讨Java中线程安全的概念、常见的线程安全问题以及如何使用不同的锁机制来解决这些问题。我们将从基本的synchronized关键字开始,到显式锁(如ReentrantLock),再到读写锁(ReadWriteLock)的讨论,并结合实例代码来展示它们在实际开发中的应用。通过本文,读者不仅能够理解线程安全的重要性,还能掌握如何有效地在Java中应用各种锁机制以保障程序的稳定运行。
|
13天前
|
Java 编译器
Java并发编程中的锁优化策略
【4月更文挑战第5天】随着多核处理器的普及,并发编程在提高程序性能方面发挥着越来越重要的作用。在Java中,锁是实现并发控制的关键机制。本文将探讨Java并发编程中的锁优化策略,包括锁粗化、锁消除、锁排序等技术,以提高程序的执行效率和降低资源争用。
|
4月前
|
存储 监控 安全
Java虚拟机的锁优化策略
Java虚拟机的锁优化策略
27 0
|
7月前
|
Java 编译器
解密Java多线程中的锁机制:CAS与Synchronized的工作原理及优化策略
解密Java多线程中的锁机制:CAS与Synchronized的工作原理及优化策略