多线程笔记整理(草稿)

2019-01-29  本文已影响0人  Ray昱成

进程、线程

概念

守护线程

CAS

CAS 机制:

公平锁与非公平锁

AbstractQueuedSynchronizer

获取锁思路

AQS 在获取锁的思路是,先尝试直接获取锁,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个锁,则会失败并将当前线程放在队列中,按照 FIFO 的原则等待锁。

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

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  1. 创建好Node后,如果队列不为空,使用cas的方式将Node加入到队列尾。注意,这里只执行了一次修改操作,并且可能因为并发的原因失败。因此修改失败的情况和队列为空的情况,需要进入enq。
  2. enq是个死循环,保证Node一定能插入队列。注意到,当队列为空时,会先为头节点创建一个空的Node,因为头节点代表获取了锁的线程,现在还没有,所以先空着。
  1. 这里先自旋几次,不成功在阻塞
  2. 如果前一个节点正好是head,表示自己排在第一位,可以马上调用tryAcquire尝试。如果获取成功就简单了,直接修改自己为head(这步是实现公平锁的核心,保证释放锁时,由下个排队线程获取锁)。
  3. 自选几次后如果还是不能获取锁就挂起,直到上一个线程release后释放锁并打断该线程。继续自旋,此时就可以tryAcquire成功,然后进行处理了。

参考:https://www.jianshu.com/p/fe027772e156

释放锁思路

唤醒后继节点,后继节点为nul则需要跳过该节点从tail节点开始。这是因为:

为何后继节点为null: 存在超时、被中断的情况
为何不是从node.next开始: 在于node.next仍然可能会存在null或者取消了,所以采用tail回溯办法找第一个可用的线程。最后调用LockSupport的unpark(Thread thread)方法唤醒该线程

独占锁

ReetrantLock 有且只有一个线程获取到锁,其余线程全部挂起,直到该拥有锁的线程释放锁,被挂起的线程被唤醒重新开始竞争锁

共享锁

CountDownLatch

读写锁

ReentrantReadWriteLock、StampedLock

共享和独占的相同和不同点

与 AQS 的独占功能一样,共享锁是否可以被获取的判断为空方法,交由子类去实现。
与 AQS 的独占功能不同,当锁被头节点获取后,独占功能是只有头节点获取锁,其余节点的线程继续沉睡,等待锁被释放后,才会唤醒下一个节点的线程,而共享功能是只要头节点获取锁成功,就在唤醒自身节点对应的线程的同时,继续唤醒 AQS 队列中的下一个节点的线程,每个节点在唤醒自身的同时还会唤醒下一个节点对应的线程,以实现共享状态的“向后传播”,从而实现共享功能

自旋??

condition实现原理

  1. 首先,线程1调用lock.lock()时,由于此时锁并没有被其它线程占用,因此线程1直接获得锁并不会进入AQS同步队列中进行等待。
  2. 在线程1执行期间,线程2调用lock.lock()时由于锁已经被线程1占用,因此,线程2进入AQS同步队列中进行等待。
  3. 在线程1中执行condition.await()方法后,线程1释放锁并进入条件队列Condition中等待signal信号的到来
  4. 线程2,因为线程1释放锁的关系,会唤醒AQS队列中的头结点,所以线程2会获取到锁。
  5. 线程2调用signal方法,这个时候Condition的等待队列中只有线程1一个节点,于是它被取出来,并被加入到AQS的等待队列中。注意,这个时候,线程1 并没有被唤醒,只是加入到了AQS等待队列中去了
  6. 待线程2执行完成之后并调用lock.unlock()释放锁之后,会唤醒此时在AQS队列中的头结点.所以线程1开始争夺锁(由于此时只有线程1在AQS队列中,因此没人与其争夺),如果获得锁继续执行。
    直到线程1释放锁整个过程执行完毕。
    可以看到,整个协作过程是靠结点在AQS的等待队列和Condition的等待队列中来回移动实现的,Condition作为一个条件类,很好的自己维护了一个等待信号的队列,并在适时的时候将结点加入到AQS的等待队列中来实现的唤醒操作
    原文:https://blog.csdn.net/u010412719/article/details/52089561

acquire和acquireInterruptibly的区别

在acquire中,如果park操作被中断,那么只是记录了interrupted状态,然后继续进入循环判断是否可以acquire或者阻塞。而在acquireInterruptibly中,一旦被中断,那么就立即抛出InterruptedException异常

更多参见:https://www.infoq.cn/article/java8-abstractqueuedsynchronizer

ReetrantLock

public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

动态代理

并发辅助类

CountDownLatch

它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能

CyclicBarrier

字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。

Semaphore

字面意思为 信号量,Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。

线程安全容器

ConcurrentHashMap、ConcurrentSkipListMap、CopyOnWriteArrayList

并发队列

ArrayBlockingQueue、ConcurrentLinkedQueue、ConcurrentLinkedQueue、SynchronousQueue、PriorityBlockingQueue

image.png
参考:https://blog.csdn.net/vernonzheng/article/details/8247564

ArrayBlockingQueue

有界队列,基于数组实现的阻塞队列

LinkedBlockingQueue

其实也是有界队列,但是不设置大小时就时Integer.MAX_VALUE,内部是基于链表实现的

PriorityBlockingQueue

具有优先级的阻塞队列,无界队列

ConcurrentLinkedQueue

无锁队列,底层使用CAS操作,通常具有较高吞吐量,但是具有读性能的不确定性,弱一致性——不存在如ArrayList等集合类的并发修改异常,通俗的说就是遍历时修改不会抛异常

SynchronousQueue

比较奇葩,内部容量为零,适用于元素数量少的场景,尤其特别适合做交换数据用,内部使用 队列来实现公平性的调度,使用栈来实现非公平的调度,在Java6时使用CAS代替了替换了原来的锁逻辑,它是Executors.newCachedThreadPool()的默认队列。
参见:https://blog.csdn.net/yanyan19880509/article/details/52562039

Executor框架

线程池

参考:https://www.jianshu.com/p/ade771d2c9c0

ExecutorService

CompletionService

上一篇 下一篇

猜你喜欢

热点阅读