Java 并发编程
要点总结
- Java 并发基础:了解并发的概念、原理、优势和挑战,掌握线程的创建、启动、状态转换和终止,理解并发关键字(volatile,final,synchronized)的作用和使用场景。
- J.U.C框架:学习 Java 提供的并发工具类,包括 Lock 框架(ReentrantLock,ReadWriteLock等)、Tools 类(CountDownLatch,CyclicBarrier等)、Collections 类(ConcurrentHashMap,CopyOnWriteArrayList等)、**Atomic 类(AtomicInteger,AtomicReference等)**和 Executors 类(ThreadPoolExecutor,ScheduledExecutorService等)。
- 并发问题与优化:掌握常见的并发问题(死锁、活锁、饥饿、内存可见性、竞态条件等)的原因和解决方法,学习如何使用线程安全策略(不可变性、同步、锁分离等)、设计模式(单例模式、生产者消费者模式等)和测试工具(JMeter,VisualVM等)来优化并发程序。
常见的并发原因和解决方法
并发工具类
- 常用的并发工具类
CountDownLatch
CountDownLatch 类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他3个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。CyclicBarrier
(回环栅栏) CyclicBarrier它的作用就是会让所有线程都等待完成后才会继续下一步行动
。 CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。Semaphore
(信号量) Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量
(允许自定义多少线程同时访问)。就这一点而言,单纯的synchronized 关键字是实现不了的。
并发队列
什么是并发队列
- 消息队列很多人知道:消息队列是分布式系统中重要的组件,是系统与系统直接的通信
- 并发队列是什么:并发队列多个线程以有次序共享数据的重要组件
并发队列和并发集合的区别:
- 队列遵循“先进先出”的规则,可以想象成排队检票,队列一般用来解决大数据量采集处理和显示的。
- 并发集合就是在多个线程中共享数据的
怎么判断并发队列是阻塞队列还是非阻塞队列 在并发队列上JDK提供了Queue接口,一个是以Queue接口下的BlockingQueue接口为代表的阻塞队列,另一个是高性能(无堵塞)队列。
阻塞队列和非阻塞队列区别
- 当队列阻塞队列为空的时,从队列中获取元素的操作将会被阻塞。
- 或者当阻塞队列是满时,往队列里添加元素的操作会被阻塞。
- 或者试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。
- 试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来
常用并发列队的介绍
- 非堵塞队列:
- ArrayDeque, (数组双端队列) ArrayDeque (非堵塞队列)是JDK容器中的一个双端队列实现,内部使用
数组进行元素存储
,不允许存储null值,可以高效的进行元素查找和尾部插入取出,是用作队列、双端队列、栈的绝佳选择,性能比LinkedList还要好。 - PriorityQueue, (优先级队列) PriorityQueue (非堵塞队列) 一个
基于优先级的无界优先级队列
。优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。该队列不允许使用 null 元素也不允许插入不可比较的对象 - ConcurrentLinkedQueue, (基于链表的并发队列) ConcurrentLinkedQueue (非堵塞队列): 是一个适用于
高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能。
ConcurrentLinkedQueue的性能要好于BlockingQueue接口,它是一个基于链接节点的无界线程安全队列。该队列的元素遵循先进先出的原则。该队列不允许null元素。
- ArrayDeque, (数组双端队列) ArrayDeque (非堵塞队列)是JDK容器中的一个双端队列实现,内部使用
- 堵塞队列:
- DelayQueue, (基于
时间优先级
的队列,延期阻塞队列) DelayQueue是一个没有边界BlockingQueue实现,加入其中的元素必需实现Delayed接口。当生产者线程调用put之类的方法加入元素时,会触发Delayed接口中的compareTo方法进行排序,也就是说队列中元素的顺序是按到期时间排序的,而非它们进入队列的顺序。排在队列头部的元素是最早到期的,越往后到期时间赿晚。 - ArrayBlockingQueue, (
基于数组的并发阻塞队列
) ArrayBlockingQueue是一个有边界的阻塞队列,它的内部实现是一个数组。有边界的意思是它的容量是有限的,我们必须在其初始化的时候指定它的容量大小,容量大小一旦指定就不可改变。ArrayBlockingQueue是以先进先出的方式存储数据 - LinkedBlockingQueue, (
基于链表的FIFO阻塞队列
) LinkedBlockingQueue阻塞队列大小的配置是可选的,如果我们初始化时指定一个大小,它就是有边界的,如果不指定,它就是无边界的。说是无边界,其实是采用了默认大小为Integer.MAX_VALUE的容量 。它的内部实现是一个链表。 - LinkedBlockingDeque, (
基于链表的FIFO双端阻塞队列
) LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列,即可以从队列的两端插入和移除元素
。双向队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。相比于其他阻塞队列,LinkedBlockingDeque多了addFirst、addLast、peekFirst、peekLast等方法,以first结尾的方法,表示插入、获取获移除双端队列的第一个元素。以last结尾的方法,表示插入、获取获移除双端队列的最后一个元素。LinkedBlockingDeque是可选容量的,在初始化时可以设置容量防止其过度膨胀,如果不设置,默认容量大小为Integer.MAX_VALUE。 - PriorityBlockingQueue, (
带优先级的无界阻塞队列
) priorityBlockingQueue是一个无界队列,它没有限制,在内存允许的情况下可以无限添加元素;它又是具有优先级的队列,是通过构造函数传入的对象来判断,传入的对象必须实现comparable接口。 - SynchronousQueue (
并发同步阻塞队列
) SynchronousQueue是一个内部只能包含一个元素的队列。插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。同样,如果线程尝试获取元素并且当前不存在任何元素,则该线程将被阻塞,直到线程将元素插入队列。将这个类称为队列有点夸大其词。这更像是一个点。
- DelayQueue, (基于
- 非堵塞队列:
并发队列的常用方法 不管是那种列队,是那个类,当是他们使用的方法都是差不多的
方法 | 描述 |
---|---|
add() | 在不超出队列长度的情况下插入元素,可以立即执行,成功返回true,如果队列满了就抛出异常。 |
offer() | 在不超出队列长度的情况下插入元素的时候则可以立即在队列的尾部插入指定元素,成功时返回true,如果此队列已满,则返回false。 |
put() | 插入元素的时候,如果队列满了就进行等待,直到队列可用。 |
take() | 从队列中获取值,如果队列中没有值,线程会一直阻塞,直到队列中有值,并且该方法取得了该值。 |
poll(long timeout,TimeUnit unit) | 在给定的时间里,从队列中获取值,如果没有取到会抛出异常。 |
remainingCapacity() | 获取队列中剩余的空间。 |
remove(Object o) | 从队列中移除指定的值。 |
contains(Object o) | 判断队列中是否拥有该值。 |
drainTo(Collectionc) | 将队列中值,全部移除,并发设置到给定的集合中。 |