当前位置: 首页 > 后端技术 > Java

Java并发编程系列之三JUC概述

时间:2023-04-01 13:15:19 Java

上一篇介绍了锁的概念来解决多线程中的同步问题。上一篇介绍了Synchronized关键字lock。本文介绍更多的轻量级锁。锁定接口及JUC相关知识。本文并没有试图把JUC框架的所有内容都解释清楚,而是站在一定的高度来理解Juc包的设计和实现。[TOC]1.LOCK锁概述另一种实现同步的方式是Lock锁。Lock锁是一个接口,它的所有实现类是:ReentrantLock(可重入锁)ReentrantReadWriteLock.ReadLock(可重入读写锁中的读锁)ReentrantReadWriteLock.WriteLock(可重入读写锁中的写锁)与synchronized不同的是,有使用LOCK锁的六大区别。1、synchronized是Java内置的关键字。锁是一个接口。2、synchronized不能判断是否获取锁,而Lock可以判断是否获取锁。3.synchronized可以自动释放锁,而Lock必须手动释放锁,不释放会造成死锁4同一个锁对象,线程A同步获取后,线程B只能等待,造成阻塞,Lock不会等待5synchronized可重入锁,不可中断,非公平锁,Lock可重入锁,可判断,非公平锁(可设置)6synchronized适合少量同步代码,Lock适合大量同步代码Lock接口位于java.util.concurrent.locks包下,父包包下有两个。java.util.concurrent:该包主要包括并发相关的接口和类、阻塞队列、线程池等,其中包含59个类或接口java.util.concurrent.atomic:该包主要包括原子操作类,如常用的AtomicInteger、AtomicBoolean、AtomicIntegerArry等,包含18个类或接口。父包java.util.concurrent涉及Java多线程最重要的部分——JUC编程。2.JUC概述JUC是java.util.concurrenttoolkit的缩写。这是一个用于处理线程的工具包。JDK1.5开始出现。在这个包中,加入了并发编程中常用的工具类。用于定义类似于线程的自定义子系统,包括线程池、异步IO和轻量级任务框架;它还提供了专为在多线程上下文中使用而设计的Collection实现;下图展示了JUC涉及的所有知识点。JUC应包括五个部分。1.Lockframework①Interface:ConditionCondition是一种接口类型,将Object的监听方法(wait、notify、notifyAll)分解为不同的对象,从而可以为每个对象提供多个等待集(wait-set)。其中Lock代替了synchronized方法和语句的使用,Condition代替了Object监控方法的使用。您可以通过await()、signal()休眠/唤醒线程。②接口:LockLock是一种接口类型,Lock实现提供了比使用synchronized方法和语句所能获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有广泛变化的属性,并且可以支持多个相关的Condition对象。③接口:ReadWriteLockReadWriteLock是一个接口类型,维护一对相关的锁,一个用于只读操作,一个用于写操作。只要没有写入者,读锁就可以由多个读取者线程并发持有。写锁是独占的。④抽象类:AbstractOwnableSynchonizerAbstractOwnableSynchonizer是一个抽象类,一个线程可以独占的同步器。此类为创建锁和相关同步器(以及所有权概念)提供了基础。AbstractOwnableSynchronizer类本身不管理或使用此信息。但是,子类和工具可以帮助控制和监视访问并提供具有适当维护值的诊断。⑤抽象类(long):AbstractQueuedLongSynchronizerAbstractQueuedLongSynchronizer是一个抽象类,以long形式维护一个AbstractQueuedSynchronizer版本的同步状态。此类具有与AbstractQueuedSynchronizer完全相同的结构、属性和方法,但所有与状态相关的参数和结果都定义为long而不是int。此类在创建需要64位状态的多级锁和屏障等同步器时很有用。⑥核心抽象类(int):AbstractQueuedSynchonizerAbstractQueuedSynchonizer是一个抽象类,它提供了一个框架,用于实现依赖先进先出(FIFO)等待队列的阻塞锁和相关同步器(信号量、事件等)。此类旨在成为大多数依赖单个原子int值来表示状态的同步器的有用基础。⑦通用锁类:LockSupportLockSupport是一个通用类,用于创建锁和其他同步类的基本线程阻塞原语。LockSupport的作用类似于“Thread中的Thread.suspend()和Thread.resume()”。LockSupport中的park()和unpark()的作用分别是阻塞和解阻塞线程。但是park()和unpark()不会遇到“Thread.suspend和Thread.resume可能导致的死锁”的问题。⑧锁普通类:ReentrantLockReentrantLock是一个普通类,它是一个可重入互斥锁,它与使用synchronized方法和语句访问的隐式监视器锁具有相同的基本行为和语义,但更强大。⑨常用锁类:ReentrantReadWriteLockReentrantReadWriteLock是读写锁接口ReadWriteLock的实现类,包括Lock子类ReadLock和WriteLock。ReadLock是共享锁,WriteLock是排他锁。⑩常用锁类:StampedLock是java8在java.util.concurrent.locks中添加的API。StampedLock控制锁有三种模式(写、读、乐观读),一个StampedLock状态由版本和模式组成,锁获取方法返回一个数字作为票戳,由相应的锁状态表示并控制访问,数字0表示没有为访问授予写锁。读锁分为悲观锁和乐观锁。2.工具类①工具常用类:CountDownLatchCountDownLatch是一个常用的类。它是一个同步辅助类,允许一个或多个线程等待,直到一组正在其他线程中执行的操作完成。②常用类:CyclicBarrierCyclicBarrier是一个常用类,它是一个同步辅助类,可以让一组线程互相等待,直到到达一个共同的屏障点(commonbarrierpoint)。CyclicBarriers在涉及固定大小的线程集且必须不时相互等待的程序中很有用。因为这个屏障在等待线程被释放后可以重新使用,所以被称为循环屏障。)③工具常用类:PhaserPhaser是JDK7新增的同步辅助类,可以实现类似CyclicBarrier和CountDownLatch的功能,支持任务动态调整,支持分层结构,实现更高的吞吐量。④工具常用类:SemaphoreSemaphore是一个常用类,它是一个计数信号量。从概念上讲,信号量维护着一个权限集。每个acquire()都会阻塞,直到许可可用,然后在必要时获取许可。每个release()添加一个许可,可能会释放一个阻塞的获取者。然而,信号量并没有使用实际的许可证对象,而是只计算可用许可证的数量并采取相应的行动。通常用于限制可以访问某些资源(物理或逻辑)的线程数。⑤常用工具类:ExchangerExchanger是线程协作的工具类,主要用于两个线程之间的数据交换。它提供了一个同步点,两个线程可以在其中相互交换数据。两个线程通过exchange()方法交换数据。当一个线程首先执行exchange()方法时,它将等待第二个线程也执行exchange()方法。当两个线程到达同步点后,两个线程就可以交换数据了。3.Collections:并发集合并发集合的类结构关系①Queue:ArrayBlockingQueue数组支持的有界阻塞队列。此队列以FIFO(先进先出)为基础对元素进行排序。队列的头部是队列中最长的元素。队列的尾部是在队列中停留时间最短的元素。新元素插入到队尾,队列取指操作是从队头获取元素。②队列:LinkedBlockingQueue是一个基于任意范围链接节点的阻塞队列。该队列对元素进行FIFO(先进先出)排序。队列的头部是队列中最长的元素。队列的尾部是在队列中停留时间最短的元素。新元素插入队列尾部,队列获取操作获取队列头部的元素。链接队列通常比基于数组的队列具有更高的吞吐量,但可以预见在大多数并发应用程序中性能较低。③队列:LinkedBlockingDeque基于链接节点和可选范围的阻塞双端队列。④队列:ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列。该队列根据FIFO(先进先出)原则对元素进行排序。队列的头部是队列中最长的元素。队列的尾部是队列中时间最短的元素。新元素插入队列尾部,队列获取操作从队列头部获取元素。当多个线程共享对公共集合的访问时,ConcurrentLinkedQueue是一个合适的选择。此队列不允许空元素。⑤队列:ConcurrentLinkedDeque是一个双向链表实现的无界队列,同时支持FIFO和FILO操作。⑥队列:DelayQueue是一个延迟无界阻塞队列,使用Lock机制实现并发访问。队列中只允许可以“推迟”的元素,队列中的头部是第一个“过期”的元素。如果队列中没有要“过期”的元素,那么即使队列中有元素,也获取不到。⑦队列:PriorityBlockingQueue无界优先级阻塞队列,使用Lock机制实现并发访问。priorityQueue的线程安全版本不允许存储空值,依赖可比较排序,并且不允许存储不可比较的对象类型。⑧Queue:SynchronousQueue没有同步队列的容量。通过CAS实现并发访问,支持FIFO和FILO。⑨队列:LinkedTransferQueueJDK7新增,一个单向链表实现的无界阻塞队列,通过CAS实现并发访问,队列元素采用FIFO(先进先出)模式。LinkedTransferQueue可以说是ConcurrentLinkedQueue、SynchronousQueue(公平模式)和LinkedBlockingQueue的超集。它不仅集成了这些类的功能,而且提供了更高效的实现。⑩List:CopyOnWriteArrayListArrayList的线程安全变体,其中所有变量操作(add、set等)都是通过制作底层数组的新副本来实现的。这通常需要大量开销,但当遍历操作的数量大大超过可变操作的数量时,它可能比替代方案更有效。当您不能或不想进行同步遍历,但需要从并发线程中排除冲突时,它也很有用。⑩①Set:CopyOnWriteArraySet使用内部的CopyOnWriteArrayListSet进行所有操作。即所有操作都转发给CopyOnWriteArayList进行操作,可以保证线程安全。添加时,将调用addIfAbsent。由于每次add都要遍历数组,所以性能会比CopyOnWriteArrayList略低。⑩②Set:ConcurrentSkipListSet是基于ConcurrentSkipListMap的可伸缩并发NavigableSet实现。集合的元素可以根据它们的自然顺序排序,或者根据创建集合时提供的比较器排序,具体取决于所使用的构造函数。⑩③Map:ConcurrentHashMap是一个线程安全的HashMap。ConcurrentHashMap在JDK7之前是通过Lock和segment(段锁)实现的,JDK8之后改为CAS+synchronized,保证并发安全。详细分析请看:【JUC并发集合:ConcurrentHashMap详解】(),其中包括JDK7和JDK8版本的源码分析。⑩④Map:ConcurrentSkipListMap线程安全的有序哈希表(相当于线程安全的TreeMap);map可以按照key的自然顺序排序,也可以按照创建map时提供的Comparator进行排序,具体取决于使用的Construction方法。4.原子性:原子类的基本特点是在多线程环境下,当多个线程同时执行这些类的实例包含的方法时,它们是互斥的,即当一个线程进入方法并执行其中的指令,不会被其他线程打断,其他线程就像自旋锁一样,一直等待,直到方法执行完毕,JVM再从等待队列中选择另一个线程进入。这只是逻辑上的理解。其实是借助硬件相关的指令实现的,线程不会被阻塞(或者只是硬件层面的阻塞)。①基本类型:AtomicBoolean、AtomicInteger、AtomicLongAtomicBoolean、AtomicInteger、AtomicLong类似,分别为bool、integer、long原子类。②Array:AtomicIntegerArray、AtomicLongArray、BooleanArrayAtomicIntegerArray、AtomicLongArray、AtomicBooleanArray都是数组原子类。③引用:AtomicReference、AtomicMarkedReference、AtomicStampedReferenceAtomicReference、AtomicMarkedReference、AtomicStampedReference是引用相关的原子类。④FieldUpdater:AtomicLongFieldUpdater、AtomicIntegerFieldUpdater、AtomicReferenceFieldUpdaterAtomicLongFieldUpdater、AtomicIntegerFieldUpdater、AtomicReferenceFieldUpdater都是FieldUpdater原子类。5、Executors:线程池线程池类结构关系①接口:ExecutorExecutor接口提供了一种将任务提交与每个任务将如何运行的机制(包括线程使用、调度等细节)分开的方法。通常使用Executors而不是显式创建线程。②ExecutorServiceExecutorService继承自Executor接口。ExecutorService提供了一种管理终止的方法和一种生成Future的方法来跟踪一个或多个异步任务的执行状态。可以关闭ExecutorService,这将导致它停止接受新任务。关闭时,执行器最后终止,此时没有任务正在执行或等待执行,也不能提交新任务。③ScheduledExecutorServiceScheduledExecutorService继承自ExecutorService接口,可以安排命令在给定的延迟后运行或周期性执行。④AbstractExecutorServiceAbstractExecutorService继承自ExecutorService接口,提供了ExecutorService执行方法的默认实现。该类使用newTaskFor返回的RunnableFuture实现submit、invokeAny和invokeAll方法,默认情况下是本包中提供的FutureTask类。⑤FutureTaskFutureTask提供了Future的基本实现,如获取任务执行结果(get)和取消任务(cancel)。如果任务还没有完成,在获取任务执行结果时会阻塞。一旦执行完成,任务将无法重新启动或取消(除非使用runAndReset来执行计算)。FutureTask常用来封装Callable和Runnable,也可以作为任务提交到线程池执行。这个类除了是一个独立的类之外,还提供了一些功能性的功能供我们创建自定义任务类。FutureTask的线程安全由CAS保证。⑥核心:ThreadPoolExecutorThreadPoolExecutor实现了AbstractExecutorService接口,也是一个ExecutorService,它使用几个可能的池线程之一来执行每个提交的任务,通常使用Executors工厂方法配置。线程池可以解决两个截然不同的问题:由于减少了每次任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,还可以提供绑定和管理资源(包括线程)的方法。每个ThreadPoolExecutor还维护着一些基本的统计数据,比如完成的任务数。⑦核心:ScheduledThreadExecutorScheduledThreadPoolExecutor实现了ScheduledExecutorService接口,可以安排在给定的延迟后运行命令,或者周期性地执行命令。当需要多个工作线程时,或者当需要ThreadPoolExecutor的额外灵活性或功能时,此类优于Timer。)⑧核心:Fork/Join框架ForkJoinPool是JDK7新增的线程池类。Fork/Join技术是分治算法的并行实现,是一种简单高效的设计技术,可以取得很好的并行性能。目的是帮助我们更好地利用多处理器的优势,利用所有可用的计算能力来提高应用程序性能。)⑨工具类:ExecutorsExecutors是一个工具类,可以用来创建ExecutorService、ScheduledExecutorService、ThreadFactory、Callable等对象。它的用途被融入到ThreadPoolExecutor、ScheduledThreadExecutor和ForkJoinPool中。通过以上章节,不难看出JUC系统的内容也是非常庞大的。通过这篇文章,我们可以了解JUC的整个系统,我们的目的也就达到了。后面的文章将对这些类和接口进行详细的梳理和分析。

猜你喜欢