JAVA面试考点——java锁机制(synchronize原理、无锁编程、CAS、AQS)

目录

 1. 什么是锁

 2. java锁机制

3. synchronize原理

4. 锁的四种状态, synchronized中的锁如何变化

偏向锁

轻量级锁

自旋锁(补充说明)

重量级锁

5、无锁编程

互斥锁

CAS compare and swap

乐观锁 Optimistic Concurrency Control

java中如何利用CAS特性进行无锁编程

AQS(AbstractQueuedSynchronizer.class)

AQS成员变量

AQS独占模式

源码分析

AQS共享模式

补充

JAVA中断


 1. 什么是锁

在并发环境下。多个线程会对同一个资源进行争抢,可能会导致数据不一致问题。因此可以使用锁机制,通过一种抽象的锁来对资源进行锁定。

 2. java锁机制

java中,每个对象有一把锁,这把锁存放在对象头中。

java对象包含了三个部分:

对象头(存放对象运行时信息)、实例数据、对齐填充字节(为满足java对象的大小必须是8比特的倍数这一条件而设计的)

 对象头中包含两部分:mark work(32bit)

3. synchronize原理

 synchronized编译后生成monitor enter和monitor exit两个字节码指令进行线程同步。

使用javac和javap对java代码进行编译和反编译

 

 这样就可以看到可读性较高的字节码,如下:

 这里的monitor常被理解为监视器或者管程

synchronized可能存在性能问题,因为synchronized编译后生成monitor enter和monitor exit两个字节码指令,monitor依赖于操作系统的mutex lock来实现的

java线程实际上是对操作系统线程的映射,所以每次挂起或者唤醒一个线程,都要切换操作系统的内核态,这种操作是比较重量级的,在一些情况下,甚至切换时间超出了任务的执行时间

这样的话,使用 synchronized会对性能产生严重影响。

java6开始,synchronized 进行了优化,引入了偏向锁、轻量级锁。

因此,锁有四种状态(锁只能升级,不能降级):

无锁、偏向锁、轻量级锁、重量级锁

4. 锁的四种状态, synchronized中的锁如何变化

偏向锁

顾名思义,就是让对象认识线程,只为一个线程提供数据。

 

 如果对象发现有多个线程在竞争数据,那么锁会升级为轻量级锁。

轻量级锁

一旦自旋等待的线程数超过一个,那么轻量级锁将会升级为重量级锁。

自旋锁(补充说明)

可以理解为一种轮询。线程在不断循环验证目标对象的锁是否被释放,如果释放则获取锁,否则则进行下一轮循环。

这种方式区别于被操作系统挂起阻塞,因为如果对象的锁很快就会被释放的话,自选就不需要进行系统中断和现场恢复,所以他的效率更高。

自旋相当于cpu空转,如果长时间自旋将会浪费cpu资源,于是出现了“适应性自旋”的优化,即自旋时间不再固定,而是由上一次在同一个锁上的自旋时间以及锁状态,这两个条件来决定自旋时间。

重量级锁

 此时需要使用monitor来对资源进行控制。

5、无锁编程

用过AQS吗,有具体的例子可以说说吗?

了解CAS吗,谈一谈你对CAS的理解吧?

看过JUC的源码吗?聊一聊具体的实现吧?

假设现在有多个线程想要操作同一个资源对象,很多人的第一反应就是使用互斥锁

互斥锁

互斥锁的同步方式是悲观的,即操作系统认为,如果不严格控制线程调用,就一定会产生异常。因此互斥锁将会将资源锁定,只供一个线程调用

但是互斥锁并不是万能的,比如一些情况下,大部分都是读操作,此时没有必要在每次读取时都锁定资源。

或者一些情况下,同步代码块执行的耗时远远小于线程切换的耗时,此时也不适合使用互斥锁。

CAS compare and swap

当资源可用时,有两个线程认为资源当前状态是可用时,他们各会产生两个值:

  • old value代表之前读到的资源对象的状态值
  • new value 代表想要资源对象的状态值

 如下图所示,资源的状态为 0 ,此时两个线程A和B都希望把资源的状态改为1,然后占用该资源。

假设A线程率先获得了时间片,他将old value与资源的状态进行compare,发现一致,于是将资源的状态值设置为new value;

而B线程落后了一步,此时资源的状态值已被修改,此时B线程在compare的时候发现与自己预期的old value不一致,所以放弃swap操作。

 在现实生活中,我们通常会让B线程进行自旋等待,自旋就是使其不断的重试CAS操作,通常会配置自旋次数来方式死循环。

 疑问:

1、CAS分为compare和swap两步操作,多线程下难以保证一致性?

CAS必须是原子性的,即比较old value和更新new value这两步必须在同时只能由一条线程进行操作。

2、如何实现CAS的原子性?

各种不同架构的CPU都提供了指令级别的CAS原子操作

乐观锁 Optimistic Concurrency Control

通过CAS来实现同步的工具,由于不会锁定资源,并且总是乐观的认为资源没有被修改过,并且每次都会自己主动尝试去compare状态值,这种同步机制被称为乐观锁。

乐观锁使用了无锁的同步机制,实际上没有用到锁

java中如何利用CAS特性进行无锁编程

AtomicInteger类底层使用CAS实现同步计数器。

下面代码中使用AtomicInteger实现三个线程累加到1000,而不产生线程安全问题。

public class Main {
    
    static AtomicInteger num = new AtomicInteger(0);

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (num.get() < 1000) {
                        System.out.println("thread name:" + 
                               Thread.currentThread().getName() + ":" + 
                               num.incrementAndGet());
                    }
                }

            });
            t.start();
        }
    }
}

我们进入源码来看下AtomicInteger 是如何实现无锁同步的。

    /**
     * Atomically increments the current value,
     * with memory effects as specified by {@link VarHandle#getAndAdd}.
     *
     * <p>Equivalent to {@code addAndGet(1)}.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return U.getAndAddInt(this, VALUE, 1) + 1;
    }

    /**
     * Atomically adds the given value to the current value of a field
     * or array element within the given object {@code o}
     * at the given {@code offset}.
     *
     * @param o object/array to update the field/element in
     * @param offset field/element offset
     * @param delta the value to add
     * @return the previous value
     * @since 1.8
     */
    @HotSpotIntrinsicCandidate
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
        return v;
    }

    @HotSpotIntrinsicCandidate
    public final boolean weakCompareAndSetInt(Object o, long offset,
                                              int expected,
                                              int x) {
        return compareAndSetInt(o, offset, expected, x);
    }

    /**
     * Atomically updates Java variable to {@code x} if it is currently
     * holding {@code expected}.
     *
     * <p>This operation has memory semantics of a {@code volatile} read
     * and write.  Corresponds to C11 atomic_compare_exchange_strong.
     *
     * @return {@code true} if successful
     */
    @HotSpotIntrinsicCandidate
    public final native boolean compareAndSetInt(Object o, long offset,
                                                 int expected,
                                                 int x);

CAS的具体实现是在Unsafe.java中。Unsafe.java主要用于执行一些底层的,与平台相关的方法。

上面代码中 compareAndSetInt 方法使用了native修饰符,说明这个方法是一个本地方法,与具体的平台实现相关。

自旋的次数可以通过启动参数进行配置,默认值为10。

AQS(AbstractQueuedSynchronizer.class)

多线程中竞争的资源以对象的形式进行封装,而CAS只能原始的修改内存上的一个值。该如何利用CAS去同步对象,就需要进一步的抽象。

JAVA是如何利用CAS进行对象同步的呢?

此时可以先思考一下:如何设计一个同步管理框架

1. 通用性,下层实现透明的同步机制,同时与上层业务解耦

2. 利用CAS,原子地修改共享标记位

3. 等待队列

AQS成员变量

这里的状态字段state没有设置成boolean类型,是因为线程或是锁的两种模式为:独占和共享

  • 独占模式: 一旦被占用,其他线程都不能占用。
  • 共享模式: 一旦被占用, 其他共享模式下的线程能占用。

所以state表示的是线程占用的数量,因此使用了int。

 


    /**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     */
    private transient volatile Node head;

    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
    private transient volatile Node tail;

    /**
     * The synchronization state.
     * 这里的状态没有设置成boolean,是因为线程或是锁的两种模式为:独占和共享
     * 独占模式: 一旦被占用,其他线程都不能占用。
     * 共享模式: 一旦被占用, 其他共享模式下的线程能占用。
     * 所以state表示的是线程占用的数量,因此使用了int
     */
    private volatile int state;

当有线程没有获取到资源时,有可能会选择排队,这里使用了链表对等待的线程进行排队,队列使用FIFO的思想。数据类型为Node。上面的head和tail属性表示队列的头和尾。

  队列中的节点有两种模式:独占和共享

AQS独占模式

 int state > 0 代表锁被占用,只能被单个节点占用

              = 0  代表锁被释放

独占模式下,锁只能被一个线程获取,其他线程必须等待。

源码分析

对于Node对象,他主要存储了如下信息:

  • 1. 线程对象
  • 2. 节点在队列里的等待状态(waitStatus
  • 3. 前后指针(prev、next)等信息

源码如下所示:

    static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled. */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking. */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition. */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate.
         */
        static final int PROPAGATE = -3;

        /**
         * Status field, taking on only the values:
         *   SIGNAL:     The successor of this node is (or will soon be)
         *               blocked (via park), so the current node must
         *               unpark its successor when it releases or
         *               cancels. To avoid races, acquire methods must
         *               first indicate they need a signal,
         *               then retry the atomic acquire, and then,
         *               on failure, block.
         *   CANCELLED:  This node is cancelled due to timeout or interrupt.
         *               Nodes never leave this state. In particular,
         *               a thread with cancelled node never again blocks.
         *   CONDITION:  This node is currently on a condition queue.
         *               It will not be used as a sync queue node
         *               until transferred, at which time the status
         *               will be set to 0. (Use of this value here has
         *               nothing to do with the other uses of the
         *               field, but simplifies mechanics.)
         *   PROPAGATE:  A releaseShared should be propagated to other
         *               nodes. This is set (for head node only) in
         *               doReleaseShared to ensure propagation
         *               continues, even if other operations have
         *               since intervened.
         *   0:          None of the above
         *
         * The values are arranged numerically to simplify use.
         * Non-negative values mean that a node doesn't need to
         * signal. So, most code doesn't need to check for particular
         * values, just for sign.
         *
         * The field is initialized to 0 for normal sync nodes, and
         * CONDITION for condition nodes.  It is modified using CAS
         * (or when possible, unconditional volatile writes).
         */
        volatile int waitStatus;

        /**
         * Link to predecessor node that current node/thread relies on
         * for checking waitStatus. Assigned during enqueuing, and nulled
         * out (for sake of GC) only upon dequeuing.  Also, upon
         * cancellation of a predecessor, we short-circuit while
         * finding a non-cancelled one, which will always exist
         * because the head node is never cancelled: A node becomes
         * head only as a result of successful acquire. A
         * cancelled thread never succeeds in acquiring, and a thread only
         * cancels itself, not any other node.
         */
        volatile Node prev;

        /**
         * Link to the successor node that the current node/thread
         * unparks upon release. Assigned during enqueuing, adjusted
         * when bypassing cancelled predecessors, and nulled out (for
         * sake of GC) when dequeued.  The enq operation does not
         * assign next field of a predecessor until after attachment,
         * so seeing a null next field does not necessarily mean that
         * node is at end of queue. However, if a next field appears
         * to be null, we can scan prev's from the tail to
         * double-check.  The next field of cancelled nodes is set to
         * point to the node itself instead of null, to make life
         * easier for isOnSyncQueue.
         */
        volatile Node next;

        /**
         * The thread that enqueued this node.  Initialized on
         * construction and nulled out after use.
         */
        volatile Thread thread;

        /**
         * Link to next node waiting on condition, or the special
         * value SHARED.  Because condition queues are accessed only
         * when holding in exclusive mode, we just need a simple
         * linked queue to hold nodes while they are waiting on
         * conditions. They are then transferred to the queue to
         * re-acquire. And because conditions can only be exclusive,
         * we save a field by using special value to indicate shared
         * mode.
         */
        Node nextWaiter;

        /**
         * Returns true if node is waiting in shared mode.
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        /**
         * Returns previous node, or throws NullPointerException if null.
         * Use when predecessor cannot be null.  The null check could
         * be elided, but is present to help the VM.
         *
         * @return the predecessor of this node
         */
        final Node predecessor() {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        /** Establishes initial head or SHARED marker. */
        Node() {}

        /** Constructor used by addWaiter. */
        Node(Node nextWaiter) {
            this.nextWaiter = nextWaiter;
            THREAD.set(this, Thread.currentThread());
        }

        /** Constructor used by addConditionWaiter. */
        Node(int waitStatus) {
            WAITSTATUS.set(this, waitStatus);
            THREAD.set(this, Thread.currentThread());
        }

        /** CASes waitStatus field. */
        final boolean compareAndSetWaitStatus(int expect, int update) {
            return WAITSTATUS.compareAndSet(this, expect, update);
        }

        /** CASes next field. */
        final boolean compareAndSetNext(Node expect, Node update) {
            return NEXT.compareAndSet(this, expect, update);
        }

        final void setPrevRelaxed(Node p) {
            PREV.set(this, p);
        }

        // VarHandle mechanics
        private static final VarHandle NEXT;
        private static final VarHandle PREV;
        private static final VarHandle THREAD;
        private static final VarHandle WAITSTATUS;
        static {
            try {
                MethodHandles.Lookup l = MethodHandles.lookup();
                NEXT = l.findVarHandle(Node.class, "next", Node.class);
                PREV = l.findVarHandle(Node.class, "prev", Node.class);
                THREAD = l.findVarHandle(Node.class, "thread", Thread.class);
                WAITSTATUS = l.findVarHandle(Node.class, "waitStatus", int.class);
            } catch (ReflectiveOperationException e) {
                throw new ExceptionInInitializerError(e);
            }
        }
    }

线程在获取锁时可能会有两种行为

  • 1. 尝试获取锁,然后立即返回结果——对应AQS中的tryAcquire方法
  • 2. 获取锁,愿意进入队列等待,直到获取——对应AQS中的acquire方法

tryAcquire方法被protected修饰,参数是一个int值,代表了对status的修改,返回值是一个boolean类型,代表是否成功获得锁。

    /**
     * Attempts to acquire in exclusive mode. This method should query
     * if the state of the object permits it to be acquired in the
     * exclusive mode, and if so to acquire it.
     *
     * <p>This method is always invoked by the thread performing
     * acquire.  If this method reports failure, the acquire method
     * may queue the thread, if it is not already queued, until it is
     * signalled by a release from some other thread. This can be used
     * to implement method {@link Lock#tryLock()}.
     *
     * <p>The default
     * implementation throws {@link UnsupportedOperationException}.
     *
     * @param arg the acquire argument. This value is always the one
     *        passed to an acquire method, or is the value saved on entry
     *        to a condition wait.  The value is otherwise uninterpreted
     *        and can represent anything you like.
     * @return {@code true} if successful. Upon success, this object has
     *         been acquired.
     * @throws IllegalMonitorStateException if acquiring would place this
     *         synchronizer in an illegal state. This exception must be
     *         thrown in a consistent fashion for synchronization to work
     *         correctly.
     * @throws UnsupportedOperationException if exclusive mode is not supported
     */
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

上层业务可以重写次方法,实现获取锁之后的相关业务:

如果选择等待锁,可以使用acquire方法,而不是自己实现复杂的排队逻辑。

如下所示,acquire的修饰符为public和final,意思是继承类可以直接调用我这个方法,而且不允许继承类擅自override,意思是这个方法一定能获取锁

    /**
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * by invoking at least once {@link #tryAcquire},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquire} until success.  This method can be used
     * to implement method {@link Lock#lock}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

代码中首先尝试获取锁,当获取不到锁时,会创建一个等待者,并将等待者加入到等待队列。

首先看下这个addWaiter方法,进入方法后,首先创建一个Node节点,然后判断下队尾是否为空,非空,将节点加到队尾的后面,并返回节点。


    /**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    private Node addWaiter(Node mode) {
        Node node = new Node(mode);

        for (;;) {
            Node oldTail = tail;
            if (oldTail != null) {
                node.setPrevRelaxed(oldTail);
                if (compareAndSetTail(oldTail, node)) {
                    oldTail.next = node;
                    return node;
                }
            } else {
                initializeSyncQueue();
            }
        }
    }

然后看下acquireQueued方法,看下等待队列是如何被消费的:

    /**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    final boolean acquireQueued(final Node node, int arg) {
        boolean interrupted = false;
        try {
            for (;;) {
                final Node p = node.predecessor();
                // 如果线程的前置节点为头节点,且尝试获取锁成功,则进行返回
                // AQS 中,头节点为一个虚节点,即头节点并不是当前需要获取锁的节点
                // 当第二个节点获取锁之后,他就会变成头节点,头节点就会出队
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                // 如果线程需要被挂起
                // 这里没有选择自旋等待,而是判断是否挂起,可以防止性能问题
                // 理想情况下,需要将那些没有资格获取锁的节点挂起,再在适合的时间进行唤醒
                if (shouldParkAfterFailedAcquire(p, node))
                    interrupted |= parkAndCheckInterrupt();
            }
        } catch (Throwable t) { // 出现异常,则取消节点的等待,并进行清理工作
            cancelAcquire(node);
            if (interrupted)
                selfInterrupt();
            throw t;
        }
    }

我们看下如何判断线程是否该被挂起:

下面代码中的四种状态,就是AQS源码中一开始列举实体属性时列举的状态。

shouldParkAfterFailedAcquire方法根据线程的状态信息,来判断线程是否需要被挂起。


    /**
     * Checks and updates status for a node that failed to acquire.
     * Returns true if thread should block. This is the main signal
     * control in all acquire loops.  Requires that pred == node.prev.
     *
     * @param pred node's predecessor holding status
     * @param node the node
     * @return {@code true} if thread should block
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
        }
        return false;
    }

parkAndCheckInterrupt方法对线程执行了挂起操作


    /**
     * Convenience method to park and then check if interrupted.
     *
     * @return {@code true} if interrupted
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);  //挂起这个线程
        return Thread.interrupted(); // 返回线程的中断标志位,并将其赋值为false
    }

通过上述分析,AQS等待队列的执行效果如下:

如果当前线程所在的节点处于头节点的后面一个,那么他将会不断尝试获取锁

 否则进行判断是否需要被挂起。

线程会被挂起的条件——线程的前驱节点不是头节点,并且waitStatus为SINGAL

这样可以保证head之后只会有一个节点在通过CAS获取锁,队列里其他线程都已被挂起或者正在被挂起,最大程度的避免无用的自旋消耗CPU

那么什么时候线程会被唤醒?

当线程使用完资源只会,会释放锁,并唤醒其他线程去获取锁。

这里使用到的方法是tryReleaserelease方法。

从下方源码中可以看到:如果继承类上层业务没有去override这个tryRelease方法,则会直接抛出异常

    /**
     * Attempts to set the state to reflect a release in exclusive
     * mode.
     *
     * <p>This method is always invoked by the thread performing release.
     *
     * <p>The default implementation throws
     * {@link UnsupportedOperationException}.
     *
     * @param arg the release argument. This value is always the one
     *        passed to a release method, or the current state value upon
     *        entry to a condition wait.  The value is otherwise
     *        uninterpreted and can represent anything you like.
     * @return {@code true} if this object is now in a fully released
     *         state, so that any waiting threads may attempt to acquire;
     *         and {@code false} otherwise.
     * @throws IllegalMonitorStateException if releasing would place this
     *         synchronizer in an illegal state. This exception must be
     *         thrown in a consistent fashion for synchronization to work
     *         correctly.
     * @throws UnsupportedOperationException if exclusive mode is not supported
     */
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

    /**
     * Releases in exclusive mode.  Implemented by unblocking one or
     * more threads if {@link #tryRelease} returns true.
     * This method can be used to implement method {@link Lock#unlock}.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryRelease} but is otherwise uninterpreted and
     *        can represent anything you like.
     * @return the value returned from {@link #tryRelease}
     */
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

假如线程尝试释放锁成功,那么下一步就会唤醒其他线程,可以看下上面源码中的unparkSuccessor方法:

 /**
     * Wakes up node's successor, if one exists.
     *
     * @param node the node
     * 传入的参数为head,该方法用于唤醒head后面的node,使其自旋的获取锁
     */
    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0) {
            // 将head的watiStates设置为0,才不会影响其他函数的判断
            node.compareAndSetWaitStatus(ws, 0); 
        }
        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         * 从尾节点开始向前搜索,找到最靠前的(head除外)并且waitStatus值 <= 0 的节点,
         * 对其进行 LockSupport.unpark 操作,即唤醒该线程
         * 线程一旦被唤醒,那么他将进行尝试获取锁,此时便形成了一个能够良好工作的闭环
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node p = tail; p != node && p != null; p = p.prev)
                if (p.waitStatus <= 0)
                    s = p;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

共享模式

锁可以被多个线程获取,表现为state值的增加,用完需要一一释放。

 int state > 0 代表锁被占用,可以被多个节点占用

              = 0 代表锁被释放

补充

JAVA中断

在Java中的挂起和中断是两个不同维度的概念。

JAVA中的中断,作用与线程对象,他并不会使得线程被挂起,而是会根据线程当前的活动状态来产生不同的效果。

  • 假设当前线程处于等待状态,那么对该线程进行interrupt,会使其抛出中断异常
  • 当线程处于运行状态,那么对该线程进行interrupt,只会改变线程中断的状态值,并不会影响该线程继续运行。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值