java 多线程

118 阅读6分钟

线程概述

线程属性

线程状态

线程监控

线程api

java 多线程实现

多线程优势

多线程挑战

线程安全

  • 原子性
  • 可见性
  • 有序性

保护线程安全的手段

内部锁

外部锁

锁怎么保证原子性,可见性,有序性

volatile 关键字

static 和final 关键字

java 内部锁原理

java 外部锁原理(aqs)

概述

AQS(AbstractQueuedSynchronizer)是用来构建锁和其他同步组件的基础框架,它也是Java三大并发工具类(CountDownLatch、CyclicBarrier、Semaphore)的基础。AQS可以被当做是一个同步监视器的实现,并且具有排队功能。当线程尝试获取AQS的锁时,如果AQS已经被别的线程获取锁,那么将会新建一个Node节点,并且加入到AQS的等待队列中,这个队列也由AQS本身自己维护。当锁被释放时,唤醒下一个节点尝试获取锁。

image.png

image.png

数据结构

AQS

//头结点
private transient volatile Node head;

//尾结点
private transient volatile Node tail;

//锁状态,更新通过CAS(有ABA问题)
private volatile int state;

Node

//当前节点处于共享模式的标记
static final Node SHARED = new Node();

//当前节点处于独占模式的标记
static final Node EXCLUSIVE = null;

//线程被取消了
static final int CANCELLED =  1;

//释放资源后需唤醒后继节点
static final int SIGNAL    = -1;

 //等待condition唤醒 
static final int CONDITION = -2;

 //工作于共享锁状态,需要向后传播,
 //比如根据资源是否剩余,唤醒后继节点
static final int PROPAGATE = -3;

//等待状态,有1,0,-1,-2,-3五个值。分别对应上面的值
volatile int waitStatus;

//前驱节点
volatile Node prev;

//后继节点
volatile Node next;

//等待锁的线程
volatile Thread thread;

//等待条件的下一个节点,ConditonObject中用到
Node nextWaiter;

关键方法

  • 获取资源
//获取资源,子类实现。如ReentrantLock 实现公平锁和非公平锁
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

  • 释放资源
// 释放资源,子类实现
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}
  • 获取共享资源,
//获取共享资源,子类实现
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

  • 释放共享资源
//释放共享资源,子类实现
protected boolean tryReleaseShared(int arg) {
        throw new UnsupportedOperationException();
    }

  • 独占锁
//获取资源,已实现,提供子类调用。模板方法,定义程序骨架
public final void acquire(int arg) {
    //tryAcquire 子类实现的方法
    if (!tryAcquire(arg) &&
        //addWaiter 尾插法插入节点
        //acquireQueued,锁竞争优化 
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        //中断
        selfInterrupt();
}


**aquire的步骤:**\
1tryAcquire()尝试获取资源。

2)如果获取失败,则通过addWaiter(Node.EXCLUSIVE), arg)方法把当前线程添加到等待队列队尾,并标记为独占模式。

3)插入等待队列后,并没有放弃获取资源,acquireQueued()自旋尝试获取资源。根据前置节点状态状态判断是否应该继续获取资源。如果前驱是头结点,继续尝试获取资源;

4)在每一次自旋获取资源过程中,失败后调用shouldParkAfterFailedAcquire(Node, Node)检测当前节点是否应该park()。若返回true,则调用parkAndCheckInterrupt()中断当前节点中的线程。若返回false,则接着自旋获取资源。当acquireQueued(Node,int)返回true,则将当前线程中断;false则说明拿到资源了。

5)在进行是否需要挂起的判断中,如果前置节点是SIGNAL状态,就挂起,返回true。如果前置节点状态为CANCELLED,就一直往前找,直到找到最近的一个处于正常等待状态的节点,并排在它后面,返回false,acquireQueed()接着自旋尝试,回到3)。

6)前置节点处于其他状态,利用CAS将前置节点状态置为SIGNAL。当前置节点刚释放资源,状态就不是SIGNAL了,导致失败,返回false。但凡返回false,就导致acquireQueed()接着自旋尝试。

7)最终当tryAcquire(int)返回false,acquireQueued(Node,int)返回true,调用selfInterrupt(),中断当前线程。

  • 锁竞争优化
//锁竞争优化 
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //获取当前竞争锁的线程Node的前置节点
            final Node p = node.predecessor();
            
            //假如前置节点是head,那么表示当前线程是等待队列中最大优先级的等待线程,可以继续最后的尝试获取锁,因为很有可能会获取到锁,从而避免线程挂起、唤醒,耗费资源,这里也算是最终一次尝试获取。
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            
            //让线程处于一种自旋状态,
            //尝试让该线程重新获取锁!当条件满足获取到了锁则可以从自旋过程中
            //退出,否则继续。

            //是检查当前是否有必要挂起,前面我们说过,只有当前置节点的waitStatus是`-1`的时候才会挂起当前节点代表的线程。当`shouldParkAfterFailedAcquire(p, node)`返回true的时候,就可以执行`parkAndCheckInterrupt()`来挂起线程。
            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)
            //前驱节点的状态是SIGNAL,说明前驱节点释放资源后会通知自己
            //此时当前节点可以安全的park(),因此返回true
            return true;
        if (ws > 0) {
            //前驱节点的状态是CANCLLED,说明前置节点已经放弃获取资源了
            //此时一直往前找,直到找到最近的一个处于正常等待状态的节点
            //并排在它后面,返回false
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            //前驱节点的状态是0或PROPGATE,则利用CAS将前置节点的状态置
            //为SIGNAL,让它释放资源后通知自己
            //如果前置节点刚释放资源,状态就不是SIGNAL了,这时就会失败
            // 返回false
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }



 private final boolean parkAndCheckInterrupt() {
        //使用LockSupport,挂起当前线程
        LockSupport.park(this);
        return Thread.interrupted();
    }


 static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }


aqs 应用

  • ReentrantLock(独占锁)

概述

ReentrantLock是独占锁,实现公平锁和非公平锁,默认非公平。

  • 加锁 默认非公平加锁方式。加锁过程如下:

1)更新锁状态(cas),若更新成功则设置独占

  1. 更新不成功,则调用AQS中acquire 加锁

3)AQS中acquire 方法调用子类tryAcquire 方法尝试加锁,若加锁不成功则自旋

  • 释放锁

syn

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    /**
     * Performs {@link Lock#lock}. The main reason for subclassing
     * is to allow fast path for nonfair version.
     */
    abstract void lock();

    /**
     * Performs non-fair tryLock.  tryAcquire is implemented in
     * subclasses, but both need nonfair try for trylock method.
     */
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            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;
    }

    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }

    protected final boolean isHeldExclusively() {
        // While we must in general read state before owner,
        // we don't need to do so to check if current thread is owner
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    // Methods relayed from outer class

    final Thread getOwner() {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }

    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); // reset to unlocked state
    }
}

非公平锁

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

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

公平锁

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        acquire(1);
    }

    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    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;
    }
}

释放锁

//ReentrantLock
public void unlock() {
    // 继承父类AQS 释放锁方法
    sync.release(1);
}


// AQS 
public final boolean release(int arg) {
    // 子类tryRelease 方法
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
           //尝试找到下一位继承人,就是确定下一个获取资源的线程,唤醒指定节点的后继节点。
            unparkSuccessor(h);
        return true;
    }
    return false;
}



 private void unparkSuccessor(Node node) {
        //如果状态为负说明是除CANCEL以外的状态,
        //尝试在等待信号时清除。
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        //下一个节点为空或CANCELLED状态
        //则从队尾往前找,找到正常状态的节点作为之后的继承人
        //也就是下一个能拿到资源的节点
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        //此时找到继承人了,那么就唤醒它
        if (s != null)
            LockSupport.unpark(s.thread);
    }



概述

ReentrantLock 是java 显示锁,ReentrantLock实现公平和非公平二种加锁方式,底层实现依赖AQS 实现

image.png

image.png

  • CountDownLatch

  • CyclicBarrier

  • Semaphore