乐观锁和悲观锁的介绍以及乐观锁常见实现方式可以阅读笔者写的这篇文章:乐观锁和悲观锁详解。
这篇文章主要介绍 :Java 中 CAS 的实现以及 CAS 存在的一些问题。
Java 中 CAS 是如何实现的?
在 Java 中,实现 CAS(Compare-And-Swap, 比较并交换)操作的一个关键类是Unsafe
。
乐观锁和悲观锁的介绍以及乐观锁常见实现方式可以阅读笔者写的这篇文章:乐观锁和悲观锁详解。
这篇文章主要介绍 :Java 中 CAS 的实现以及 CAS 存在的一些问题。
在 Java 中,实现 CAS(Compare-And-Swap, 比较并交换)操作的一个关键类是Unsafe
。
如果将悲观锁(Pessimistic Lock)和乐观锁(PessimisticLock 或 OptimisticLock)对应到现实生活中来。悲观锁有点像是一位比较悲观(也可以说是未雨绸缪)的人,总是会假设最坏的情况,避免出现问题。乐观锁有点像是一位比较乐观的人,总是会假设最好的情况,在要出现问题之前快速解决问题。
悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说,共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。
JMM(Java 内存模型)主要定义了对于一个共享变量,当另一个线程对这个共享变量执行写操作后,这个线程对这个共享变量的可见性。
要想理解透彻 JMM(Java 内存模型),我们先要从 CPU 缓存模型和指令重排序 说起!
为什么要弄一个 CPU 高速缓存呢? 类比我们开发网站后台系统使用的缓存(比如 Redis)是为了解决程序处理速度和访问常规关系型数据库速度不对等的问题。 CPU 缓存则是为了解决 CPU 处理速度和内存处理速度不对等的问题。
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢?
AQS 的全称为 AbstractQueuedSynchronizer
,翻译过来的意思就是抽象队列同步器。这个类在 java.util.concurrent.locks
包下面。
AQS 就是一个抽象类,主要用来构建锁和同步器。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
}
Atomic
翻译成中文是“原子”的意思。在化学上,原子是构成物质的最小单位,在化学反应中不可分割。在编程中,Atomic
指的是一个操作具有原子性,即该操作不可分割、不可中断。即使在多个线程同时执行时,该操作要么全部执行完成,要么不执行,不会被其他线程看到部分完成的状态。
原子类简单来说就是具有原子性操作特征的类。
java.util.concurrent.atomic
包中的 Atomic
原子类提供了一种线程安全的方式来操作单个变量。
JDK 提供的这些容器大部分在 java.util.concurrent
包中。
ConcurrentHashMap
: 线程安全的 HashMap
CopyOnWriteArrayList
: 线程安全的 List
,在读多写少的场合性能非常好,远远好于 Vector
。ConcurrentLinkedQueue
: 高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList
,这是一个非阻塞队列。BlockingQueue
: 这是一个接口,JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。ConcurrentSkipListMap
: 跳表的实现。这是一个 Map,使用跳表的数据结构进行快速查找。简单总结一下我了解的使用线程池的时候应该注意的东西,网上似乎还没有专门写这方面的文章。
线程池必须手动通过 ThreadPoolExecutor
的构造函数来声明,避免使用Executors
类创建线程池,会有 OOM 风险。
Executors
返回线程池对象的弊端如下(后文会详细介绍到):
FixedThreadPool
和 SingleThreadExecutor
:使用的是有界阻塞队列 LinkedBlockingQueue
,任务队列的默认长度和最大长度为 Integer.MAX_VALUE
,可能堆积大量的请求,从而导致 OOM。CachedThreadPool
:使用的是同步队列 SynchronousQueue
,允许创建的线程数量为 Integer.MAX_VALUE
,可能会创建大量线程,从而导致 OOM。ScheduledThreadPool
和 SingleThreadScheduledExecutor
: 使用的无界的延迟阻塞队列DelayedWorkQueue
,任务队列最大长度为 Integer.MAX_VALUE
,可能堆积大量的请求,从而导致 OOM。池化技术想必大家已经屡见不鲜了,线程池、数据库连接池、HTTP 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。
这篇文章我会详细介绍一下线程池的基本概念以及核心原理。
顾名思义,线程池就是管理一系列线程的资源池,其提供了一种限制和管理线程资源的方式。每个线程池还维护一些基本统计信息,例如已完成任务的数量。
这里借用《Java 并发编程的艺术》书中的部分内容来总结一下使用线程池的好处: