进程、线程
概念
- 进程就是进行中的程序,它是个动态的概念。是系统进行资源分配与调度的基本单位.
- 线程就是进程中并发执行的一个子任务。
- 程序计数器和运行栈是线程私有,共享内存空间和父进程资源。
- ThreadPoolTaskExecutor线程池拿到线程,然后
CompletionService<Long> completionService = new ExecutorCompletionService<Long>(threadPoolTaskExecutor)
得到CompletionService做异步执行 - Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。
completionService.submit(new Callable)
然后completionService.take()
拿到Feature, future.get()得到结果
守护线程
- 当线程只剩下守护线程的时候,JVM就会退出;补充一点如果还有其他的任意一个用户线程还在,JVM就不会退出
- thread.setDaemon(true)必须在thread.start()之前设置
- 作用:当主线程结束时,结束其余的子线程(守护线程)自动关闭,就免去了还要一个个关闭的麻烦。
CAS
CAS 机制:
- 适用场景:乐观认为并发不高,不需要阻塞,可以不上锁。
- 特点:一般使用中常用自旋,不断比较更新,直到成功。
- 缺点:高并发cpu压力大;ABA问题
- 关联:在AQS和Atomic相关类中也是使用到了CAS机制。
锁
公平锁与非公平锁
- 先对锁进行请求的就一定先获取到锁,那么这就是公平锁,反之是非公平锁。
- 非公平锁机制的效率往往会胜过公平锁的原因是,恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟.
- 持有锁的时间相对较长或者请求锁的平均间隔较长,应该使用公平锁。因为这些情况下,插队带来的吞吐量提升可能不会出现
AbstractQueuedSynchronizer
获取锁思路
AQS 在获取锁的思路是,先尝试直接获取锁,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个锁,则会失败并将当前线程放在队列中,按照 FIFO 的原则等待锁。
final void lock() { acquire(1);}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- addWaiter是线程入队之前包装成Node,然后追加到队尾。
- 创建好Node后,如果队列不为空,使用cas的方式将Node加入到队列尾。注意,这里只执行了一次修改操作,并且可能因为并发的原因失败。因此修改失败的情况和队列为空的情况,需要进入enq。
- enq是个死循环,保证Node一定能插入队列。注意到,当队列为空时,会先为头节点创建一个空的Node,因为头节点代表获取了锁的线程,现在还没有,所以先空着。
- 调用acquireQueued阻塞线程
- 这里先自旋几次,不成功在阻塞。
- 如果前一个节点正好是head,表示自己排在第一位,可以马上调用tryAcquire尝试。如果获取成功就简单了,直接修改自己为head(这步是实现公平锁的核心,保证释放锁时,由下个排队线程获取锁)。
- 自选几次后如果还是不能获取锁就挂起,直到上一个线程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调用lock.lock()时,由于此时锁并没有被其它线程占用,因此线程1直接获得锁并不会进入AQS同步队列中进行等待。
- 在线程1执行期间,线程2调用lock.lock()时由于锁已经被线程1占用,因此,线程2进入AQS同步队列中进行等待。
- 在线程1中执行condition.await()方法后,线程1释放锁并进入条件队列Condition中等待signal信号的到来。
- 线程2,因为线程1释放锁的关系,会唤醒AQS队列中的头结点,所以线程2会获取到锁。
- 线程2调用signal方法,这个时候Condition的等待队列中只有线程1一个节点,于是它被取出来,并被加入到AQS的等待队列中。注意,这个时候,线程1 并没有被唤醒,只是加入到了AQS等待队列中去了。
- 待线程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();
}
公平的锁原理是:老老实实的开始就走 AQS 的流程排队获取锁
非公平锁原理是:在 lock 的时候先直接 cas 修改一次 state 变量(尝试获取锁),成功就返回,不成功再走 AQS 的流程。
-
公平锁和非公平锁区别:
image.png ReetrantLock支持对同一线程重加锁,但是加锁多少次,就必须解锁多少次,这样才可以成功释放锁
ReentrantLock提供了多样化的同步特性,如超时获取锁、可以被中断获取锁(synchronized的同步是不能中断的)、等待唤醒机制的多个条件变量(Condition)等
Condition能够精细的控制多线程的休眠与唤醒。
对于一个锁,我们可以为多个线程间建立不同的Condition
AbstractQueuedSynchronizer又称为队列同步器,内部通过一个int类型的成员变量state来控制同步状态.内部类Node线程获取锁的排队工作,同时利用内部类ConditionObject构建等待队列.
动态代理
- newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
并发辅助类
CountDownLatch
它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能
CyclicBarrier
字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。
Semaphore
字面意思为 信号量,Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
线程安全容器
ConcurrentHashMap、ConcurrentSkipListMap、CopyOnWriteArrayList
并发队列
ArrayBlockingQueue、ConcurrentLinkedQueue、ConcurrentLinkedQueue、SynchronousQueue、PriorityBlockingQueue
参考:https://blog.csdn.net/vernonzheng/article/details/8247564
ArrayBlockingQueue
有界队列,基于数组实现的阻塞队列
LinkedBlockingQueue
其实也是有界队列,但是不设置大小时就时Integer.MAX_VALUE,内部是基于链表实现的
- ArrayBlockingQueue 与 LinkedBlockingQueue 对比
ArrayBlockingQueue 实现简单,表现稳定,添加和删除使用同一个锁,通常性能不如后者
LinkedBlockingQueue 添加和删除两把锁是分开的,所以竞争会小一些
PriorityBlockingQueue
具有优先级的阻塞队列,无界队列
ConcurrentLinkedQueue
无锁队列,底层使用CAS操作,通常具有较高吞吐量,但是具有读性能的不确定性,弱一致性——不存在如ArrayList等集合类的并发修改异常,通俗的说就是遍历时修改不会抛异常
SynchronousQueue
比较奇葩,内部容量为零,适用于元素数量少的场景,尤其特别适合做交换数据用,内部使用 队列来实现公平性的调度,使用栈来实现非公平的调度,在Java6时使用CAS代替了替换了原来的锁逻辑,它是Executors.newCachedThreadPool()的默认队列。
参见:https://blog.csdn.net/yanyan19880509/article/details/52562039
Executor框架
线程池
参考:https://www.jianshu.com/p/ade771d2c9c0