java 并发(线程&锁)

java 并发(线程&锁)

##线程
###线程概念

操作系统调度的最小单元是线程,也叫轻量级进程(LightWeight Process),在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行

###线程优先级

setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分
配时间片的数量要多于优先级低的线程。设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占

###线程的状态

  • new
  • runnable(就绪,允许状态)
  • blocked
  • waitting(进入等待状态,需要触发条件)
  • timewaiting(进入等待状态,但是在指定时间内会放回)
  • terminated

    在这里插入图片描述
    ###守护线程

Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程
Daemon线程被用作完成支持性工作,但是在Java虚拟机退出时Daemon线程中的finally块(优雅停机,线程池安全退出)

###已过期方法
stop
resume
suspend

###线程间的通信

在这里插入图片描述

  • 等待通知机制(notity/wait/wati(time)/notyfyAll)
  • Thread.join 在线程中调用另外线程的join方法,该线程会等到另外的那个线程返回之后再继续执行
  • ThreadLocal

###ThreadLocal

  • 为每个使用该变量的线程提供的一个副本,可以做到数据隔离
  • 数据不一致问题:存储了同一个对象的引用
  • 内存泄漏问题:
    1.内存泄漏是导致内存溢出的原因之一,但两者并不等价,内存泄漏更多的是程序中不再持有某个对象的引用,但是该对象仍然无法被垃圾回收器回收,根本原因是该对象到引用根Root的链路是可达的
    2.举例–java 高并发编程详解270页

-w428

在这里插入图片描述
引申问题:

扫描二维码关注公众号,回复: 3943522 查看本文章
  1. JVM 引用与垃圾回收

    • 强引用 strongRefrence:
      创建一个对象并把这个对象赋给一个引用变量。强引用有引用变量指向时永远不会被垃圾回收。即使内存不足的时候
    • 弱引用 weakRefrence: weakReference 在JVM触发任意的gc 都会导 致Entry的回收
      如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
    • 软引用 softRefrence:
      如果一个对象只具有软引用。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存
    • PhantomReference(幻影引用)
      可以很好的获取到哪个对象即将被垃圾回收
      参考:《java高并发设计模式》 304页
  2. ThreadLocal与InheritableThreadLocal区别及应用场景

  3. ThreadLocal的扩容过程

private void resize() {
13             Entry[] oldTab = table;
14             int oldLen = oldTab.length;
15             int newLen = oldLen * 2;
16             Entry[] newTab = new Entry[newLen];
17             int count = 0;
18             // 遍历Entry[]数组
19             for (int j = 0; j < oldLen; ++j) {
20                 Entry e = oldTab[j];
21                 if (e != null) {
22                     ThreadLocal<?> k = e.get();
23                     if (k == null) {// 如果key=null
24                         e.value = null; // 把value也置null,有助于GC回收对象
25                     } else {// 如果key!=null
26                         int h = k.threadLocalHashCode & (newLen - 1);// 计算hash值 
27                         while (newTab[h] != null)// 如果这个位置已使用
28                             h = nextIndex(h, newLen);// 线性往后查询,直到找到一个没有使用的位置,h递增
29                         newTab[h] = e;//在第一个空节点上塞入Entry e
30                         count++;// 计数++
31                     }
32                 }
33             }
34 
35             setThreshold(newLen);// 设置新的阈值(实际set方法用了2/3的newLen作为阈值)
36             size = count;// 设置ThreadLocalMap的元素个数
37             table = newTab;// 把新table赋值给ThreadLocalMap的Entry[] table
38         }

参考:https://www.cnblogs.com/dennyzhangdd/p/7978455.html 源码分析
###synchronized与lock的区别

  • synchronized 在成功完成功能或者抛出异常时,虚拟机会自动释放线程占有的锁;而Lock对象在发生异常时,如果没有主动调用unLock()方法去释放锁,则锁对象会一直持有,因此使用Lock时需要在finally块中释放锁
  • lock接口锁可以通过多种方法来尝试获取锁包括立即返回是否成功的tryLock(),以及一直尝试获取的lock()方法和尝试等待指定时间长度获取的方法,相对灵活了许多比synchronized;
  • 通过在读多,写少的高并发情况下,我们用ReentrantReadWriteLock分别获取读锁和写锁来提高系统的性能,因为读锁是共享锁,即可以同时有多个线程读取共享资源,而写锁则保证了对共享资源的修改只能是单线程的
  • lock可以非阻塞方式获取锁
    使用推荐:少量同步使用synchronized,简单.
    并发高,争抢激烈使用lock

###synchronized实现原理
synchronized是基于Monitor来实现同步的。

在这里插入图片描述
在这里插入图片描述
Monitor 的工作机理

  1. 线程进入同步方法中。
  2. 为了继续执行临界区代码,线程必须获取 Monitor 锁。如果获取锁成功,将成为该监视者对象的拥有者。任一时刻内,监视者对象只属于一个活动线程(The Owner)
  3. 拥有监视者对象的线程可以调用 wait() 进入等待集合(Wait Set),同时释放监视锁,进入等待状态。
  4. 其他线程调用 notify() / notifyAll() 接口唤醒等待集合中的线程,这些等待的线程需要重新获取监视锁后才能执行 wait() 之后的代码。
  5. 同步方法执行完毕了,线程退出临界区,并释放监视锁
    参考:
    https://www.cnblogs.com/nsw2018/p/5821738.html
    https://www.toutiao.com/i6538662208836993544/

###LockSupport

  • 概念: LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语。LockSupport很类似于二元信号量(只有1个许可证可供使用),如果这个许可还没有被占用,当前线程获取许可并继续执行;如果许可已经被占用,当前线程阻塞,等待获取许可

  • LockSupport阻塞和解除阻塞线程直接操作的是Thread,而Object的wait/notify它并不是直接对线程操作,它是被动的方法,它需要一个object来进行线程的挂起或唤醒.Thead在调用wait之前, 当前线程必须先获得该对象的监视器(synchronized),被唤醒之后需要重新获取到监视器才能继续执行.而LockSupport可以随意进行park或者unpark

  • 基本操作
    1. 先执行park,然后在执行unpark,进行同步,并且在unpark的前后都调用了getBlocker,可以看到两次的结果不一样,第二次调用的结果为null,这是因为在调用unpark之后,执行了Lock.park(Object blocker) 方法中的setBlocker(t, null) 方法,所以第二次调用getBlocker时为null
    2. 先执行unpark,在调用park,直接就没被阻塞

在这里插入图片描述
参考:
https://blog.csdn.net/secsf/article/details/78560013 synchornized与locksupport区别
https://www.imooc.com/article/48378 含项目代码
https://www.jianshu.com/p/ceb8870ef2c5
https://www.jianshu.com/p/1add173ea703
https://yq.aliyun.com/articles/493552

##AQS(队列同步器AbstractQueuedSynchronizer)

  • 概念:抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch
  • aqs维护着一个state属性,代表共享的资源,和一个first-in-first-out (FIFO)等待队列。基于此实现加锁、同步的基础框架。
    state资源可以表示锁或同步的状态,如在ReentrantLock中加锁或者重入则state+=1,释放锁state-=1。当state>0时,其他线程会加入到FIFO队列等待释放锁。在Semaphore中则是获取资源state-=1,释放资源state+=1。state为0代表资源已消耗完,阻塞等待

    在这里插入图片描述
  1. 数据结构
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    	...
    	//等待队列头
        private transient volatile Node head;
        //等待队列尾
        private transient volatile Node tail;
        //同步状态
        private volatile int state;
    	...
    }
  1. Node结构

变量waitStatus则表示当前被封装成Node结点的等待状态,共有4种取值CANCELLED、SIGNAL、CONDITION、PROPAGATE。

  • CANCELLED:值为1,在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,即结束状态,进入该状态后的结点将不会再变化。
  • SIGNAL:值为-1,被标识为该等待唤醒状态的后继结点,当其前继结点的线程释放了同步锁或被取消,将会通知该后继结点的线程执行。说白了,就是处于唤醒状态,只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行。
  • CONDITION:值为-2,与Condition相关,该标识的结点处于等待队列中,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。
  • PROPAGATE:值为-3,与共享模式相关,在共享模式中,该状态标识结点的线程处于可运行状态。
  • 0状态:值为0,代表初始化状态。AQS在判断状态时,通过用waitStatus>0表示取消状态,而waitStatus<0表示有效状态。
static final class Node {
 	static final Node SHARED = new Node();	
 	static final Node EXCLUSIVE = null;
 	volatile int waitStatus;
 	volatile Node prev;
 	volatile Node next;
 	volatile Thread thread;
 	Node nextWaiter;
}
  1. 状态相关方法
protected final int getState() {
        return state;
    }
    protected final void setState(int newState) {
        state = newState;
    }
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

####独占式获取与释放
AQS模板方法之acquire(int arg)独占式获取同步状态

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  1. 添加到等待队列且自旋等待获取
    private Node addWaiter(Node mode) {
        //新建一个Node,mode表示被独占或共享
        Node node = new Node(Thread.currentThread(), mode);
        //尝试快速设置尾节点
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            //CAS设置tail
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t; 
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
  1. 新建一个node,先尝试插入到尾节点后面。如果失败就自旋,直到添加成功为止
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.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

  1. 自旋尝试获取同步状态,成功则返回。
    shouldParkAfterFailedAcquire()检测是否需要阻塞当前线程(根据前一个节点的waitStatus),删除取消状态的线程
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        //前一个节点是SINGNAL,则当前节点需要等待
        if (ws == Node.SIGNAL)
            return true;
        //ws>0 表示线程被中断或超时,删除等待线程
        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(); 
    }

####AQS模板方法

AQS模板方法之acquire(int arg)独占式获取同步状态
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

先尝试获取锁,成功则返回。不成功添加到等待队列,然后循环等待直到获取成功。

AQS模板方法之acquireInterruptibly(int arg),独占式获取锁,可响应中断
    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        //添加到等待队列中
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                //只有当前节点的pre是head,才可以尝试获取锁。
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //如果线程调用了中断,抛出异常,实现中断
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                //中断后,取消等待
                cancelAcquire(node);
        }
    }

先判断线程是否被中断,如果中断抛出InterruptedException异常
尝试获取锁,成功直接返回。失败执行doAcquireInterruptibly。
doAcquireInterruptiblu先添加到等待队列中,在自旋中,只有当前节点的pre是head才尝试获取锁(因为头节点是获取同步状态成功的节点,头节点释放锁后唤醒下一个节点),

#####AQS模板方法之tryAcquireNanos(int arg, long nanosTimeout),超时等待

    public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }

     private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        //添加一个独占节点到等待队列
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            //自旋尝试获取锁,检测超时
            for (;;) {
                //尝试获取锁,获取成功返回true
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                //大于spinForTimeoutThreshold时间间隔就park一段时间
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                //线程中断,抛出异常
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

先尝试获取锁,获取失败执行doAcquireNanos()方法
doAcquireNanos()除了尝试获取锁同时记录超时,超过时间没有成功,返回false

AQS模板方法之release(int arg)释放锁
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            //从后往前早,状态小于0 的节点,相当于找到离头节点最近的有效未取消或中断的节点
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            //唤醒head之后的节点
            LockSupport.unpark(s.thread);
    }

####共享获取

AQS模板方法之acquireShared(int arg)获取共享锁
    public final void acquireShared(int arg) {
        //先尝试获取共享锁
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

    private void doAcquireShared(int arg) {
        //添加一个共享节点到队列
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; 
    setHead(node);//head指向自己
     //如果还有剩余量,继续唤醒下一个邻居线程
    if (propagate > 0 || h == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();
    }
}


在这里插入图片描述

参考:https://www.cnblogs.com/waterystone/p/4920797.html

###ReentrantLock 源码分析

###ReentrantReadWriteLock 源码分析

默认初始化readlock ,writelock,其中sync 同步器继承AQS


public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** Performs all synchronization mechanics */
    final Sync sync;

    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }


    abstract static class Sync extends AbstractQueuedSynchronizer {}

    static final class NonfairSync extends Sync {}

    static final class FairSync extends Sync {}

    public static class ReadLock implemen·ts Lock, java.io.Serializable {}

    public static class WriteLock implements Lock, java.io.Serializable {}
}
  • Sync类中变量
    因为state 值是32位int型,前16位表示读锁个数,后16位表示写锁个数
 static final int SHARED_SHIFT   = 16;
        //因为写锁上前16位,每次要让共享锁+1,就应该让state加 1<<16,       
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT); / static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;  //每种锁的最大重入数量
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
        /** Returns the number of shared holds represented in count  */
        //获取读锁个数。向右移16得到的数据为读锁个数
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        /** Returns the number of exclusive holds represented in count  */
        //获取写锁个数
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1; //如果独占模式被占且不是当前线程持有,则获取失败
            int r = sharedCount(c);
            //如果公平策略没有要求阻塞且重入数没有到达最大值,则直接尝试CAS更新state
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                //更新成功后会在firstReaderHoldCount中或readHolds(ThreadLocal类型的)的本线程副本中记录当前线程重入数(浅蓝色代码),这是为了实现jdk1.6中加入的getReadHoldCount()方法的,这个方法能获取当前线程重入共享锁的次数(state中记录的是多个线程的总重入次数),加入了这个方法让代码复杂了不少,但是其原理还是很简单的:如果当前只有一个线程的话,还不需要动用ThreadLocal,直接往firstReaderHoldCount这个成员变量里存重入数,当有第二个线程来的时候,就要动用ThreadLocal变量readHolds了,每个线程拥有自己的副本,用来保存自己的重入数。
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current); //用来处理CAS没成功的情况,逻辑和上面的逻辑是类似的,就是加了无限循环
        }

获取读锁失败的话,加入到等待队列

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
        //获取失败之后,加入到等待队列,一直自旋获取锁,头节点获取到锁后需要唤醒它下一个节点,第二个节点需要不断的自旋询问是否可以拿到锁
            doReleaseShared();
            return true;
        }
        return false;
    }
protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            //浅蓝色代码也是为了实现jdk1.6中加入的getReadHoldCount()方法,在更新当前线程的重入数。
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            //这里是真正的释放同步状态的逻辑,就是直接同步状态-SHARED_UNIT,然后CAS更新,没啥好说的
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    // Releasing the read lock has no effect on readers,
                    // but it may allow waiting writers to proceed if
                    // both read and write locks are now free.
                    return nextc == 0;
            }
        }

参考:https://www.cnblogs.com/sheeva/p/6480116.html
参考:https://www.cnblogs.com/leesf456/p/5419132.html
###Condition

-w600
在这里插入图片描述

Condition配合ReentrantLock可以实现等待/通知的机制。await()在await()前需要先获取锁。进入await()线程释放锁,进入等待状态直到被中断或者被signle()/singleAll()/single()/single()唤醒在Condition等待的线程,执行之前需要获取锁

####Condition 源码解析

Condtion只是一个接口。主要分析在AQS中Condtion的实现,ConditionObject。分析过程中主要解决。await()如何释放锁?如何等待?如何被唤醒?唤醒后如何继续执行等问题

AQS等待队列与Condition队列是两个相互独立的队列
await()就是在当前线程持有锁的基础上释放锁资源,并新建Condition节点加入到Condition的队列尾部,阻塞当前线程
signal()就是将Condition的头节点移动到AQS等待节点尾部,让其等待再次获取锁

在这里插入图片描述
II.节点1执行Condition.await()
1.将head后移
2.释放节点1的锁并从AQS等待队列中移除
3.将节点1加入到Condition的等待队列中
4.更新lastWaiter为节点1


在这里插入图片描述

III.节点2执行signal()操作
5.将firstWaiter后移
6.将节点4移出Condition队列
7.将节点4加入到AQS的等待队列中去
8.更新AQS的等待队列的tail

在这里插入图片描述

public final void await() throws InterruptedException {
    // 1.如果当前线程被中断,则抛出中断异常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 2.将节点加入到Condition队列中去,这里如果lastWaiter是cancel状态,那么会把它踢出Condition队列。
    Node node = addConditionWaiter();
    // 3.调用tryRelease,释放当前线程的锁
    long savedState = fullyRelease(node);
    int interruptMode = 0;
    // 4.为什么会有在AQS的等待队列的判断?
    // 解答:signal操作会将Node从Condition队列中拿出并且放入到等待队列中去,在不在AQS等待队列就看signal是否执行了
    // 如果不在AQS等待队列中,就park当前线程,如果在,就退出循环,这个时候如果被中断,那么就退出循环
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 5.这个时候线程已经被signal()或者signalAll()操作给唤醒了,退出了4中的while循环
    // 自旋等待尝试再次获取锁,调用acquireQueued方法
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null)
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

将condition队列的node,放到AQSd队列中

final boolean transferForSignal(Node node) {
    /*
     * 设置node的waitStatus:Condition->0
     */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    /*
     * 加入到AQS的等待队列,让节点继续获取锁
     * 设置前置节点状态为SIGNAL
     */
    Node p = enq(node);
    int c = p.waitStatus;
    if (c > 0 || !compareAndSetWaitStatus(p, c, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

参考:https://blog.csdn.net/coslay/article/details/45217069
参考:https://www.jianshu.com/p/be2dc7c878dc
###valotile

  • 概念:volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文的切换和调度
    两个原则:
  • volateile 的变量在写入的时候,回直接写到内存上
  • 其他线程通过嗅探总线,检测自己缓存对的内存块上的数据是否过期,过期就会设置为无效,当需要操作该数据时候,会从内存中从新加载进来

    在这里插入图片描述
    ##CAS(Compare And Swap)
  • 当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作,这点从图中也可以看出来。基于这样的原理,CAS操作即使没有锁,同样知道其他线程对共享资源操作影响,并执行相应的处理措施。同时从这点也可以看出,由于无锁操作中没有锁的存在,因此不可能出现死锁的情况,也就是说无锁操作天生免疫死锁


在这里插入图片描述
##锁
###公平锁与非公平锁
公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
  非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。
  在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。
而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。设置方法如下:ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
参考:
https://blog.csdn.net/yanyan19880509/article/details/52345422/ (生动形象)

###可重入锁
synchronized/ReentrantReadWriteLock

参考:
https://blog.csdn.net/yanyan19880509/article/details/52345422/ (生动形象)
https://blog.csdn.net/u012545728/article/details/80843595
###独享锁/共享锁

  • 独享锁:该锁每一次只能被一个线程所持有
  • 共享锁:该锁可被多个线程共有,典型的就是ReentrantReadWriteLock里的读锁,它的读锁是可以被共享的,但是它的写锁确每次只能被独占

###互斥锁/读写锁/自旋锁

  •  互斥锁:共享资源的使用是互斥的,即一个线程获得资源的使用权后就会将该资源加锁,使用完后会将其解锁,如果在使用过程中有其他线程想要获取该资源的锁,那么它就会被阻塞陷入睡眠状态,直到该资源被解锁才会被唤醒,如果被阻塞的资源不止一个,那么它们都会被唤醒,但是获得资源使用权的是第一个被唤醒的线程,其它线程又陷入沉睡.
    
  • 读写锁:它拥有读状态加锁、写状态加锁、不加锁这三种状态。只有一个线程可以占有写状态的锁,但可以有多个线程同时占有读状态锁,这也是它可以实现高并发的原因。当其处于写状态锁下,任何想要尝试获得锁的线程都会被阻塞,直到写状态锁被释放;如果是处于读状态锁下,允许其它线程获得它的读状态锁,但是不允许获得它的写状态锁,直到所有线程的读状态锁被释放;为了避免想要尝试写操作的线程一直得不到写状态锁,当读写锁感知到有线程想要获得写状态锁时,便会阻塞其后所有想要获得读状态锁的线程。所以读写锁非常适合资源的读操作远多于写操作的情况
    
  • 自旋锁
    在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU

###乐观锁/悲观锁
乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新,
CAS 利用操作系统底层,compare and sweep 的机制

###分段锁
是一种锁设计,例如ConcurrentHashMap
ConcurrentHashMap来说一下分段锁的含义以及设计思想,
ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。
但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。
分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作
###偏向锁/轻量级锁/重量级锁
这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。

  • 偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价
  • 轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能
  • 重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低

参考:https://www.toutiao.com/i6538662208836993544/

猜你喜欢

转载自blog.csdn.net/arswbjeqnhiykz7229/article/details/83622454