Java 锁

作者: 小马蛋 | 来源:发表于2019-10-31 15:47 被阅读0次

一、Java中的锁

Java中有两种加锁的方式:一种是用synchronized关键字,另一种是用Lock接口的实现类。

二、synchronized与Lock接口实现类的区别:

1)synchronized 是java关键字,是虚拟机层面的,当代码块执行完毕或发生异常时,虚拟机会自动释放锁。

2)Lock实现类 是代码层面的,需要我们在代码执行完毕或发生异常时,手动释放锁。

3)Lock更灵活,更能定制用户需求,比如可中断性,在使用synchronized锁的前提下,一个线程持有锁,在代码块执行期间,其它线程也要执行此代码块,但是锁被另一个线程率先持有了,那么这些线程只能等待,如果想让线程等待一定时间,或者直接中断,synchronized是无法做到,而Lock可以帮助我们实现自定义的一些需求,诸如上面提到的能够知道锁已被其它线程持有、等待10秒如果没有获得就退出锁竞争等等。就好比一个是成品,一个是半成品。成品拿来直接吃,半成品可以加一些自己喜欢的佐料,然后再吃。

4)在使用Lock锁时,如果忘记释放锁,会导致死锁的情况发生。而synchronized不会发生,除非代码块进入死循环一直无法释放持有的锁。

三、性能

在锁竞争不是很激烈的场景下,二者差距不大。而当锁竞争激烈的时候,Lock的性能稍高于synchronized,根据不同场景,自行选定。

此次要展开讲的是Lock相关实现类。

四、锁的分类

1)公平锁、非公平锁 - 获取锁的公平性

公平锁是指按照线程申请持有锁的顺序进行锁的颁发,类似队列的概念,先到先得,后到后得。

非公平锁,是指随机分配锁,无规律可循,synchronized是非公平锁。

2)可重入锁、不可重入锁

可重入锁,是指持有锁的线程,再次尝试获取锁时,会自动获取而无需等待。ReentrantLock也是可重入锁。

不可重入锁,无法区分敌我,一概等待,容易造成死锁。

重入意味着获取锁的操作的粒度是线程而不是调用

3)互斥锁、读写锁 - 功能性划分

互斥锁(排他锁):锁只能颁发给一个线程持有,ReentrantLock是独享锁。

读写锁:在读-读的情况下,读锁可以颁发给多个线程进行读操作,但是写锁是互斥锁,写-写、读-写的过程是互斥的,ReadWriteLock是读写锁。

4)悲观锁、乐观锁 - 心态

悲观锁、乐观锁应该是一种锁的理念,而不是具体实现,各编程语言乃至数据库都有相关实现。

悲观锁,悲观的认为,对同一数据的并发操作都是要进行修改的,我们可以理解为小心谨慎。

乐观锁,乐观的认为,对同一数据的并发操作不一定要进行修改,只是在进行修改的时候进行数据检测(CAS操作,compareAndSwap),是否发生修改,没有发生修改,正常执行,如发现已修改,返回给用户,让用户抉择下一步该如何行事,我们可以理解为,心大格局大,不怕背锅。

5)自旋锁 - 获取不到锁时的处理方式

线程A持有锁进行逻辑处理,此时线程B过来,想要获取锁,但是发现锁被其它线程持有了,通常情况下,线程B进入等待状态,等待锁的释放,而自旋锁不会立即挂起,自旋锁会执行一个空循环,当持有锁的线程触发了释放锁的条件,自旋锁会得到通知,跳出循环体,尝试获取锁。
这样做的好处就是减少了线程上下文切换带来的消耗。缺点也很明显,占用cpu,尤其是锁竞争激烈的时候。
自Java7之后,自旋锁总是会被执行,自旋次数由jvm自动调整。

五、锁的其它概念

1)锁消除

虚拟机即时编译时,对运行上下文进行扫描,去除不可能共享资源的竞争锁,通过锁消除,可以减掉申请锁的步骤和时间,提高效率。

    public String getName() {
        StringBuffer sb = new StringBuffer();
        sb.append("a");
        sb.append("b");
        sb.append("c");
        sb.append("d");
        return sb.toString();
    }

StringBuffer.append()源码:

    @Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

很简单的代码,局部变量sb是安全的,所以无须加锁,加锁反而会浪费资源,降低性能。

2)锁粗化

把多次锁的操作合并为一个操作,降低单位时间内大量锁请求、同步、释放带来的性能损耗。

    public void do() {
        synchronized(key) {
            // your code
        }
    
        // your code
    
        synchronized(key) {
            // your code
        }
    }

粗化后:

    public void do() {
        synchronized(key) {
            // your code
            // your code
            // your code
        }
    }

3)synchronized锁升级

MarkWord.jpeg 从网上扒的,侵删!

锁升级:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

前提:有线程A、B,A先访问同步块,在A进入同步块时,线程B执行到同步块。

    public void do() {
        synchronized(key) {
            // do something
        }
    }

① 线程A访问同步块,此时没有其它线程持有该同步块的锁,那么,线程A持有锁并进入同步块,此时锁为偏向锁。
具体分析看这里

② 线程B访问同步块,线程A正在同步块中处理,锁还没有释放,线程B尝试获取锁,但是发现,锁被线程A持有,这时候就发生了锁竞争,此时偏向锁自动升级为轻量级锁,线程B开始自旋以等待锁被释放,自旋是有次数限制的,自Java7之后,jvm自动进行自旋次数的调整。

③ 当锁竞争激烈或同步块处理时间较长,自旋次数达到限制,此时锁就升级为重量级锁,线程B挂起,等待锁的释放再行竞争,如果此时线程C再来获取锁时,线程C直接挂起。

synchronized锁不会降级,只允许升级,偏向锁、轻量级锁、重量级锁也就是synchronized内置锁的三种状态。

六、Lock接口的实现类

1)ReentrantLock可重入锁

可重入锁的原理是在锁内部维护了一个线程标识,标识该锁目前被那个线程占用,然后关联一个计数器,该锁没有被任何线程占用时计数器为0,当一个线程持有了该锁,计数器+1,其他线程在获取该锁时候发现锁的所有者不是自己所以被阻塞,
但是当获取该锁的线程再次获取锁时候发现锁拥有者是自己会把计数器值+1, 当释放锁后计数器会-1,当计数器为0时候,锁里面的线程标识重置为null,这时候阻塞的线程会获取被唤醒来获取该锁。

ReentrantLock 拥有与synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。
此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)

2)ReentrantReadWriteLock读写锁

正常情况下对同一数据的并发并不都是修改操作,存在读操作和写操作,基于这个前提ReentrantReadWriteLock能提供比排他锁更好的性能优势和吞吐。
读写锁内部维护了两个静态内部类ReadLock和WriteLock,一个读锁,一个写锁。

ReentrantReadWriteLock支持以下功能:
1)支持公平和非公平的获取锁的方式;

2)支持可重入。读线程在获取了读锁后还可以获取读锁;写线程在获取了写锁之后既可以再次获取写锁又可以获取读锁;

3)还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不允许的;

4)读取锁和写入锁都支持锁获取期间的中断;

5)Condition支持。仅写入锁提供了一个 Conditon 实现;读取锁不支持 Conditon ,readLock().newCondition() 会抛出 UnsupportedOperationException。

3)StampedLock读写锁

Java8中新增加的读写锁,是对ReentrantReadWriteLock读写锁的改机版。改进思想就是,读(乐观读)写之间不会阻塞对方,写写之间依然保持阻塞。

举个例子:

1)ReentrantReadWriteLock背景,线程A首先获取读锁,然后线程B获取写锁,线程B等待A读完之后,再进行写操作,茫然一顾,似乎没有问题。

2)StampedLock读锁(乐观读)被线程A首先持有后,此时再有线程B获取写锁,持有写锁的线程B并不会等待A线程读取完数据,而是通知A线程重读或者抛出异常。
为什么会这样设计?在读线程比写线程多的情况下,写线程极易被大量的读线程阻塞,也就是饥饿现象,StampedLock,读写之间不是阻塞的,写线程会造成读线程的读取失败,然后重读!

StampedLock的特点:

1)读写之间不会阻塞对方,写写之间依然保持阻塞。

2)StampedLock是不可重入锁

3)StampedLock的悲观读锁,写锁不支持条件变量。

如果某个线程阻塞在读锁或者写锁上,如果此时调用了这个线程的interrupt方法,会导致这个线程所在的cpu100%,所以处理这类情况,最好一定不要调用中断操作,如果需要支持中断,一定使用可中断的悲观读锁的readLock Interruptibly() 和写锁 writeLock Interruptibly()。

七、源码解读

1)Lock

Lock是顶层接口,制订了锁的行为特性,具体细节由实现类自由发挥。

public interface Lock {
    /**
     * 获取锁
     * 如果锁不可用,则当前线程将被禁用以进行线程调度,并处于等待状态,直到获取锁为止
     * lock方法实现可能够检测到锁的错误使用,例如可能导致死锁的调用,并且在这种情况下可能抛出(未检查的)异常。环境和异常类型必须由相关实现记录。
     */
    void lock();

    /**
     * 请求锁,除非当前线程被中断。
     *
     * 如果没有其他线程持有锁,则当前线程获取到锁,并为锁计数加1,并且立即返回。
     * 如果当前线程已经持有锁,则为锁计数加1,并立即返回。
     * 如果其他线程持有锁,则当前线程将处于不可用状态以达到线程调度目的,等待直到下面两个事件中的一个发生:
     * 1)当前线程获取到锁
     * 2)其他线程中断当前线程
     *
     * 如果当前线程获取到锁,则将锁计数设置为1。
     * 如果当前线程在方法条目上设置了中断状态或者在请求锁的时候被中断,将抛出中断异常。
     *
     * 等锁
     */
    void lockInterruptibly() throws InterruptedException;

    /**
     * 与lock和lockInterruptible不同的是,tryLock()方法会立即返回结果,如果锁可用并且当前线程能够成功获取的话,直接返回true,否则返回false。
     *
     * 无需等锁,有无都返回结果
     */
    boolean tryLock();

    /**
     * 在给定的等待时间内并且线程没有被中断以及锁可用的情况下,去获取锁。
     * 如果锁可用,方法会直接返回。
     * 如果锁不可用,则当前线程将会处于不可用状态以达到线程调度目的,等待,直到下面三个事件中的一个发生:
     * 1)当前线程获取到锁
     * 2)其他线程中断当前线程
     * 3)指定的等待时间已过
     * 假如当前线程:
     * 在该方法的条目上设置其中断状态或在获取锁时中断,并且支持锁获取中断时,将抛出中断异常,当前线程中断状态会被清除。
     * 假如给定的等待时间已过,将会返回false。
     *
     * 等待一定时间,知道被其他线程中断或者超时
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    /**
     * 释放锁
     * 用完就要释放,否则造成死锁,其实现类是否有超时设置?
     */
    void unlock();

    /**
     * Condition与锁是通过lock.newCondition()方法产生一个与当前锁绑定的Condtion实例。
     * 我们通知该实例来控制线程的等待与通知。
     */
    Condition newCondition();
}

2)ReentrantLock

ReentrantLock可重入锁,支持公平锁和非公平锁,其内部维护了两个内部类,公平锁同步对象和非公平锁同步对象,分别继承其内部静态类Sync,公平锁和非公平锁的最大的区别在于,在获取锁时,公平锁会查找等待时间最长的线程,将该线程唤醒,尝试获取锁。

我觉得在这里有必要提一嘴,在《Java并发编程实战》这本书的13.3这一章节里,有提到:

在激烈竞争的情况下,非公平锁的性能高于公平锁的性能的一个原因是:在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟,假设线程A持有一个锁,并且线程B请求这个锁。由于这个锁已被线程A持有,因此B将被挂起。当A释放锁时,B将被唤醒,因此会再次尝试获取锁。与此同时,如果C也请求这个锁,那么C很可能会在B被完全唤醒之前获得、使用以及释放这个锁。这样的情况是一种“双赢”的局面,B获得锁的时刻并没有推迟,C更早地获得了锁,并且吞吐量也获得了提高。

当持有锁的时间相对较长,或者请求锁的平均时间间隔较长,那么应该使用公平锁,在这些情况下,“插队”带来的吞吐量提升(当锁处于可用状态时,线程却还处于被唤醒的过程中)则可能不会出现。

与默认的ReentrantLock一样,内置加锁并不会提供确定的公平性保证,但在大多数情况下,在锁实现上实现统计上的公平性保证已经足够了。Java语言规范并没有要求JVM以公平的方式来实现内置锁,而在各种JVM中也没有这样做。ReentrantLock并没有进一步降低锁的公平性,只是使一些已经存在的内容更明显。

AQS内部已经维护了一个FIFO的队列,为什么还要加上上述判断,那是因为队列内的排好序了,但是你无法防止新生线程继续获取锁!

/**
 *
 * 实现Lock接口的可重入锁
 * @since 1.5
 */
public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    /**
     * 提供所有实现机制的同步器
     */
    private final Sync sync;

    /**
     * 抽象类
     * 此锁的同步控制基础。下面的两个子类是公平和非公平的版本。使用AQS状态表示锁上的保持数。
     * 抽象队列同步器AbstractQueuedSynchronizer (以下都简称AQS),是用来构建锁或者其他同步组件的基础框架,
     * 它使用了一个int成员变量来表示同步状态,通过内置的FIFO(first-in-first-out)同步队列来控制获取共享资源的线程。
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * 尝试获取锁
         * 交给下面的两个子类去实现
         * 因为ReentrantLock有公平锁和非公平锁两个版本,所以在获取锁逻辑会有差别
         */
        abstract void lock();

        /**
         * 尝试获取锁,直接返回是否持有锁的标识
         */
        final boolean nonfairTryAcquire(int acquires) {
            // 当前线程
            final Thread current = Thread.currentThread();
            // 获取同步状态
            int c = getState();
            // c == 0 表示没有线程持有锁
            if (c == 0) {
                // CAS 抢占锁,如果成功返回false 否则跳出分支语句返回false
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 如果当前线程是持有锁的线程,可以继续持有,state+1
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                // fast-fail检查
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

        /**
         * 尝试释放锁
         * AQS release方法调用子类的tryRelease
         */
        protected final boolean tryRelease(int releases) {
            // 当前状态原子量 减 释放值
            int c = getState() - releases;
            // 如果线程不是锁的持有者,抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // 没持有多层锁则将持有锁的线程设置为null 表示没有线程持有该锁
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

        // 当前线程是否为锁的持有者
        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        // 获取通信对象
        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        // 获取持有锁的线程
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        // 获取锁state
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        // 锁是否被持有
        final boolean isLocked() {
            return getState() != 0;
        }

        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0);
        }
    }

    /**
     * 非公平锁的同步对象
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * 获取锁,尝试立即获取锁
         * 比较并设置状态
         *  如果成功,设置线程独占
         *  如果失败,判断是否和持有锁的线程是同一个线程,是,则持有锁state+1,进入同步代码块,否则进入等待队列
         */
        final void lock() {
            // CAS 如果返回true说明获取成功
            if (compareAndSetState(0, 1))
                // 设置拥有独占访问权的线程对象
                setExclusiveOwnerThread(Thread.currentThread());
            /*
             否则,调用AQS的尝试获取锁方法。
             AQS尝试获取锁方法是子类必须要重写的方法,所以实际调用的是NonfairSync的tryAcquire方法
             可重入锁逻辑:判断持有锁的线程和当前线程是否同一个,true则继续持有,false进入等待队列
             */
            else
                acquire(1);
        }

        /**
         *
         */
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

    /**
     * 公平锁对象
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        /*
         公平可重入锁,直接调用AQS的acquire方法。
         依然是可重入锁逻辑。
         公平锁为了保证先到先得原则,会判断是否存在比当前线程等待时间更长的线程,如果是,此线程需要进入等待队列,而不是像非公平锁那样直接进行CAS操作。
         */
        final void lock() {
            acquire(1);
        }

        /*
        AQS acquire方法调用,如果当前state为0,会判断是否存在更早的等待线程
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            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;
        }
    }

    /**
     * 创建一个非公平锁
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * 通过构造参数,创建非公平锁或公平锁
     * fair - true 创建公平锁
     * fair - false 创建非公平锁
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

    /**
     * 获取锁,如果锁被其它线程持有,进入等待队列,一直等待
     */
    public void lock() {
        sync.lock();
    }

    /**
     * 获取锁,如果锁被其它线程持有,进入等待队列,要么获取到锁要被被其它线程通知中断等待并抛出InterruptedException
     */
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    /**
     * 尝试获取锁,直接返回是否持有锁的标识
     */
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

    /**
     * 尝试获取锁,如果锁被其它线程持有,进入等待队列,
     * 1)等待超时返回false
     * 2)等待中被其它线程通知中断等待并抛出InterruptedException
     * 3)获取到锁返回true
     */
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

    /**
     * 释放锁
     */
    public void unlock() {
        sync.release(1);
    }

    /**
     * 再行研究
     */
    public Condition newCondition() {
        return sync.newCondition();
    }

    /**
     * 获取持有此锁及等待此锁的线程数(持有数)
     */
    public int getHoldCount() {
        return sync.getHoldCount();
    }

    /**
     * 当前线程是否持有此锁
     * 此方法通常用于调试和测试例如
     * class X {
     *   ReentrantLock lock = new ReentrantLock();
     *   // ...
     *
     *   public void m() {
     *       assert lock.isHeldByCurrentThread();
     *       // ... method body
     *   }
     *   ========================================
     * class X {
     *   ReentrantLock lock = new ReentrantLock();
     *   // ...
     *
     *   public void m() {
     *       assert !lock.isHeldByCurrentThread();
     *       lock.lock();
     *       try {
     *           // ... method body
     *       } finally {
     *           lock.unlock();
     *       }
     *   }
     * }}
     */
    public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }

    /**
     * 查询此锁是否有线程持有,方法本身设计就是用于监视锁状态,不用于同步控制。
     * 核心是利用volatile修饰变量,使每个线程持有的变量都是最新的,无法保证原子性。
     */
    public boolean isLocked() {
        return sync.isLocked();
    }

    /**
     * 是否公平锁
     */
    public final boolean isFair() {
        return sync instanceof FairSync;
    }

    /**
     * 返回当前拥有此锁的线程
     * 如果没有线程持有此锁,则返回null
     * 由于等待队列里的线程状态随时都在发生变化,所以返回解雇也只是一个快照的结果
     */
    protected Thread getOwner() {
        return sync.getOwner();
    }

    /**
     * 查询是否有线程正在等待获取此锁。
     * 由于取消可能随时发生,所以返回的结果也只是一个快照的结果。
     */
    public final boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

    /**
     * 查询指定线程是否正在等待获取此锁。
     * 由于取消可能随时发生,因此返回不保证此线程将获得此锁。
     */
    public final boolean hasQueuedThread(Thread thread) {
        return sync.isQueued(thread);
    }

    /**
     * 返回等待锁的线程数,因为在遍历时随时可能各种状态变更所以,此数值也是一个估计值,
     * 此方法设计用于监视锁状态,而不是同步控制。
     */
    public final int getQueueLength() {
        return sync.getQueueLength();
    }

    /**
     * 返回正在等待的线程的集合
     * 因为在遍历时随时可能各种状态变更所以,所以此集合也是一个快照集合,
     * 此方法设计用于监视锁状态,而不是同步控制。
     */
    protected Collection<Thread> getQueuedThreads() {
        return sync.getQueuedThreads();
    }

    /**
     * 查询是否有任何线程正在等待与此锁关联的给定条件。
     * 由于超时和中断可能随时发生,不能保证值的正确性。
     * 此方法本身设计就是用于监视锁状态,而不是用于同步控制。
     */
    public boolean hasWaiters(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
    }

    /**
     * 返回与此锁关联的给定条件上等待的线程数的估计值。由于超时和中断可能随时发生,所以这是估计值。
     * 此方法本身设计就是用于监视锁状态,而不是用于同步控制。
     */
    public int getWaitQueueLength(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
    }

    /**
     * 返回包含以下状态的线程集合
     * 正在等待与此锁关联的给定条件。
     * 返回的数据并不一定很精确,只是做一个监测使用而已
     */
    protected Collection<Thread> getWaitingThreads(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
    }

    /**
     * Returns a string identifying this lock, as well as its lock state.
     * The state, in brackets, includes either the String {@code "Unlocked"}
     * or the String {@code "Locked by"} followed by the
     * {@linkplain Thread#getName name} of the owning thread.
     *
     * @return a string identifying this lock, as well as its lock state
     */
    public String toString() {
        Thread o = sync.getOwner();
        return super.toString() + ((o == null) ?
                                   "[Unlocked]" :
                                   "[Locked by thread " + o.getName() + "]");
    }
}

下面是UML


uml.png

八、synchronizedReentrantLock进行选择

ReentrantLock在加锁和内存上提供的语义与内置锁相同,此外它还提供了一些其他功能,包括定时的锁等待、可中断的锁、公平性,以及实现非块结构的加锁。

与显示锁先比,内置锁synchronized仍然具有很大的优势。内置锁为许多开发人员所熟悉,并且简洁紧凑。并且在java6之后,其性能其实与显示锁已经不相上下,如果当内置锁无法满足需求时,才可以考虑ReentrantLock

九、锁的意义

加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。

十、拓展

ReentrantLock支持的中断,是什么意思呢?当等待获取锁的线程,被其它线程通知中断,这么做的意义是什么?什么场景可以用到?线程中断的机制是什么?

相关文章

网友评论

      本文标题:Java 锁

      本文链接:https://www.haomeiwen.com/subject/mustbctx.html