一、锁
1)、乐观锁、悲观锁、可重入锁、自旋锁、独占锁、共享锁、读写锁、非公平锁、公平锁、分段锁
- 乐观锁:在更新时判断别人有没有更新这个数据,采取写时先读出当前版本号,然后加锁操作。如CAS
- 悲观锁:每次读写数据的时候都会上锁。
- 可重入锁(递归锁):同一线程外层函数获取锁之后,内层递归函数仍然有获取该锁的代码,但不受影响。
- 自旋锁:若持有锁线程能在很短时间内释放锁资源,则等待竞争锁的线程无需在内核态和用户态之间切换进入阻塞挂起状态。它们只需要自旋(等待)一下,等持有锁线程释放锁后即可立即获取锁,避免用户线程和内核切换的消耗。
- 独占锁:每次只能由一个线程持有锁。
- 共享锁:允许多个线程同时获取锁,并发访问共享资源。如ReentrantReadWriteLock
- 读写锁:为了提高性能,在读的地方使用读锁,在写的地方使用写锁。读读不互斥、读写互斥、写写互斥。如ReentrantReadWriteLock
- 公平锁:锁的分配机制是公平的,先提出获取请求的线程会先被分配到锁
- 非公平锁:JVM按随机、就近原则分配锁的机制
- 分段锁:一种思想。如ConcurrentHashMap
2)、JVM级别:volatile、synchronized
volatile : 轻量级锁,保证可见性、有序性
synchronized :悲观锁、可重入锁、自旋锁、独占锁、非公平锁。只能升不能降(无锁、偏向锁、轻量级锁、重量级锁)。通过监视器监视对象头中对象使用。采用悲观并发策略
- Wait Set:存放调用wait方法被阻塞的线程
- Contention List:竞争队列,所有请求锁的线程首先存放在这个竞争队列中
- Entry List:存放Contention List中有资成为候选资源的线程
- OnDeck:任意时刻,最多称为候选资源的线程被移动到Entry List
- Owner:当前已经获取到所资源的线程
- !Owner:当前释放锁的线程
- 偏向锁:只有一个线程执行同步块时进一步提高性能,消除线程重入锁(CAS)的开销
- 轻量级锁:适用于线程交替执行同步块的情况,锁的获取和释放依赖多次CAS原子操作
- 重量级锁:适用于并发执行,效率较低。通过对象内部的一个监视器锁(monitor)实现的,依赖于操作系统Mutex Lock锁实现的锁
3)、API级别:ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch、CyclcBarrier、AtomicInteger等
AtomicInteger:提供原子操作的Integer类。
AQS:定义了一套多线程访问共享资源的同步器框架。具体资源的获取/释放由各自的自定义同步器去实现。AQS内部定义了volatile变量的state、FIFO的CLH队列。
独占同步器:state初始化为0,标识未锁定状态,A线程lock时,会调用tryAcquire()独占该锁并将state+1。此后其他线程再调用tryAcquire()时就会失败,直到A线程unlock()到state=0为止(即释放锁)。A线程自己可以重新获取锁(state累加),即可重入概念
ReentrantLock:可重入锁、自旋锁,独占锁、非公平锁(默认)、公平锁,可响应中断锁、可轮询锁请求、定时锁。采用乐观并发策略
- ReentrantLock的锁资源以volatile的state状态描述,利用CAS实现对锁资源的抢占,并通过一个CLH队列阻塞所有竞争线程,在后续则逐个唤醒等待中的竞争线程。
- ReentrantLock继承AQS完全从代码层面实现了java同步机制,AQS中的Condition配合ReentrantLock使用,实现了wait/notify功能。
独占和共享同步器
ReentrantReadWriteLock:读写锁,ReentrantReadWriteLock.readLock()、ReentrantReadWriteLock.writeLock()
共享同步器:
Semaphore:基于计数的信号量。多个线程竞争获取许可信号,做完自己申请后归还,超过阈值后,线程申请许可信号将会被阻塞。如acquire()、release()
CountDownLatch:线程计数器。闭锁需要countDown一次即state会CAS减1,待state=0,会unpark()主调用线程,让主线程通过
CyclcBarrier:回环栅栏。让一组线程await等待至某个状态之后再全部执行。
4)、集合
ConcurrentHashMap:分段锁。数据结构由segment[] 和 HashEntry[] 组成。每一个segment继承ReentrantLock。默认情况下并发度16。一个segment里面含有一个HashEntry[],HashEntry用于存储键值对数据,是一个链表结构的元素。
LinkedBlockingQueue:两个独立锁ReentrantLock,在链表的消费端和生产端分别采用独立的锁控制数据同步。
CopyOnWriteArrayList:当往一个容器添加元素时,先将当前容器复制一个新的出来,将元素添加到新的容器里,最后再将当前容器的引用指向新容器。
HashTable:使用Synchronized锁保证put方法在多线程下的数据安全。
二、多线程
线程状态:
- 创建:生成线程对象
- 就绪:调用start方法
- 运行:开始运行run函数
- 阻塞:调用sleep、suspend、wait等方法
- 死亡:run执行结束 or stop方法后
创建线程方式:
- 继承Thread类
- 实现Runnable接口,无返回值
- 通过Callable接口和Future、FutureTask,有返回值
- 通过线程池
线程基本方法:
- wait:线程等待,释放锁资源
- sleep:线程睡眠,不释放锁资源
- yield:线程让步,让出CPU执行片
- interrupt:线程中断,改变线程内部的一个中断标识符,不会因此而改变状态。
- join:等待其他线程终止
- notify:线程唤醒:唤醒在此对象监视器上等待的单个线程
- notifyAll:唤醒全部线程
线程池状态:
- RUNNING:能接受新任务,处理已添加任务
- SHUTDOWN:不接受新任务,能处理已添加任务
- STOP:不接受新任务,不处理已添加任务,中断正在处理任务
- TIDYING:所有任务已终止,ctl记录数量为0
- TERMINATED:线程池彻底终止
创建线程池
- newFixThreadPool:固定长度线程池
- newCachedThreadPool:缓存线程池
- newSingleThreadExecutor:单线程的线程池
- newScheduledThreadPool:调度线程池,以延时or定时执行任务
- 以上线程池均调用ThreadPoolExecutor方法,参数如下:
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数
- keepAliveTime:超过corePoolSize的空闲线程存活时间
- unit:keepAliveTime的单位
- workQueue:任务队列,被提交尚未执行的任务
- threadFactory:线程工厂,如默认Executors.defaultThreadFactory()
- handle:拒绝策略
- 线程池工作过程:刚创建时,无线程。当调用execute()添加一个任务时,会做如下判断:
- 正在运行的线程数量是否小于corePoolSize,若是,则马上创建线程运行这个任务
- 否则判断阻塞队列是否已满,若否,将任务放入阻塞队列中
- 否则判断正在运行的线程数量是否小于maximumPoolSize,若是,则马上创建非核心线程执行任务
- 否则,按照拒绝策略进行处理
- 拒绝策略:均实现了RejectedExecutionHandle接口
- AbortPolicy:直接抛出异常,阻止系统正常运行
- CallerRunsPolicy:直接在调用者线程中运行
- DiscardOldestPolicy:丢弃最老的一个请求
- DiscardPolicy:丢弃无法处理的任务
- 阻塞队列
- ArrayBlockQueue:由数组结构组成的有界阻塞队列。公平、非公平
- LinkedBlockQueue:由链表结构组成的有界阻塞队列,默认大小Integer.MAX_VALUE。两个独立锁提高并发
- PriorityBlockQueue:支持优先级排序的无界阻塞列表。compareTo排序实现优先
- DelayQueue:使用优先级队列实现的无界阻塞列表。缓存失效、定时任务
- SynchronousQueue:不存储元素的阻塞队列。不存储数据,可用于传递数据
- LinkedTransferQueue:由链表结构组成的无界阻塞队列。
- LinkedBlockingDeque:由链表结构组成的双向阻塞队列。可以从队列两端插入和移除元素