Java并发包重点统计

最近回来复习java并发包的知识,特意做下统计总结(并发包在面试中以及个人技术上都是重中之重),如有错误,恳请指点 。

本文通过罗列出来这些常见的,重点的,面试常问的简单介绍供阅读者参考(建议读者针对每个api进行源码阅读),并不一一分析,阅读者根据每个点自行进行补充,不至于没有方向没有目的的学习。或许有空的时候我会把每个API拿出来分析分析。

1.CountDownLatch

CountDownLatch 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行:

确保某个计算在其需要的所有资源都被初始化之后才继续执行;

确保某个服务在其依赖的所有其他服务都已经启动之后才启动;

等待直到某个操作所有参与者都准备就绪再继续执行。

关键点:一个或多个线程等待一组线程执行完后再执行。通过AQS实现,原理是通过aqs中state变量来做次数判断。

2.Cyclicbarrier:

CyclicBarrier为常用类,其是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。

关键点:一组线程准备好后一起执行。定义了count变量作为判断是否到达屏障。

3.Fork/Join:

Fork/Join框架是一个实现了ExecutorService接口的多线程处理器。它可以把一个大的任务划分为若干个小的任务并发执行,充分利用可用的资源,进而提高应用的执行效率。

关键点:分而治之,工作窃取

4.Semaphore:

Semaphore为常用类,其是一个计数信号量,从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞 每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。

关键点:控制线程对资源的获取,通过aqs的state变量来判断

5.Callable,FutureTask:

Java 5.0 在java.util.concurrent 提供了一个新的创建执行线程的方式:Callable 接口。Callable 接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是Runnable 不会返回结果,并且无法抛出经过检查的异常。Callable 需要依赖FutureTask ,FutureTask 也可以用作闭锁。

关键点:结果返回

6.volatile:

Java提供了一种稍弱的同步机制,即volatile 修饰变量,用来确保将变量的更新操作通知到其他线程。可以将volatile 看做一个轻量级的锁,但是又与锁有些不同: 

- 对于多线程,不是一种互斥关系 

- 不能保证变量状态的“原子性操作”

关键点:保证可见性,不保证原子性,原理是工作内存与主内存

7.ThreacLocal:

ThreadLocal类顾名思义可以理解为线程本地变量。也就是说如果定义了一个ThreadLocal,每个线程往这个ThreadLocal中读写是线程隔离,互相之间不会影响的。它提供了一种将可变数据通过每个线程有自己的独立副本从而实现线程封闭的机制。

ThreadLocal:线程私有

8.ReentrantLock:

ReentrantLock为常用类,它是一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

关键点:可重入锁,通过AQS实现

9.ReadWriteLock:

ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有writer,读取锁可以由多个reader 线程同时保持。写入锁是独占的。ReadWriteLock 读取操作通常不会改变共享资源,但执行写入操作时,必须独占方式来获取锁。对于读取操作占多数的数据结构。ReadWriteLock 能提供比独占锁更高的并发性。而对于只读的数据结构,其中包含的不变性可以完全不需要考虑加锁操作。

关键点:读写锁,共享锁,排他锁

10.CAS:

CAS (Compare-And-Swap)是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。

CAS 是一种无锁的非阻塞算法的实现。

CAS 包含了3 个操作数: (1)需要读写的内存值V (2)进行比较的值A (3)拟写入的新值B

当且仅当V 的值等于A 时,CAS 通过原子方式用新值B 来更新V 的值,否则不会执行任何操作。

关键点:保证原子性操作,可参考i++线程不安全进行入手

11.AQS:

AbstractQueuedSynchonizer为抽象类,其为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。此类的设计目标是成为依靠单个原子 int 值来表示状态的大多数同步器的一个有用基础。 个人觉得掌握了此类(一定要跟一跟源码),大多其他并发API原理都很容易上手,所以这个是重中之重。

关键点:一定要看源码看源码看源码。队列,state等等。ReentrantLock使用了它做同步状态判断,CountDownLatch使用它做次数判断,等等

12.ThreadPoolExecutor:

FixedThreadPool :

定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。适用于负载较重的服务器,使用了无界队列

SingleThreadExecutor:

单线程化的线程池,它只会用唯一的工作线程来执行任务,使用了无界队列

CachedThreadPool :

可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。(线程最大并发数不可控制),适用于执行很多短期异步任务的程序。

WorkStealingPool(JDK7以后):

基于ForkJoinPool实现

ScheduledThreadPoolExecutor:

定期执行周期任务,Timer不建议使用了。

关键点:线程池作用,每个参数,4种拒绝策略及自定义策略。最好自己先实现一个线程池,掌握上面几种jdk提供的线程池

合理设置线程池大小:

计算密集型:线程数适当小一点,建议:机器的Cpu核心数+1

IO密集型:线程数适当大一点,建议:机器的Cpu核心数*2

关键点:线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程

13.CompletionService:

如果向Executor提交了一组计算任务,并且希望在计算完成后获得结果,那么可以保留与每个任务关联的Future,然后反复使用get方法,同时 将参数timeout指定为0,从而通过轮询来判断任务是否完成。这种方法虽然可行,但却有些繁琐。幸运的是,还有一种更好的方法:完成服务 CompletionService。

关键点:异步获取并行任务执行结果,先完成的先被取出,减少了不必要的等待时间(因为任务执行后才放入queue)

14.ConcurrentHashMap:

在多线程环境下,操作HashMap会导致各种各样的线程安全问题,比如在HashMap扩容重哈希时出现的死循环问题,脏读问题等。HashMap的这一缺点往往会造成诸多不便,虽然在并发场景下HashTable和由同步包装器包装的HashMap(Collections.synchronizedMap(Map m) )可以代替HashMap,但是它们都是通过使用一个全局的锁来同步不同线程间的并发访问,因此会带来不可忽视的性能问题。庆幸的是,JDK为我们解决了这个问题,它为HashMap提供了一个线程安全的高效版本 —— ConcurrentHashMap。在ConcurrentHashMap中,无论是读操作还是写操作都能保证很高的性能:在进行读操作时(几乎)不需要加锁,而在写操作时通过锁分段技术只对所操作的段加锁而不影响客户端对其它段的访问。

关键点:1,先把hashmap的数据结构与并发下不安全问题弄懂,扩容时并发导致死循环(node节点变成成环形链表,在get的时候走不出来)2,分段锁(可以理解多个hashtable),并没有完全解决锁竞争问题,通过降低锁的粒度来提升性能。

以上内容强烈建议阅读者亲自去阅读源代码理解。

你可能感兴趣的:(Java并发包,java并发包,java面试重点,java锁,java基础)