Java 多线程随笔

2022/4/3 1:19:33

本文主要是介绍Java 多线程随笔,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

随笔

Java 的线程状态主要有五种,定义在 Thread 类里:

public enum State {
    // 新建的还没有开始线程
    NEW,

    // 正在 JVM 中执行但是可能正在等待来自操作系统的其他资源,比如 CPU
    RUNNABLE,

    // 正在等待一个监视锁 来首次进入或(在调用 Object.wait 后)重新进入同步块
    BLOCKED,

    /**
     * 等同于操作系统的挂起状态
     *
     * 在下列情况发生时,线程会进入此状态:
     * 1. 无超时的 Object#wait() 
     * 2. 无超时的 Thread#join()
     * 3. LockSupport#park()
     *
     * 如果要结束这个状态,必须由外部激活它
     */
    WAITING,

    /**
     * 带有时间限制的 WAITING 状态,超过这个时间就自动结束这个状态,以下方法的调用会使一个线程进入此状态: 
     *
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     */
    TIMED_WAITING,

    // 终止状态,线程已完成执行
    TERMINATED;
}

可重入锁

如何锁

非公平锁

无参构造函数默认使用非公平锁,它的锁方法如下所示:

    final void lock() {
        // 如果原来没有线程持有此锁,尝试使用CAS获取它的锁,如果成功就可以直接获得锁
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 否则便是有线程持有该锁,就开始获取锁的漫漫长路
            acquire(1);
    }

    public final void acquire(int arg) {

        // 首先尝试获取锁,失败则从队列中获取锁直到成功
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

            // 如果有中断发生,那就中断自己
            selfInterrupt();
    }

如何尝试获取锁?tryAcquire(arg) 会调用此方法:

    // 不公平地获取锁
    final boolean nonfairTryAcquire(int acquires) {

        // 获取当前线程
        final Thread current = Thread.currentThread();

        // 获取当前锁状态
        int c = getState();

        // 如果没有线程持有锁
        if (c == 0) {

            // 那么尝试使用CAS修改锁状态,如果成功则当前线程获得锁成功
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {

            // 否则如果当前线程就是当前持有锁的线程,直接修改锁状态
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }

        // 其余情况都算是尝试获取锁失败
        return false;
    }

简单来说就是,如果没有线程持有这个锁,那我就拿到锁了;如果当前拿着锁的线程就是我自己,那我又拿到锁了;其他情况都拿不到锁。锁可被多个线程持有,故名可重入。

// 从等待队列中获取锁,挂起式等待
final boolean acquireQueued(final Node node, int arg) {

    // 定义获取锁的结果,默认成功
    boolean failed = true;
    try {
        // 标识是否被中断,默认没有
        boolean interrupted = false;

        // 重复以下步骤直到成功获取到锁
        for (;;) {
            // 获取当前节点的前一个节点
            final Node p = node.predecessor();

            // 如果前一个节点是头节点,那么就尝试获取锁
            if (p == head && tryAcquire(arg)) {

                // 如果成功获取到锁,当前节点就是头节点了
                setHead(node);

                // 让 p 这个对象不可达
                p.next = null; // help GC

                // 获取成功了
                failed = false;

                // 返回中断标识
                return interrupted;
            }

            // 如果当前节点应该睡觉,那就将当前线程挂起(挂起后这个循环将不再获得 CPU 资源,直到被唤起才会获得 CPU 资源),并且检查一下是否有中断发生了
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())

                // 如果在当前线程被挂起之后,有当前线程的中断发生了
                interrupted = true;
        }
    } finally {
        // 如果最终获取锁失败了,那就不拿了!
        if (failed)
            cancelAcquire(node);
    }
}

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 前一个节点处于什么状态
    int ws = pred.waitStatus;

    // 如果它的前一个节点处于将会释放锁的状态,那么它就可以放心睡觉了,因为它的前一个节点会叫醒它
    if (ws == Node.SIGNAL)
        return true;

    // 如果它的前一个节点处于放弃锁的状态
    if (ws > 0) {

        // 那么在队列中一直往前找,直到找到不放弃锁的节点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);

        // 将那些放弃锁的节点都扔了
        pred.next = node;
    } else {

        // 如果不是上面那些情况,也就是那些既没有放弃锁又不会叫醒我的节点,那就让它们会叫醒我
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }

    // 然后说,我还不可以睡觉!
    return false;
}

private final boolean parkAndCheckInterrupt() {

    // 令当前线程挂起
    LockSupport.park(this);

    // 返回是否有线程被中断了
    return Thread.interrupted();
}


这篇关于Java 多线程随笔的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程