java多线程9.使用显式锁

在协调共享对象的访问时可以使用的机制有synchronized和volatile。java 5.0新增了一种新的机制:ReentrankLock。

ReentrankLock并不是一种替代内置加锁的方法,而是当内置加锁机制不适用时,作为一种可选择的高级功能。与无条件的锁获取模式相比,它具有更完善的错误恢复机制,而且它能够支持中断。
Lock与ReentrantLock

Lock提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁的方法都是显示地。

在Lock的实现中必须提供与内部锁相同的内存可见性语义,但在加锁语义、调度算法、顺序保证以及性能特性等方面可以有所不同。

public interface Lock{
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long timeout,TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

ReentrantLock实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性。在获取ReentrantLock时,有着与进入同步代码块相同的内存语义,在释放ReentrantLock时,同样有着与退出同步代码块相同的内存语义。

ReentrantLock支持在Lock接口中定义的所有获取锁模式,并且为处理锁的不可用性问题提供了更高的灵活性。

Lock接口的标准使用形式:

   /**
     * 必须在finally块中释放锁,否则,如果在被保护的代码中抛出了异常,那么这个锁永远都无法释放。
     * 当使用加锁时,还必须考虑在try块中抛出异常的情况
     * 如果可能使对象处于某种不一致的状态,那么就需要更多的try-catch或try-finally代码块。
     * 如果没有使用finally来释放Lock,那么相当于启动了一个定时炸弹。当炸弹爆炸时,将很难追踪到最初发生错误的位置,因为没有记录应该释放锁的位置和时间。
     * 这就是ReentrantLock不能完全替代synchronized的原因,它更加危险。在java6之后,改进了算法来管理内置锁。
     */
    protected void terminated(){
        Lock lock = new ReentrantLock();
        lock.lock();
        try{
            //更新对象状态
            //捕获异常,并在必要时恢复不变性条件
        }finally{
            lock.unlock();
        }
    }

轮询锁与定时锁

可定时与可轮询的锁获取模式由tryLock方法实现,与无条件的锁获取模式相比,它具有更完善的错误恢复机制。

在内置锁中,死锁是一个严重的问题,恢复程序的唯一方法是重新启动程序,而防止死锁的唯一方法就是在构造程序时避免出现不一致的锁顺序。

可定时与可轮询的锁提供了另一种选择:避免死锁的发生。

   /**
     * 示例:通过tryLock来避免锁顺序死锁。
     * 使用tryLock来获取两个锁,如果不能同时获得,那么就回退并重新尝试。在休眠时间中包括固定部分和随机部分,从而降低发生活锁的可能性。
     * 如果在指定时间内不能获得所有需要的锁,那么transferMoney将返回一个失败的状态。
     *
     * @param fromAcct
     * @param toAcct
     * @param amount
     * @param timeout
     * @param unit
     * @return
     * @throws InterruptedException
     */
    public boolean transferMoney(Account fromAcct,Account toAcct,
            DollarAmount amount,long timeout,TimeUnit unit)throws InterruptedException{
        long fixedDelay = getFixedDelayComponentNanos(timeout,unit);
        long randMod = getRandomDelayModulusNanos(timeout,unit);
        long stopTime = System.nanoTime() + unit.toNanos(timeout);
        while(true){
            if(fromAcct.lock.tryLock()){
                try{
                    if(toAcct.lock.tryLock()){
                        try{
                            if(fromAcct.getBlance()compareTo(amount) < 0){
                                throw new InsufficientFundsException();
                            }else{
                                fromAcct.debit(amount);
                                toAcct.credit(amount);
                                return true;
                            }
                        }finally{
                            toAcct.lock.unlock();
                        }
                    }
                }finally{
                    fromAcct.lock.unlock();
                }
            }
            if(System.nanoTime() < stopTime){
                return false;
            }
            TimeUnit.NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);
        }
    }
   /**
     * 示例:带有时间限制的加锁
     * 确保对资源进行串行访问的方法:一个单线程Executor;另一个方法是使用一个独占锁来保护对资源的访问。
     * 试图在Lock保护的共享通信线路上发送一条消息,如果不能再指定时间内完成,代码就会失败。tryLock能够在这种带有时间限制的操作中实现独占的加锁行为。
     */
    Lock lock = new ReentrantLock();

    public boolean trySendOnSharedLine(String meassage,long timeout,TimeUnit unit) throws InterruptedException{
        long nanosToLock = unit.toNanos(timeout) - estimatedNanosToSend(meassage);
        if(lock.tryLock(nanosToLock, TimeUnit.NANOSECONDS)){
            return false;
        }
        try{
            return sendOnSharedLine(meassage);
        }finally{
            lock.unlock();
        }
    }
   /**
     * 示例:可中断的锁获取操作
     * lockInterruptibly方法能够在获得锁的同时保持对中断的响应,并且由于它包含在Lock中,因此无须创建其他类型的不可中断阻塞机制。
     *
     * @param message
     * @return
     * @throws InterruptedException
     */
    public boolean sendOnSharedLine(String message) throws InterruptedException{
        lock.lockInterruptibly();
        try{
            return cancellableSendOnSharedLine(meassage);
        }finally{
            lock.unlock();
        }
    }

    private boolean cancellableSendOnSharedLine(String message) throws InterruptedException{
        //...
        return false;
    }

公平性

ReentrantLock的构造函数中提供了两种公平性的选择:创建一个非公平的锁默认(或者一个公平的锁)。

在公平的锁上,线程将按照它们发出请求的顺序来获得锁,但在非公平的锁上,则允许插队:

当一个线程请求非公平的锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有的等待线程并获得这个锁,而在公平的锁中,如果另一个线程持有这个锁或者有其他线程在队列中等待这个锁,那么新发出请求的线程将被放入队列中。在非公平的锁中,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中。因此当执行加锁操作时,公平性将由于在挂起线程和恢复线程时存在的开销而极大地降低性能。
在synchronized和ReentrantLock之间的选择

在一些内置锁无法满足需求的情况下,ReentrantLock可以作为一种高级工具。当需要一些高级功能时才应该使用ReentrantLock,这些功能包括:可定时的、可轮询的与可中断的锁获取操作,公平队列,以及非块结构的锁。否则,还是应该优先使用synchronized。

java5中,内置锁与ReentrantLock相比还有另一个优点:在线程转储中能给出在哪些调用帧中获得了哪些锁,并能检测和识别发生死锁的线程。而JVM并不知道哪些线程持有ReentrantLock,因此在调试使用ReentrantLock的线程的问题时,将起不到帮助作用。java6解决了这个问题,它提供了一个管理和调试接口,锁可以通过该接口进行注册,从而与ReentrantLock相关的加锁信息就能出现在线程转储中,并通过其他的管理接口和调试接口来访问。

synchronized是JVM的内置属性,它能执行一些优化,例如对线程封闭的锁对象的锁消除优化,通过增加锁的粒度来消除内置锁的同步,而如果通过基于类库的锁来实现这些功能,则可能性不大。

读 - 写锁

    public interface ReadWriteLock{
        Lock readLock();
        Lock WriteLock();
    }

实现ReadWriteLock需要考虑的一些问题:

释放优先:当一个写入操作释放写入锁时,并且队列中同时存在读线程和写线程,那么应该优先选择读线程,写线程,还是最先发出请求的线程。

读线程插入:如果锁是由读线程持有,但有写线程正在等待,那么新到达的读线程能否立即获得访问权,还是应该在写线程后面等待?如果允许读线程插队到写线程之前,那么将提高并发性,但却可能造成写线程发生饥饿问题。

重入性:读取锁和写入锁是否是可重入的。

降级:如果一个线程持有写入锁,那么它能否在不释放该锁的情况下获得读取锁?这可能会使得写入锁被降级为读取锁,同时不允许其他写线程修改被保护的资源。

升级:读取锁能否优先于其他正在等待的读线程和写线程而升级为一个写入锁?在大多数的读写锁实现中并不支持升级,因为如果没有显示地升级操作,那么很容易造成死锁(如果两个读线程试图同时升级为写入锁,那么二者都不会释放读取锁)。

ReentrantReadWriteLock为这两种锁都提供了可重入的加锁语义,默认也是非公平锁。

当锁的持有时间较长并且大部分操作都不会修改被守护的资源时,那么读写锁能提高并发性。

/**
 * ReadWriteMap中使用了ReentrantReadWriteLock来包装Map,从而使它能够在多个线程之间被安全的共享,并且能够避免读写和谢谢冲突。
 * 实际上ConcurrentHashMap的性能已经很好了,如果需要对另一种Map实现如LinkedHashMap提供并发性更高的访问,可以考虑。
 *
 * @param <K>
 * @param <V>
 */
public class ReadWriteMap<K,V> {
    private final Map<K,V> map;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock r = lock.readLock();
    private final Lock w = lock.writeLock();

    public ReadWriteMap(Map<K,V> map){
        this.map = map;
    }

    public V put(K key,V value){
        w.lock();
        try{
            return map.put(key, value);
        }finally{
            w.unlock();
        }
    }
    //remove() putAll() clear()

    public V get(Object key){
        r.lock();
        try{
            return map.get(key);
        }finally{
            r.unlock();
        }
    }
}

#笔记内容参考  《java并发编程实战》

原文地址:https://www.cnblogs.com/shanhm1991/p/9899992.html

时间: 2024-10-06 17:13:39

java多线程9.使用显式锁的相关文章

java之AQS和显式锁

本次内容主要介绍AQS.AQS的设计及使用.ReentrantLock.ReentrantReadWriteLock以及手写一个可重入独占锁 1.什么是AQS? AQS,队列同步器AbstractQueuedSynchronizer的简写,JDK1.5引入的,是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作.AQS的作者Doug Lea大师期望它能够成为实现大部分同步需求的基础. 2.AQS的设计及其作用 Abst

显式锁(四)Lock的等待通知机制Condition

?? 任意一个Java对象,都拥有一组监视器方法(定义在根类Object上),主要包括:wait( ).wait(long timeout).notify().notifyAll()方法:这些方法与关键字synchronized结合使用,可以实现 隐式锁的等待/通知机制.而显示锁Lock也实现了等待/通知机制:Condition接口也提供了类似Object的监视器方法,与Lock配合使用可以实现 显式锁的等待/通知机制,但是两者在使用方式和功能特性有所差别.总得来说,Condition接口更加灵

并发编程—4显式锁 Lock

目录 4.显式锁 Lock 4.1 概念 内置锁 vs 显示锁 可重入锁 vs 不可重入锁 公平锁 vs 非公平锁 读锁 vs 写锁 4.2 ReentrantLock源码解读 4.显式锁 Lock 4.1 概念 内置锁 vs 显示锁 synchronize是java语言层面实现的锁,称为内置锁.使用方便代码简洁,而且在jdk新版本优化后,性能也得到了很大的提高.synchronize是一个可重入锁.而Lock是jdk提供开发者是用的一个显式锁.通过lock()和unlock()方法加锁和释放锁

java基础知识回顾之java Thread类学习(六)--java多线程同步函数用的锁

1.验证同步函数使用的锁----普通方法使用的锁 思路:创建两个线程,同时操作同一个资源,还是用卖票的例子来验证.创建好两个线程t1,t2,t1线程走同步代码块操作tickets,t2,线程走同步函数封装的代码操作tickets,同步代码块中的锁我们可以指定.假设我们事先不知道同步函数用的是什么锁:如果在同步代码块中指定的某个锁(测试)和同步函数用的锁相同,就不会出现线程安全问题,如果锁不相同,就会发生线程安全问题. 看下面的代码:t1线程用的同步锁是obj,t2线程在操作同步函数的资源,假设不

第13章 显式锁

性能是一个不断变化的指标,如果在昨天的测试基准中发现X比Y更快,那么在今天就可能已经过时了. 在激烈竞争的情况下,在非公平锁的性能高于公平锁的性能的一个原因是:在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟.假设线程A持有一个锁,并且线程B请求这个锁.由于这个锁已被线程A持有,因此B将被挂起.当A释放锁时,B将被唤醒,因此会再次尝试获取锁.与此同时,如果C也请求这个锁,那么C很可能会在B被完全唤醒之前获得.使用以及释放这个锁.这样的情况是一种“双赢”的局面:B获得锁的时刻并没有推

JDK并发包温故知新系列(五)—— 显式锁与显式条件

显式锁-Lock与ReadWriteLockJDK针对Lock的主要实现是ReentrantLock,ReadWriteLock实现是ReentrantReadWriteLock.本文主要介绍ReentrantLock. ReentrantReadWriteLock两把锁共享一个等待队列,两把锁的状态都由一个原子变量表示,特有的获取锁和释放锁逻辑. ReentrantReadWriteLock的基本原理:读锁的获取,只要求写锁没有被线程持有就可以获取,检查等待队列,逐个唤醒等待读锁线程,遇到等待

在不使用显式锁的方式下使用多线程

一个串被定义为序列的调用事件句柄(非并行调用),使用串允许在多线程环境中执行代码而不使用显示的互斥锁. 串可以是隐式的或者显式的,如下方的可替代方法所示: 仅在一个线程中调用io_service::run()意味着使用隐式的串执行所有的事件句柄,因为io_service确保了句柄只被run()内部调用. 当有一个只和一个连接关联的异步操作链时(比如半双工的协议HTTP),不可能并发的执行句柄,这是一个隐式的串. 显式的串调用是一个io_service::strand的实例,所有的事件句柄函数需要

Java并发编程系列-(4) 显式锁与AQS

4 显示锁和AQS 4.1 Lock接口 核心方法 Java在java.util.concurrent.locks包中提供了一系列的显示锁类,其中最基础的就是Lock接口,该接口提供了几个常见的锁相关的操作. public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit

无废话Android之smartimageview使用、android多线程下载、显式意图激活另外一个activity,检查网络是否可用定位到网络的位置、隐式意图激活另外一个activity、隐式意图的配置,自定义隐式意图、在不同activity之间数据传递(5)

1.smartimageview使用 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"