资讯专栏INFORMATION COLUMN

Java中的锁

gaara / 2208人阅读

摘要:当前线程在超时时间内被中断超时时间结束,返回释放锁获取等待通知组件,该组件和当前的锁绑定,当前线程只有获取了锁,才能调用该组件的方法,调用后,当前线程将释放锁。同步器是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。

本文在参考java并发编程实战后完成,参考内容较多

Java中的锁

锁是用来控制多线程访问共享资源的方式,一个锁能够防止多个线程同事访问共享资源。在Lock接口出现之前,Java程序是通过synchronized来实现锁功能的,在JDK1.5之后,新增的Lock接口可以实现锁功能,他的功能与Synchronized类似,但是需要显式的获取和释放锁,他失去了隐式获取释放锁的便捷性,但是可操作性更强,同时具有可中断获取锁以及超时获取锁的特性。

Lock接口

Lock接口提供了几个synchronized不具备的主要特性:

尝试非阻塞的获取锁

能被中断的获取锁

超时获取锁

Lock是一个接口,定义如下:

package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;

/**
 * @see ReentrantLock
 * @see Condition
 * @see ReadWriteLock
 *
 * @since 1.5
 * @author Doug Lea
 */
public interface Lock {

    /**
     * 线程获取锁,如果获取锁失败,线程无法向下执行
     */
    void lock();

    /**
     * 可中端的获取锁,和lock()相比这个方法可以响应中断,就是在获取锁的过程中可以中断当前线程
     */
    void lockInterruptibly() throws InterruptedException;

    /**
     * 尝试非阻塞的获取锁,调用该方法后立刻返回,获取锁成功返回true,否则返回false
     */
    boolean tryLock();

    /**
     * 超时的获取锁,在发生下面的情况下会返回:
     * 1、在超时时间范围内获取锁成功立刻返回。2、当前线程在超时时间内被中断 3、超时时间结束,返回false
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    /**
     * 释放锁
     */
    void unlock();

    /**
     * 获取等待通知组件,该组件和当前的锁绑定,当前线程只有获取了锁,才能调用该组件的wait方法,调用后,当前线程将释放锁。
     */
    Condition newCondition();
}
队列同步器
队列同步器AbstractQueuedSynchronizer,是构建锁或者其他同步组件的基础框架,他使用int成员变量表示同步状态(这个同步状态在不同的同步组件中表示的含义会有差异),通过内置的FIFO队列完成线程获取资源的排队工作。

同步器使用的主要方式是通过继承并实现它定义的抽象方法来管理同步状态。队列同步器提供了操作同步状态的方法,可以保证状态的修改是线程安全的,同步器支持独占的获取同步状态,也支持共享式的获取。

同步器是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。锁是面向使用这的,他定义了使用者与锁交互的接口,隐藏了实现细节;同步器面向的是锁的实现者,他简化了锁的实现方式,屏蔽了同步状态管理、线程排队,等待唤醒等底层操作。

AbstractQueuedSynchronizer的定义:

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable

//同步器提供的修改活访问状态的方法

//获取当前同步状态
protected final int getState() {
    return state;
}
//设置当前同步状态
protected final void setState(int newState) {
    state = newState;
}
//使用CAS设置当前状态,可以保证设置状态的原子性
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

同步器的设计是基于模版方法模式的,使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定的同步组件实现中,并调用同步器提供的模版方法,而这些模版方法将会调用使用者重写的方法

ReentrantLock

ReentrantLock是Java中提供的另一种锁的实现.他通过调用lock()方法获取锁;调用unlock()方法释放锁。
ReentrantLock的实现依赖于Java同步框架AbstractQueuedSynchronizer,AQS使用一个整形的volatile变量(命名为state)来维护同步状态,volatile变量是ReentrantLock内存语义实现的关键。

ReentrantLock分为公平锁和非公平锁。

公平锁:每个线程抢占锁的顺序为先后调用lock方法的顺序依次获取锁

每个线程抢占锁的顺序不定,谁运气好,谁就获取到锁,和调用lock方法的先后顺序无关。

ReentrantLock的类图如下:

ReentrantLock实现了Lock接口,内部有三个内部类,Sync、NonfairSync、FairSync,Sync是一个抽象类型,它继承AbstractQueuedSynchronizer,这个AbstractQueuedSynchronizer是一个模板类,它实现了许多和锁相关的功能,并提供了钩子方法供用户实现,比如tryAcquire,tryRelease等。Sync实现了AbstractQueuedSynchronizer的tryRelease方法。NonfairSync和FairSync两个类继承自Sync,实现了lock方法,然后分别公平抢占和非公平抢占针对tryAcquire有不同的实现。

首先分析公平锁:
ReentrantLock lock = new ReentrantLock(true);公平锁声明,如果不为true,或者使用默认,那么是非公平锁。
加锁lock()方法的调用轨迹如下:

ReentrantLock.lock()

sync.lock() [ReentrantLock.FairSync.lock()]

AbstractQueuedSynchronizer.acquire(int arg) [arg = 1]

ReentrantLock.FairSync.tryAcquire(int acquires);

第4步是加锁的关键步骤:

protected final boolean tryAcquire(int acquires) {
    //获取当前尝试获取锁的线程
    final Thread current = Thread.currentThread();
    //获取AQS中的volatile变量state
    int c = getState();
    if (c == 0) {
        //判断队列之前是否有其他的线程在等待
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            //设置锁的持有者为当前线程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //如果当前线程已经获取了锁,那么进行锁重入
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
CAS + volatile 构成了AQS以及原子变量类。
JDK文档对CAS的说明:如果当前状态值与预期值相等,则以原子的方式讲同步状态设置为给定的更新值,此操作具有volatile读和写的内存语义。这意味着编译器不能对CAS与CAS前面和后面的任意内存操作重排序。CAS操作对应的本地方法最终对应的处理器源代码回有一个Atomic::cmpxchg指令。程序回根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果是多处理器上运行,那么添加Lock前缀,如果是单处理器那么就会省略。lock前缀会确保内存的读写改操作原子执行。(但处理器自身会维护)
【补充volatile的内存语义:1、在程序中,当第一个操作为普通变量的读或写时,如果第二个操作为volatile写,则编译器不能重拍下这两个操作 2、当第二个操作为volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后  3、当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序,这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前  4、当第一个操作是volatile写,第二个操作是volatile读时,不能重排序】
读写锁

ReentrantLock是支持可重入的排他锁,这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有和读线程和其他的写线程均被阻塞。读写锁维护了两个锁,一个读锁,一个写锁,通过读写锁,可以提高兵法性能。

Java中提供的读写锁的实现是ReentrantReadWriteLock,提供的特性如下:

公平性选择,支持公平和非公平的锁获取方式

重进入

锁降级 遵循获取写锁,获取读锁再释放写锁的次序,写锁能够降级成读锁。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74594.html

相关文章

  • Java的锁

    摘要:中的锁锁像同步块一样,是一种线程同步机制,但比中的同步块更复杂。这意味着如果一个线程进入了代码中的同步块,并因此获得了该同步块使用的同步对象对应的管程上的锁,那么这个线程可以进入由同一个管程对象所同步的另一个代码块。 Java中的锁 showImg(http://segmentfault.com/img/bVbOvK); 锁像synchronized同步块一样,是一种线程同步机制,...

    paulquei 评论0 收藏0
  • java并发编程学习1--基础知识

    摘要:死亡状态线程退出有可能是正常执行完成也有可能遇见异常退出。类有新建与死亡状态返回其余状态返回判断线程是否存活。线程因某些原因进入阻塞状态。执行同步代码块的过程中执行了当前线程放弃开始睡眠进入就绪状态但是不会释放锁。 【java内存模型简介 JVM中存在一个主存区(Main Memory或Java Heap Memory),Java中所有变量都是存在主存中的,对于所有线程进行共享,而每个...

    huangjinnan 评论0 收藏0
  • Java的锁之乐观锁与悲观锁

    摘要:解决上问题在变量前添加版本号,将变成循环时间长开销大,因为自旋需要消耗只能保证一个共享变量的原子操作分类二重入锁支持重进入的锁,排它锁分类三读写锁一对锁,读锁,写锁,在同一时刻允许多线程访问 1、 分类一:乐观锁与悲观锁   a)悲观锁:认为其他线程会干扰本身线程操作,所以加锁 i.具体表现形式:synchronized关键字和lock实现类 ...

    huangjinnan 评论0 收藏0
  • 浅谈Java并发编程系列(七) —— 深入解析synchronized关键字

    摘要:第一个字被称为。经量级锁的加锁过程当一个对象被锁定时,被复制到当前尝试获取锁的线程的线程栈的锁记录空间被复制的官方称为。根据锁对象目前是否处于被锁定状态,撤销偏向后恢复到未锁定或经量级锁定状态。 Synchronized关键字 synchronized的锁机制的主要优势是Java语言内置的锁机制,因此,JVM可以自由的优化而不影响已存在的代码。 任何对象都拥有对象头这一数据结构来支持锁...

    piglei 评论0 收藏0
  • Java并发总结

    摘要:限期阻塞调用方法等待时间结束或线程执行完毕。终止状态线程执行完毕或出现异常退了。和都会检查线程何时中断,并且在发现中断时提前放回。工厂方法将线程池的最大大小设置为,而将基本大小设置为,并将超时大小设置为分钟。 wait()、notify()、notifyAll() Object是所有类的基类,它有5个方法组成了等待、通知机制的核心:notify()、notifyAll()、wait()...

    szysky 评论0 收藏0

发表评论

0条评论

gaara

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<