[Java多线程]-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)
前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java。大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填一次。
思路:大概看了线程相关的一些知识,对线程的运行机制,同步机制,以及整个系统都做一个全面的了解。在深入每一个部分去看一下线程及其相关包的源码做深入了解。
目标:线程,并发包(线程池,并发的数据结构,锁,原子类)。
通过一些资料的查看最终把目标定位在线程和并发包上,线程是核心,并发包是辅助工具,用于多线程运行时的并发问题。其实这样看来多线程并没有很多的东西,支持并发的数据结构用于保证数据的安全性,各种锁机制用来保证类,对象,方法,属性的并发安全。它的难点主要是在运用上,各种锁机制的运用,会给系统带来负担,也会给程序性能带来影响,但是同时又要保证数据的同步。锁机制使用的强度和位置,直接决定了并发系统的好坏。
java中文API:http://www.javaweb.cc/help/JavaAPI1.6/overview-summary.html,下面具体看一下这些包里面有哪些东西。
主要涉及到下面几个包:
线程相关:java.lang下面的几个类
接口摘要 | |
Runnable | Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。 |
类摘要 |
---|
Thread | 线程 是程序中的执行线程。 |
ThreadGroup | 线程组表示一个线程的集合。 |
ThreadLocal<T> | 该类提供了线程局部 (thread-local) 变量。 |
并发包相关:
1. Java.util.concurrent包
接口摘要 | |
BlockingDeque<E> | 支持两个附加操作的 Queue ,这两个操作是:获取元素时等待双端队列变为非空;存储元素时等待双端队列中的空间变得可用。 |
BlockingQueue<E> | 支持两个附加操作的 Queue ,这两个操作是:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。 |
Callable<V> | 返回结果并且可能抛出异常的任务。 |
CompletionService<V> | 将生产新的异步任务与使用已完成任务的结果分离开来的服务。 |
ConcurrentMap<K,V> | 提供其他原子 putIfAbsent、remove、replace 方法的 Map 。 |
ConcurrentNavigableMap<K,V> | 支持 NavigableMap 操作,且以递归方式支持其可导航子映射的 ConcurrentMap 。 |
Delayed | 一种混合风格的接口,用来标记那些应该在给定延迟时间之后执行的对象。 |
Executor | 执行已提交的 Runnable 任务的对象。 |
ExecutorService | Executor 提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 |
Future<V> | Future 表示异步计算的结果。 |
RejectedExecutionHandler | 无法由 ThreadPoolExecutor 执行的任务的处理程序。 |
RunnableFuture<V> | 作为 Runnable 的 Future 。 |
RunnableScheduledFuture<V> | 作为 Runnable 的 ScheduledFuture 。 |
ScheduledExecutorService | 一个 ExecutorService ,可安排在给定的延迟后运行或定期执行的命令。 |
ScheduledFuture<V> | 一个延迟的、结果可接受的操作,可将其取消。 |
ThreadFactory | 根据需要创建新线程的对象。 |
枚举摘要 | |
TimeUnit | TimeUnit 表示给定单元粒度的时间段,它提供在这些单元中进行跨单元转换和执行计时及延迟操作的实用工具方法。 |
2. java.util.concurrent.locks包
接口摘要 | |
Condition | Condition 将 Object 监视器方法(wait 、notify 和 notifyAll )分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。 |
Lock | Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。 |
ReadWriteLock | ReadWriteLock 维护了一对相关的锁 ,一个用于只读操作,另一个用于写入操作。 |
类摘要 | |
AbstractOwnableSynchronizer | 可以由线程以独占方式拥有的同步器。 |
AbstractQueuedLongSynchronizer | 以 long 形式维护同步状态的一个 AbstractQueuedSynchronizer 版本。 |
AbstractQueuedSynchronizer | 为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。 |
LockSupport | 用来创建锁和其他同步类的基本线程阻塞原语。 |
ReentrantLock | 一个可重入的互斥锁 Lock ,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。 |
ReentrantReadWriteLock | 支持与 ReentrantLock 类似语义的 ReadWriteLock 实现。 |
ReentrantReadWriteLock.ReadLock | ReentrantReadWriteLock.readLock() 方法返回的锁。 |
ReentrantReadWriteLock.WriteLock | ReentrantReadWriteLock.writeLock() 方法返回的锁。 |
3. java.util.conturrent.atomic
类摘要 | |
AtomicBoolean | 可以用原子方式更新的 boolean 值。 |
AtomicInteger | 可以用原子方式更新的 int 值。 |
AtomicIntegerArray | 可以用原子方式更新其元素的 int 数组。 |
AtomicIntegerFieldUpdater<T> | 基于反射的实用工具,可以对指定类的指定 volatile int 字段进行原子更新。 |
AtomicLong | 可以用原子方式更新的 long 值。 |
AtomicLongArray | 可以用原子方式更新其元素的 long 数组。 |
AtomicLongFieldUpdater<T> | 基于反射的实用工具,可以对指定类的指定 volatile long 字段进行原子更新。 |
AtomicMarkableReference<V> | AtomicMarkableReference 维护带有标记位的对象引用,可以原子方式对其进行更新。 |
AtomicReference<V> | 可以用原子方式更新的对象引用。 |
AtomicReferenceArray<E> | 可以用原子方式更新其元素的对象引用数组。 |
AtomicReferenceFieldUpdater<T,V> | 基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新。 |
AtomicStampedReference<V> | AtomicStampedReference 维护带有整数“标志”的对象引用,可以用原子方式对其进行更新。 |
对每个包里的部分类进行结构解析:
1. Java.util.concurrent:这个包里,主要常用到的是数据结构Queue,MAP,List,和线程池
*&*Queue队列相关的类图:
*&*List和Set相关的类图:List和Set在并发包中的实现类有copyOwriteArrayList,CopyOnWriteArraySet,ConcurrentSkipListSet,三个类
*&*Map相关的类图:并发包中与Map相关的包括ConcurrentHashMap,ConcurrentSkipListMap两个类
这些都是和数据结构相关的,其中蓝色的部分表示的是并发包的内容,灰色部分表示其他包(大部分是util)中的内容。其中省略了一部分内容类和接口,因为很多不常用,而且用的时候一部分也不是作为一个数据结构来用的。这也不是这部分关注的重点。我们主要关注并发包相关的内容。这三张类图,应该可以让大家对并发包的数据结构有一个大致的了解,并发包还有一个内容就是线程池。
*&*线程池类图:线程池重最终有两个实现类ThreadPoolExeutor和SheduleThreadPoolExeutor.但是我们一般不直接去实现这两个类去创建一个线程池,我们通常用Exeutors这个类来创建一个线程池,这个类中把线程池的创建和管理进行了封装,我们只需要运用这个类就可以创建一个线程池并进行管理。另外一个接口ThreadFactory主要是用来创建线程的,实现这个接口我们就拥有了一个线程工厂,创建线程更方便。
2. Java.util.concurrent.Locks:(以下说明摘自API)为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。该框架允许更灵活地使用锁和条件,但以更难用的语法为代价
Lock
接口支持那些语义不同(重入、公平等)的锁规则,可以在非阻塞式结构的上下文(包括 hand-over-hand 和锁重排算法)中使用这些规则。主要的实现是 ReentrantLock。
ReadWriteLock
接口以类似方式定义了一些读取者可以共享而写入者独占的锁。此包只提供了一个实现,即 ReentrantReadWriteLock
,因为它适用于大部分的标准用法上下文。但程序员可以创建自己的、适用于非标准要求的实现。
Condition
接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。
AbstractQueuedSynchronizer
类是一个非常有用的超类,可用来定义锁以及依赖于排队阻塞线程的其他同步器。
AbstractQueuedLongSynchronizer
类提供相同的功能但扩展了对同步状态的 64 位的支持。
前面两者都扩展了类 AbstractOwnableSynchronizer
(一个帮助记录当前保持独占同步的线程的简单类)。LockSupport
类提供了更低级别的阻塞和解除阻塞支持,这对那些实现自己的定制锁类的开发人员很有用。
3. Java.util.concurrent.atomic:(以下内容摘自API)原子类这部分没有很复杂的类关系,主要是对基础的int,long,bolean变量,以及相关的数组和对象引用,提供了原子访问和更新的类。
原子访问和更新的内存效果一般遵循以下可变规则,正如 The Java Language Specification, Third Edition (17.4 Memory Model) 中的声明:
-
- get 具有读取 volatile 变量的内存效果。
- set 具有写入(分配)volatile 变量的内存效果。
- 除了允许使用后续(但不是以前的)内存操作,其自身不施加带有普通的非 volatile 写入的重新排序约束,lazySet 具有写入(分配)volatile 变量的内存效果。在其他使用上下文中,当为 null 时(为了垃圾回收),lazySet 可以应用不会再次访问的引用。
- weakCompareAndSet 以原子方式读取和有条件地写入变量但不 创建任何 happen-before 排序,因此不提供与除 weakCompareAndSet 目标外任何变量以前或后续读取或写入操作有关的任何保证。
- compareAndSet 和所有其他的读取和更新操作(如 getAndIncrement)都有读取和写入 volatile 变量的内存效果。
除了包含表示单个值的类之外,此包还包含 Updater 类,该类可用于获取任意选定类的任意选定 volatile 字段上的 compareAndSet 操作
类 AtomicBoolean
、AtomicInteger
、AtomicLong
和 AtomicReference
的实例各自提供对相应类型单个变量的访问和更新。
类AtomicReferenceFieldUpdater
、AtomicIntegerFieldUpdater
和 AtomicLongFieldUpdater
是基于反射的实用工具,可以提供对关联字段类型的访问。它们主要用于原子数据结构中,该结构中同一节点(例如,树节点的链接)的几个 volatile 字段都独立受原子更新控制。这些类在如何以及何时使用原子更新方面具有更大的灵活性,但相应的弊端是基于映射的设置较为拙笨、使用不太方便,而且在保证方面也较差。
AtomicIntegerArray
、AtomicLongArray
和 AtomicReferenceArray
类进一步扩展了原子操作,对这些类型的数组提供了支持。这些类在为其数组元素提供 volatile 访问语义方面也引人注目,这对于普通数组来说是不受支持的。
AtomicMarkableReference
类将单个布尔值与引用关联起来。例如,可以在数据结构内部使用此位,这意味着引用的对象在逻辑上已被删除。
AtomicStampedReference
类将整数值与引用关联起来。例如,这可用于表示与更新系列对应的版本号。
花了两天时间看着一段的API,稍微整理了一下,大部分内容还是来自API,但是我个人觉得这种东西听起来很难入门,其实一大部分原因是我们没用从总体上去把握它,从最开始的接口和类的清单表中可能很多东西没有接触过,其实那都不是问题,当看到类图的时候清楚地看到他们之间的父子关系,其实最终需要我们去掌握的类不是很多。
我个人认为,数据结构(queue,Map,List)这部分是最容易掌握的,因为它的内部机制已经实现了,我们只需要知道在什么场景需要使用它,什么时候的数据需要用这种并发安全的数据结构在经过不断地使用就能掌握(深入理解当我没说,哈哈)。线程池这部分也很容易掌握,我们只要熟悉Exeutors类,掌握里面的方法,就足够熟练的运用对线程池。然后是原子类,原子类访问的变量是volited修饰的,原子类实际上就是对数据进行了操作的原子性(操作是一个不可分割的整体,ex:updape包括读取数据,修改数据,写回数据三个步骤,普通的操作就不能保证update操作的原子性)一致性的封装,保证了对这些数据操作的时候是线程安全的。最难的应该是在锁上,加锁会造成线程阻塞,高并发状态下是否该用锁,和在什么地方用锁,锁的粒度很关键,本来只需要在数据上加锁,而我们却加在了方法上,或者对象上,那对性能的影响可能不是一星半点。各种锁的用法和技巧,带来的差异,弊端都需要清楚的知道。
希望能给像我一样还未入门的朋友带来一点帮助,现在已经基本了解了框架,后面就是对这些部分的深入探索,从具体的应用中看差异,深入源码和底层的VM实现看原理,我相信一步步去做一定会逐渐上手。
❤如果这篇文章对你有一点点的帮助请给一份推荐! 谢谢!你们的鼓励是我继续前进的动力。更多内容欢迎访问我的个人博客
❤本博客只适用于研究学习为目的,大多为学习笔记,如有错误欢迎指正,如有误导概不负责(本人尽力保证90%的验证和10%的猜想)。