一:同步控制
1. synchronized的扩展——重入锁
同一个线程可以反复进入的锁,比synchronized同步块具有更大的灵活性。
- 创建锁
- 非公平: ReentrantLock lock = new ReentrantLock();
- 公平: ReentrantLock lock = new ReentrantLock(true);
- 使用锁:lock.lock();
- 使用可中断锁:lock.lockInterruptibly();
- 使用限时锁:lock.tryLock(时间, TimeUnit单位);
- 释放锁:lock.unlock();
- 判断是否拥有锁:lock.isHeldByCurrentThread()
(1)处理同步问题
对临界资源加锁,进行互斥同步
import java.util.concurrent.locks.ReentrantLock;
public class ReenterLockTest implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
public static int i;
@Override
public void run() {
for (int j = 0; j < 10; j++) {
lock.lock();
try {
i++;
}
finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLockTest test = new ReenterLockTest();
Thread a = new Thread(test);
Thread b = new Thread(test);
a.start();
b.start();
a.join();
b.join();
System.out.println(i);
}
}
(2)中断响应
在等待锁的时候,被等待线程可以通知其中断等待
public void run() {
for (int j = 0; j < 10; j++) {
try {
lock.lockInterruptibly();
i++;
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("被通知不用等待了");
}
finally {
if (lock.isHeldByCurrentThread())
lock.unlock();
System.out.println("线程退出");
}
}
}
(3)锁申请等待限时
- 有参数:线程等待一段时间后若锁不可用,直接放弃
- 没有参数:直接判断是否可用,不可用直接放弃
public void run() {
for (int j = 0; j < 10; j++) {
if (lock.tryLock()) {
//业务代码
try {
i++;
} finally {
if (lock.isHeldByCurrentThread())
lock.unlock();
System.out.println("线程退出");
}
}
}
}
(4)公平锁
默认非公平锁,线程倾向于再次获取已经持有的锁,高效
公平锁,每个线程都有机会获得锁,开销大
2. 重入锁的伙伴——条件
让锁在特定的时间等待和通知
- 创建:Condition condition = lock.newCondition()
- 等待
- await():获取特定的锁后,使线程等待,同时释放锁,当线程被中断也能跳出等待
- awaitUninterruptibly():线程中等待中无法响应中断
- 通知
- signal():获取特定的锁后,唤醒等待的线程,释放锁
- signalAll():获取特定的锁后,唤醒所有等待的线程,释放锁
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ReenterLockTest implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
public static int i;
@Override
public void run() {
if (lock.tryLock()) {
//业务代码
try {
i++;
System.out.println("开始等待");
condition.await();
System.out.println("被唤醒了");
} catch (InterruptedException e) {
System.out.println("我在等待时被中断了");
} finally {
if (lock.isHeldByCurrentThread())
lock.unlock();
System.out.println("线程退出");
}
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLockTest test = new ReenterLockTest();
Thread a = new Thread(test);
a.start();
//等待线程A把锁释放,否则拿不到锁
Thread.sleep(2000);
lock.lock();
condition.signal();
lock.unlock();
System.out.println(i);
}
}
3. 锁的扩展——信号量Semaphore
允许多个资源同时访问同个临界资源
- 创建:
- 指定同时访问的线程个数:Semaphore semaphore = new Semaphore(线程数)
- 指定线程个数和公平锁:Semaphore semaphore = new Semaphore(线程数,true)
- 进入临界区:
- semaphore.acquire():获取进入临界区的许可,获取失败则等待或者被中断
- semaphore.acquireUninterruptibly():尝试获取进入临界区的许可,获取失败则等待,无法响应中断
- semaphore.tryAcquire():尝试获取进入临界区的许可,获取失败直接退出
- 退出临界区:
- semaphore.release():访问资源结束后释放许可
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
static Semaphore semaphore = new Semaphore(5);
static class MyThread extends Thread{
@Override
public void run() {
try {
semaphore.acquire();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getId()+"正在执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
semaphore.release();
}
}
}
public static void main(String[] args) {
MyThread[] threads = new MyThread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new MyThread();
threads[i].start();
}
}
}
4. 读写锁—— ReadWriteLock
读写分离锁可以有效帮助减少锁竞争,提升系统性能。
允许多个线程同时读,不会造成阻塞,但是读写和写写间的操作依然需要等待和持有锁。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLock {
private static ReentrantLock myLock = new ReentrantLock();
private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private static Lock readLock = lock.readLock();
private static Lock writeLock = lock.writeLock();
static class ReadThread extends Thread{
@Override
public void run() {
// myLock.lock();
readLock.lock();
System.out.println("开始大量读操作");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
// myLock.unlock();
readLock.unlock();
}
}
}
static class WriteThread extends Thread{
@Override
public void run() {
try {
// myLock.lock();
writeLock.lock();
System.out.println("开始写操作");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
// myLock.unlock();
}
}
}
public static void main(String[] args) {
ReadThread[] readThreads = new ReadThread[10];
for (int i = 0; i < readThreads.length; i++) {
readThreads[i] = new ReadThread();
readThreads[i].start();
}
WriteThread[] writeThreads = new WriteThread[2];
for (int i = 0; i < writeThreads.length; i++) {
writeThreads[i] = new WriteThread();
writeThreads[i].start();
}
}
}
5. 倒计数器——CountDownLatch
让线程等待直到倒计数结束后再执行,每当有一个线程完成工作时计数-1,当所有线程完成工作计数为0,则等待的线程可以继续执行
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest {
private static CountDownLatch latch = new CountDownLatch(2);
static class CheckThreadA extends Thread{
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A检查完毕");
latch.countDown();
}
} static class CheckThreadB extends Thread{
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程B检查完毕");
latch.countDown();
}
}
public static void main(String[] args) throws InterruptedException {
CheckThreadA a = new CheckThreadA();
CheckThreadB b = new CheckThreadB();
a.start();
b.start();
//等待所有线程完成工作后,主线程才能继续进行
latch.await();
System.out.println("都检查好了");
}
}
6. 循环栅栏——CyclicBarrier
可以重复使用的倒计数器
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest {
//主线程需要完成的所有任务
static class BarrierThread extends Thread{
private int flag;
public BarrierThread(int flag) {
this.flag = flag;
}
@Override
public void run() {
switch (flag){
case 0:
System.out.println("第一个任务完成!");
flag = 1;
break;
case 1:
System.out.println("第二个任务完成!");
flag = 2;
break;
default:
System.out.println("任务失败!");
break;
}
}
}
//子线程需要分别完成的任务
static class MyThread extends Thread{
private String name;
private CyclicBarrier barrier;
public MyThread(CyclicBarrier barrier,String name) {
this.barrier = barrier;
this.name = name;
}
@Override
public void run() {
try {
System.out.println(name+"开始第一个工作");
Thread.sleep(Math.abs(new Random().nextInt()%10000));
//等待其他线程一起完成第一个工作
barrier.await();
System.out.println(name+"开始第二个工作");
Thread.sleep(Math.abs(new Random().nextInt()%10000));
//等待其他线程一起完成第二个工作
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
int N = 10;
CyclicBarrier barrier = new CyclicBarrier(N,new BarrierThread(0));
MyThread[] threads = new MyThread[N];
for (int i = 0; i < N; i++) {
threads[i] = new MyThread(barrier,"线程"+i);
threads[i].start();
}
}
}
7. 线程阻塞工具类——LockSupport
可以实现在任意位置让线程阻塞,并且不会和resume一样让线程突然终止,也不需要向wait一样先获取对象的锁,也不会产生中断异常(出现中断默默退出)
- 阻塞:lock.park(),lock.parkNanos(时间),lock.parkUntil(时间)
- 如果临界区资源可用,则立即返回并将其变为不可用(只有一个可以进入)
- 如果临界区资源不可用,则阻塞
- 唤醒:lock.unpark()
- 将临界区资源变为可用
8. 限流工具类——RateLimiter
- 漏桶算法:利用一个存储区,当有请求进入系统时,无论请求的速率如何,先保存在存储区中,再以固定的速率流出存储区处理
- 令牌桶算法:在每个单位时间内产生一定量的令牌存入桶中,程序只有拿到令牌才能处理请求,否则丢弃请求(避免系统崩溃)或等待令牌
RateLimiter实现了令牌桶算法
- 创建:RateLimiter limiter = RateLimiter.create(令牌数)
- 限流
- 等待令牌:limiter.acquire();
- 丢弃请求:limiter.tryAcquire();
import com.google.common.util.concurrent.RateLimiter;
public class RateLimiterTest {
private static RateLimiter limiter = RateLimiter.create(2);
static volatile int i = 0;
static class MyThread extends Thread{
@Override
public void run() {
System.out.println(System.currentTimeMillis());
}
}
public static void main(String[] args) {
MyThread[] threads = new MyThread[10];
for (int j = 0; j < threads.length; j++) {
// limiter.acquire();
if (!limiter.tryAcquire())
continue;
threads[i] = new MyThread();
threads[i].start();
}
}
}
二:线程池
1. 基本概念
(1)原因
大量的线程会耗尽CPU和内存资源,可能会使创建和销毁线程所占用的时间超过线程真实工作的时间,大量的线程回收也会延长GC的停顿时间
(2)线程池
在线程池中维护多个活跃线程,在用户需要线程时,由原来的创建线程变成了从线程池获取空闲线程;在用户工作完成后,由原来的销毁线程变成了归还线程给线程池
(3)分类
- 普通线程服务
- FixedThreadPool:维护固定数量的线程,在任务提交时,如果有空闲线程则执行任务,否则把任务存放在任务队列中
- SingleThreadExecutor:维护一个线程,当有多个任务提交时,把其他任务存放在任务队列中
- CachedThreadPool:维护动态变化数量的线程,在任务提交时,如果有空闲线程则直接执行任务,否则创建新线程执行任务,之后新线程返回线程池复用
- 调度线程服务
- SingleThreadScheduledExecutor:维护一个线程,可以延迟执行任务或周期性执行任务
- ScheduledThreadPool:维护多个线程,可以延迟执行任务或周期性执行任务
(4)使用线程服务
从线程工厂Executors获取所需要的线程服务ExecutorService或者调度线程服务ScheduledExecutorService
1. 普通线程服务
- submit:提交指定的任务去执行并且返回Future对象,即执行的结果
- execute:提交指定的任务去执行,打印异常信息
- shutdown:拒绝接受新提交的任务,但是会继续运行之前的任务
- shutdownNow:拒绝接受新提交的任务,同时停止正在运行的任务
- invokeAll:批量完成多个任务,在所有任务完成后返回
- invokeAny:批量完成多个任务,在一个任务完成后返回
import java.util.concurrent.*;
public class ThreadPoolTest {
static class Task implements Runnable {
@Override
public void run() {
System.out.println("当前线程ID为:" + Thread.currentThread().getId());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("我在睡眠时被中断了");
}
}
}
public static void main(String[] args) {
Task task = new Task();
ExecutorService service = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
service.submit(task);
}
// service.shutdown();
service.shutdownNow();
try {
Thread.sleep(2000);
service.submit(task);
} catch (RejectedExecutionException exception) {
System.out.println("已经拒绝提交任务了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2. 调度线程服务
- schedule:延迟一段时间后执行任务
- scheduleAtFixRate:上一次任务的开始时间之后过一段时间调度下一次任务(如果任务执行时间大于调度时间,则在任务执行后开始下一次任务)
- scheduleWithFixDelay:上一次任务的结束时间之后过一段时间调度下一次任务(如果任务执行时间大于调度时间,则在任务执行后调度一段时间开始下一次任务)
如果任务出现异常,则后续任务无法继续执行
import java.util.concurrent.*;
public class ThreadPoolTest {
static class Task implements Runnable {
@Override
public void run() {
System.out.println("当前线程ID为:" + Thread.currentThread().getId());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("我在睡眠时被中断了");
}
}
}
public static void main(String[] args) {
Task task = new Task();
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
// service.scheduleAtFixedRate(task,0,2, TimeUnit.SECONDS);
service.scheduleWithFixedDelay(task,0,2,TimeUnit.SECONDS);
}
}
2. 线程池的底层实现
(1)基本线程池
线程池都是由ThreadPoolExecutor的构造函数实现的:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters and default thread factory and rejected execution handler.
* It may be more convenient to use one of the {@link Executors} factory
* methods instead of this general purpose constructor.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
可以看到,线程池主要参数如下:
- corePoolSize:线程数量
- maximumPoolSize:最大线程数量
- keepAliveTime:空闲线程存活时间
- unit:时间单位
- workQueue:任务队列
- 直接提交队列(SynchronousQueue):没有容量的队列,入队即出队,总是会将提交的任务交给线程执行(创建新线程),如果线程数量达到最大值则采取拒绝策略
- 有界任务队列(ArrayBlockingQueue):固定容量的队列,在线程数<corePoolSize时创建新线程,否则将任务加入队列。在任务队列满后且线程数<maximumPoolSize时创建新线程,否则采取拒绝策略
- 无界任务队列(LinkedBlockingQueue):无限容量的队列,不存在任务队列满的情况,可能会耗尽系统内存。在线程数<corePoolSize时创建新线程,否则将任务加入队列。
- 优先任务队列(PriorityBlockingQueue):特殊无界队列,可以控制任务顺序
- defaultThreadFactory:默认线程工厂,创建线程
- defaultHandler:默认拒绝策略
- AbortPolicy:直接抛出异常
- CallerRunsPolicy:直接在调用者线程中运行被丢弃的任务(性能下降)
- DiscardPolicy:默默丢弃任务
- DiscardOldestPolicy:丢弃即将执行的任务,尝试再次提交任务
(2)具体线程池
newFixedThreadPool:
线程个数和最大线程个数相同,采用无界任务队列
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newSingleThreadExecutor:
退化的newFixedThreadPool
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newCachedThreadPool:
最大线程个数为无穷大,采用直接提交任务队列
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
(3)线程池的执行
/**
* Executes the given task sometime in the future. The task
* may execute in a new thread or in an existing pooled thread.
*
* If the task cannot be submitted for execution, either because this
* executor has been shutdown or because its capacity has been reached,
* the task is handled by the current {@code RejectedExecutionHandler}.
*
* @param command the task to execute
* @throws RejectedExecutionException at discretion of
* {@code RejectedExecutionHandler}, if the task
* cannot be accepted for execution
* @throws NullPointerException if {@code command} is null
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
直接调度执行任务:
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
加入任务队列:
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
拒绝策略:
else if (!addWorker(command, false))
reject(command);
(4)自定义线程池、线程工厂、拒绝策略
import java.util.concurrent.*;
public class MyThreadPoolTest {
static class Task implements Runnable {
@Override
public void run() {
String group = Thread.currentThread().getThreadGroup().getName();
String name = Thread.currentThread().getName();
System.out.println(group+"--"+name);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("我在睡眠时被中断了");
}
}
}
//初始化一个固定容量的,使用合适的无界任务队列容量的,采用自定义工厂和自定义拒绝策略的线程池
static class MyThreadPool extends ThreadPoolExecutor{
public MyThreadPool(int size) {
super(size, size, 0L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10),new MyThreadFactory(),new MyRejectHandler());
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println("准备执行任务:"+r.toString());
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
System.out.println("任务执行完毕:"+r.toString());
}
@Override
protected void terminated() {
System.out.println("线程池退出");
}
}
//自定义线程工厂
static class MyThreadFactory implements ThreadFactory{
int index = 0;
ThreadGroup group;
@Override
public Thread newThread(Runnable r) {
//记录创建时间
System.out.println("正在创建线程---"+System.currentTimeMillis());
//自定义线程名称,组名,优先级
group = new ThreadGroup("线程组A");
Thread thread = new Thread(group,r);
thread.setName("线程-"+index++);
thread.setPriority(Thread.NORM_PRIORITY);
//设置守护线程
thread.setDaemon(false);
return thread;
}
}
//自定义拒绝策略
static class MyRejectHandler implements RejectedExecutionHandler{
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("我拒绝了一个任务"+r.toString());
}
}
public static void main(String[] args) throws InterruptedException {
Task task = new Task();
ExecutorService service = new MyThreadPool(5);
for (int i = 0; i < 10; i++) {
service.submit(task);
Thread.sleep(100);
}
service.shutdown();
}
}
处理异常
@Override
public void execute(Runnable command) {
super.execute(wrap(command,clientTrace()));
}
@Override
public Future<?> submit(Runnable task) {
return super.submit(wrap(task,clientTrace()));
}
//提交任务的堆栈信息
private Exception clientTrace(){
return new Exception("clientTrace");
}
//在捕捉到异常时,打印提交任务时的异常,再打印任务本身的异常
private Runnable wrap(final Runnable task,final Exception trace){
return new Runnable() {
@Override
public void run() {
try {
task.run();
}
catch (Exception e){
trace.printStackTrace();
throw e;
}
}
};
}
3. ForkJoin线程池
一个大任务通过fork方法开启多个分支线程,处理小任务,小任务处理完成后使用join方法等待其他分支线程完成,最终得到结果。
空闲线程会帮助其他线程,在其任务队列尾端取出任务执行
如果线程一直空闲,则会被挂起并压入栈,待有新的任务时唤醒并出栈
- RecursiveAction:没有返回值的任务
- RecursiveTask:携带返回值的任务
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
public class ForkJoinTest {
static class Task extends RecursiveTask<Long> {
private long start;
private long end;
public Task(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long sum = 0;
long step = (start + end) / 100; //分解
ArrayList<Task> tasks = new ArrayList<>(); //记录子任务
long index = start;
//拆分成多个子任务并提交
for (int i = 0; i < 100; i++) {
Task task = new Task(index, index+step > end ? end : index+step);
index = step + 1;
tasks.add(task);
task.fork();
}
//执行子任务
for (Task t :
tasks) {
sum += t.join();
}
return sum;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ForkJoinPool pool = new ForkJoinPool();
Task task = new Task(0L, 200000L);
//提交任务
ForkJoinTask<Long> result = pool.submit(task);
//获取返回值
long res = result.get();
System.out.println("结果:" + res);
}
}
4. Guava支持的线程池
- DirectExecutor:将任务在当前线程中直接执行,统一同步调用和异步调用
- Daemon线程池:通过MoreExecutors.getExitingExecutorService(线程服务),使得线程服务对应的连接池转为当前线程的守护线程池
三:并发容器
1. 线程安全的HashMap
(1)Collections包装
Map map = Collections.synchronizedMap(new HashMap<>());
- 简单
- 多线程环境下性能差
(2)ConcurrentHashMap
ConcurrentHashMap map = new ConcurrentHashMap();
在内部进一步细分若干个小的HashMap,默认16段。在加锁时根据哈希值获取对应的哈希段并加锁
- 适合高并发
(3)跳表SkipList
ConcurrentSkipListMap map = new ConcurrentSkipListMap();
用于快速查找的数据结构,本质上维护一个分层、有序的链表
修改表只需要局部修改,通过随机算法插入
查找表时从上层到下层跳跃式查询
组成:
保存键值对的节点
static final class Node<K,V> {
final K key;
volatile Object value;
volatile Node<K,V> next;
/**
* Creates a new regular node.
*/
Node(K key, Object value, Node<K,V> next) {
this.key = key;
this.value = value;
this.next = next;
}
}
节点通过CAS操作设置键值对,指向下一个结点
boolean casValue(Object cmp, Object val) {
return UNSAFE.compareAndSwapObject(this, valueOffset, cmp, val);
}
/**
* compareAndSet next field
*/
boolean casNext(Node<K,V> cmp, Node<K,V> val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
保存节点的索引
static class Index<K,V> {
final Node<K,V> node;
final Index<K,V> down;
volatile Index<K,V> right;
/**
* Creates index node with given values.
*/
Index(Node<K,V> node, Index<K,V> down, Index<K,V> right) {
this.node = node;
this.down = down;
this.right = right;
}
}
通过CAS操作,指向右边的索引
/**
* compareAndSet right field
*/
final boolean casRight(Index<K,V> cmp, Index<K,V> val) {
return UNSAFE.compareAndSwapObject(this, rightOffset, cmp, val);
}
头索引
static final class HeadIndex<K,V> extends Index<K,V> {
final int level;
HeadIndex(Node<K,V> node, Index<K,V> down, Index<K,V> right, int level) {
super(node, down, right);
this.level = level;
}
}
2. 线程安全的List
(1)Vector
Vector vector = new Vector();
- 性能差
(2)Collections包装
List list = Collections.synchronizedList(new ArrayList<>());
- 多线程环境下性能差
(3)ConcurrentLinkedQueue
ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();
通过使用CAS设置值、下一结点、头尾结点
boolean casItem(E cmp, E val) {
return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
}
boolean casNext(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
private boolean casTail(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val);
}
private boolean casHead(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val);
}
- 只有一个结点:通过循环保证插入新结点成功
- 多个结点:通过循环查找到最后一个结点并插入,更新尾结点
- 哨兵结点:需要重新循环查找到最后一个结点或者是未结点
/**
* Inserts the specified element at the tail of this queue.
* As the queue is unbounded, this method will never return {@code false}.
*
* @return {@code true} (as specified by {@link Queue#offer})
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
checkNotNull(e);
final Node<E> newNode = new Node<E>(e);
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) {
// p is last node
if (p.casNext(null, newNode)) {
// Successful CAS is the linearization point
// for e to become an element of this queue,
// and for newNode to become "live".
if (p != t) // hop two nodes at a time
casTail(t, newNode); // Failure is OK.
return true;
}
// Lost CAS race to another thread; re-read next
}
else if (p == q)
// We have fallen off list. If tail is unchanged, it
// will also be off-list, in which case we need to
// jump to head, from which all live nodes are always
// reachable. Else the new tail is a better bet.
p = (t != (t = tail)) ? t : head;
else
// Check for tail updates after two hops.
p = (p != t && t != (t = tail)) ? t : q;
}
}
(4) 高效读取
CopyOnWriteArrayList:通过锁实现并发,性能较高
CopyOnWriteArrayList list = new CopyOnWriteArrayList();
- 只阻塞写和写的操作
- 读操作之间:不会出现并发
- 写操作和读操作之间:对数据的副本加锁,进行写操作,再替换原来的数据并解锁。由于数据使用volatile修饰,具有可见性。
CopyOnWriteLinkedQueue:通过CAS操作实现并发
(5)数据共享通道BlockingQueue
实现线程之间的数据共享
- ArrayBlockingQueue:有界
- LinkedBlockingQueue:无界
入队:
- offer:如果队列为满返回false
- put:如果队列为满则等待
出队:
- poll:如果队列为空返回null
- take:如果队列为空则等待
实现:通过可重入锁和Condition条件变量进行入队和出队的条件控制(生产者-消费者模型)