基础知识

进程与线程
线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。

方法

  • Thread.sleep(long millis),一定是当前线程调用此方法,线程进入 TIME_WAITING 状态,但不释放对象锁,millis 后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
  • Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的 cpu 时间片,由运行状态变会就绪状态,让 OS 再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证 yield() 达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield() 不会导致阻塞。
  • t.join()/t.join(long millis),当前线程里调用其它线程 t 的 join 方法,当前线程进入 TIME_WAITING 状态,当前线程不释放已经持有的对象锁。线程 t 执行完毕或者 millis 时间到,当前线程进入就绪状态。
  • obj.wait(),当前线程调用对象的 wait() 方法,当前线程释放对象锁,进入等待队列。依靠 notify()/notifyAll() 唤醒或者 wait(long timeout) timeout 时间到自动唤醒。
  • obj.notify() 唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll() 唤醒在此对象监视器上等待的所有线程。

    终止线程的 4 种方式

  • 正常结束

  • 退出标识:有些线程需要长时间的运行,只有在外部某些条件满足的情况下,才能关闭这些线程
  • Interrupt() 方法
    • 线程处于阻塞状态:调用 interrupt() 方法会抛出异常,此时要退出线程必须 catch 异常,并使用 break 方法退出。
    • 线程不处于阻塞状态:使用 isInterrupted() 判断线程的中断标志来退出循环,当使用 interrupt() 方法时,中断标志就会置 true
  • stop 方法:thread.stop() 调用之后,创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用 thread.stop() 后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性

    线程生命周期

    image.png

Java 规定了 虚拟机内的线程(非操作系统线程)的生命周期,参考 Thread 源码:

  • NEW(新建):仅定义了一个线程对象,还未调用它的 start() 方法。Thread state for a thread which has not yet started.
  • RUNNABLE(可运行):调用了线程的 start 方法,已经被 JVM 执行,但还在等待系统其它资源,如 CPU 时间片、I/O 等,根据操作系统知识可进一步将可运行状态细分为就绪、运行中两种状态(Java 并没有进一步细分)。Thread state for a runnable thread. A thread in the runnable state is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as processor.
    • 就绪:调用了 start 方法,但没有得到 CPU 时间片的状态
    • 运行中:得到了 CPU 时间片的状态,正在执行
  • BLOCKED(阻塞):线程等待 monitor lock 的状态,只有得到了这个对象锁之后,状态才会由阻塞变为就绪。等待 monitor lock 进入被锁住的代码块,或者在调用 wait() 方法之后又进入被锁住的代码块(A thread in the blocked state is waiting for a monitor lock to enter a synchronized block/method or reenter a synchronized block/method after calling {@link Object#wait() Object.wait}.)
    • 等待阻塞:运行的线程执行 o.wait() 方法,JVM 会把该线程放入等待队列(waitting queue)中
    • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中
    • 其他阻塞:运行的线程执行 Thread.sleep(long ms) 或t.join() 方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态,当 sleep() 状态超时、join() 等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入可运行(runnable)状态
  • WAITING(等待):A thread in the waiting state is waiting for another thread to perform a particular action.
    • 等待另一个线程的特定操作的状态,等待被其他线程唤醒
    • 等待的过程是主动等待的,而阻塞是得不到对象锁而被动阻塞住
    • 当线程拿到锁之后,调用相应锁对象的 wait()、join()、继承 LockSupport 类的 park() ,调用其中的方法线程就会处于这个状态。
  • TIMED_WAITING(有限期等待或超时等待):Thread state for a waiting thread with a specified waiting time.
    • 等待另一个线程执行指定等待时间的操作的线程处于此状态
    • 等待另一个线程特定时间,时间过后会自动唤醒
    • 线程执行 sleep()、wait(long)、join(long)、LockSupport.parkNanos 、LockSupport.parkUntil 方法时会处于这种状态
  • TERMINATED(终止):线程业务逻辑执行完成退出的状态
    Thread state for a terminated thread.The thread has completed execution.

从获取 锁 的角度,阻塞分为 3 种情况
image.png

  • 等待阻塞(wait pool):调用 wait() 方法之后(释放锁)
  • 同步阻塞(lock pool):synchronized,lock.lock 操作之后,获取锁等待的状态
  • 其它阻塞(blocked):Thread.sleep(),t1.join() 执行之后(不释放锁)

wait 和 sleep 区别

共同点:

  • 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
  • wait() 和 sleep() 都可以通过 interrupt() 方法 打断线程的暂停状态 ,从而使线程立刻抛出 InterruptedException。

如果线程 A 希望立即结束线程 B,则可以对线程 B 对应的 Thread 实例调用 interrupt 方法。如果此刻线程 B 正在 wait/sleep /join,则线程 B 会立刻抛出 InterruptedException,在 catch() {} 中直接 return 即可安全地结束线程。
需要注意的是,InterruptedException 是线程自己从内部抛出的,并不是 interrupt() 方法抛出的。对某一线程调用 interrupt() 时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出 InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出 InterruptedException 。

不同点:

  1. Thread类的方法:sleep(),yield()等 。Object的方法:wait()和notify()等
  2. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
  3. wait,notify 和 notifyAll 只能在同步控制方法或者同步控制块里面使用,而 sleep 可以在任何地方使用
  4. sleep 必须捕获异常,而 wait,notify和notifyAll 不需要捕获异常

所以sleep() 和 wait() 方法的最大区别是:sleep() 睡眠时,保持对象锁,仍然占有该锁;而 wait() 睡眠时,释放对象锁。但是 wait() 和 sleep() 都可以通过 interrupt() 方法打断线程的暂停状态,从而使线程立刻抛出 InterruptedException(但不建议使用该方法)。

锁的种类

https://pdai.tech/md/java/thread/java-thread-x-lock-all.html
image.png

死锁

产生死锁的必要条件:
互斥条件:该资源任意一个时刻只由一个线程占用。
请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。

避免死锁:
破坏请求与保持条件 :一次性申请所有的资源。
破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

Happens-before 原则

如果线程间的操作无法从如下几个规则推导出来,那么它们的操作就没有顺序性保障,虚拟机或者操作系统就能随意地进行重排序,从而可能会发生并发安全问题。

  • 程序次序规则(Program Order Rule):在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。准确地说,应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环等结构。
  • 管程锁定规则(Monitor Lock Rule):一个 unlock 操作先行发生于后面对同一个锁的 lock 操作。这里必须强调的是同一个锁,而 “后面” 是指时间上的先后顺序。
  • volatile 变量规则(Volatile Variable Rule):对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作,这里的 “后面” 同样是指时间上的先后顺序。
  • 线程启动规则(Thread Start Rule):Thread 对象的 start () 方法先行发生于此线程的每一个动作。
  • 线程终止规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过 Thread.join () 方法结束、Thread.isAlive () 的返回值等手段检测到线程已经终止执行。
  • 线程中断规则(Thread Interruption Rule):对线程 interrupt () 方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 Thread.interrupted () 方法检测到是否有中断发生。
  • 对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize () 方法的开始。
  • 传递性(Transitivity):如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那就可以得出操作 A 先行发生于操作 C 的结论。

    synchronized

  • synchronized 是 Java 语法层面的同步,简单清晰

  • Lock 需要主动释放锁
  • JVM 在线程和对象元数据中记录了 synchronized 锁的相关信息,而 Lock 没有

    三种使用方式

    image.png
    1.修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获得 当前对象实例的锁
    1. synchronized void method() { //业务代码 }

2.修饰静态方法: 也就是给当前类加锁,会作用于类的所有对象实例 ,进入同步代码前要获得 当前 class 的锁。因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管 new 了多少个对象,只有一份)。所以,如果一个线程 A 调用一个实例对象的非静态 synchronized 方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁

  1. synchronized static void method() { //业务代码 }

3.修饰代码块 :指定加锁对象,对给定对象/类加锁。synchronized(this|object) 表示进入同步代码库前要获得给定对象的锁。synchronized(类.class) 表示进入同步代码前要获得 当前 class 的锁

  1. synchronized(this) { //业务代码 }

原理

锁优化

JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
https://www.cnblogs.com/wuqinglong/p/9945618.html

  • 自旋锁与自适应自旋

Java 的线程时映射到操作系统的原生内核线程上,挂起线程和恢复线程需要转到内核态进行,比较消耗资源;所以 JVM 会让线程执行一个忙等待(自旋),然后再获取锁。
JDK 6 对自旋锁进行了优化,等待的时间根据同一个锁上面的自旋时间和锁的拥有者的状态来决定。
若上一次自旋刚获取成功过锁,那么 JVM 允许线程等待相对更长的时间;若自旋很少成功获得锁,那么会直接省略自旋的过程

  • 锁消除

JIT 即时编译器通过逃逸分析技术,发现 synchronized 锁对象,只有一个线程能加锁,不存在共享数据竞争的问题,那么会将锁进行消除。
例如,下面代码在字节码层面会转换为 StringBuilder.append 方式,而 append 方法都有一个同步块,JVM 分析到参数只会在方法内部调用,不存在共享数据的竞争,这里的锁可以被消除掉。

  1. public String concatString(String s1, String s2, String s3) {
  2. return s1 + s2 + s3;
  3. }
  • 锁粗化

若一系列的连续操作都是对同一个对象反复加锁和解锁,甚至加锁操作在循环体中,JVM 会将加锁同步的范围扩展(粗化)到操作外部。

  • 轻量级锁

相对于 synchronized 重量级锁而言,它的设计初衷,就是在没有多线程竞争的前提下,减少传统重量级锁使用操作系统互斥量产生的性能消耗。
实现:在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象 Mark Word 的拷贝;然后 JVM 通过 CAS 操作,将锁对象的 Mark Word 更新为指向 Lock Record 的指针,CAS 成功表示成功获取到锁,Mark Word 中的锁标志位也改为 01
若 CAS 更新失败,说明当前存在竞争,然后 JVM 检查锁对象的 Mark Word 是否指向当前线程的栈帧,如果是,那么说明线程已经拥有这个对象的锁,直接进入同步方法块;否则轻量级锁膨胀为重量级锁,锁标志位改为 10。
image.png

  • 偏向锁

意思是锁会偏向于第一个获得它的线程,在后面执行过程中,锁没有被其它线程获取,那么持有偏向锁的线程将无需再进行同步。
偏向锁可以提高带有同步但无竞争的程序性能,但是对于程序中大多数锁总是被不同线程访问的情况,偏向锁就是多余的。
轻量级锁是在无竞争条件下使用 CAS 操作去消除同步带来的互斥量;偏向锁是在无竞争的情况下,把整个同步都消除,连 CAS 操作都不做。

image.png


与 ReentrantLock 对比

1.5.1. 两者都是可重入锁
1.5.2.synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API
1.5.3.ReentrantLock 比 synchronized 增加了一些高级功能

  • 等待可中断 : ReentrantLock提供了一种能够中断等待锁的线程的机制,通过 lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
  • 可实现公平锁 : ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。
  • 可实现选择性通知(锁可以绑定多个条件): synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制。ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition()方法。

volatile

Java 内存模型

image.png
Java 内存模型抽象了线程和主内存之间的关系,就比如说线程之间的共享变量必须存储在主内存中。Java 内存模型主要目的是为了屏蔽系统和硬件的差异,避免一套代码在不同的平台下产生的效果不一致。

volatile 关键字 除了防止 JVM 的指令重排 ,还有一个重要的作用就是保证变量的可见性。

ThreadLocal

每一个线程都有自己的专属本地变量

  1. public class Thread implements Runnable {
  2. //......
  3. //与此线程有关的ThreadLocal值。由ThreadLocal类维护
  4. ThreadLocal.ThreadLocalMap threadLocals = null;
  5. //与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
  6. ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
  7. //......
  8. }

从上面Thread类 源代码可以看出Thread 类中有一个 threadLocals 和 一个 inheritableThreadLocals 变量,它们都是 ThreadLocalMap 类型的变量,我们可以把 ThreadLocalMap 理解为ThreadLocal 类实现的定制化的 HashMap。默认情况下这两个变量都是 null,只有当前线程调用 ThreadLocal 类的 set或get方法时才创建它们,实际上调用这两个方法的时候,我们调用的是ThreadLocalMap类对应的 get()、set()方法。

ThreadPoolExecutor

FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致 OOM。
CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。
image.png

  1. /**
  2. * 用给定的初始参数创建一个新的ThreadPoolExecutor。
  3. */
  4. public ThreadPoolExecutor(int corePoolSize,
  5. int maximumPoolSize,
  6. long keepAliveTime,
  7. TimeUnit unit,
  8. BlockingQueue<Runnable> workQueue,
  9. ThreadFactory threadFactory,
  10. RejectedExecutionHandler handler) {
  11. if (corePoolSize < 0 ||
  12. maximumPoolSize <= 0 ||
  13. maximumPoolSize < corePoolSize ||
  14. keepAliveTime < 0)
  15. throw new IllegalArgumentException();
  16. if (workQueue == null || threadFactory == null || handler == null)
  17. throw new NullPointerException();
  18. this.corePoolSize = corePoolSize;
  19. this.maximumPoolSize = maximumPoolSize;
  20. this.workQueue = workQueue;
  21. this.keepAliveTime = unit.toNanos(keepAliveTime);
  22. this.threadFactory = threadFactory;
  23. this.handler = handler;
  24. }
  • corePoolSize

核心线程数,当线程数小于核心线程数时,线程池优先通过创建核心线程去处理任务
默认情况下,核心线程会一直存活,若设置了 allowsCoreThreadTimeOut = true,那么核心线程数在空闲时也会被回收。

  • maximumPoolSize

最大的线程数,当线程数 >= corePoolSize,并且任务队列满时。线程池会创建新的线程去处理任务。
当总线程数 = maximumPoolSize 时,若任务队列满,对于后续加入的任务,线程池会执行 RejectedExecutionHandler 拒绝策略

  • keepAliveTime,TimeUnit

keepAliveTime 表示线程最大空闲时间,即线程超过 keepAliveTime 没有任务处理时,线程池会对空闲的线程进行回收,直到线程数 = corePoolSize
若设置了 allowsCoreThreadTimeOut = true,那么核心线程超过空闲时间时也会被回收。
TimeUnit 表示时间的单位,可以为 天、小时、分钟、秒,主要配合 keepAliveTime 使用

  • workQueue

阻塞的任务队列,当正在工作的线程数 = corePoolSize 时,后续的任务会先加入到任务队列中,后续由线程池进行调度处理。
常用的任务队列根据 有界与无界、阻塞与非阻塞分为以下几种:
image.png

threadFactory

创建线程的工厂,可以通过实现 ThreadFactory 接口的方式,去构造线程,设定自定义的线程名称,优先级等。
当不指定 threadFactory 实现时,线程池会默认使用 Executors.defaultThreadFactory() 作为创建线程,创建的线程名称默认为 pool-xxx-thread-,非守护线程、默认优先级。

  1. public ThreadPoolExecutor(int corePoolSize,
  2. int maximumPoolSize,
  3. long keepAliveTime,
  4. TimeUnit unit,
  5. BlockingQueue<Runnable> workQueue,
  6. RejectedExecutionHandler handler) {
  7. this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  8. Executors.defaultThreadFactory(), handler);
  9. }
  10. public class Executors {
  11. static class DefaultThreadFactory implements ThreadFactory {
  12. private static final AtomicInteger poolNumber = new AtomicInteger(1);
  13. private final ThreadGroup group;
  14. private final AtomicInteger threadNumber = new AtomicInteger(1);
  15. private final String namePrefix;
  16. DefaultThreadFactory() {
  17. SecurityManager s = System.getSecurityManager();
  18. group = (s != null) ? s.getThreadGroup() :
  19. Thread.currentThread().getThreadGroup();
  20. namePrefix = "pool-" +
  21. poolNumber.getAndIncrement() +
  22. "-thread-";
  23. }
  24. public Thread newThread(Runnable r) {
  25. Thread t = new Thread(group, r,
  26. namePrefix + threadNumber.getAndIncrement(),
  27. 0);
  28. if (t.isDaemon())
  29. t.setDaemon(false);
  30. if (t.getPriority() != Thread.NORM_PRIORITY)
  31. t.setPriority(Thread.NORM_PRIORITY);
  32. return t;
  33. }
  34. }
  35. }

RejectedExecutionHandler

任务拒绝策略,当任务队列满并且正在工作的线程数已经达到 maximumPoolSize 时,线程池已经没有能力去处理新的任务,此时需要执行相应的任务拒绝策略。
AbortPolicy 是默认的拒绝策略
image.png

静态工厂方法

Executors 类提供几个静态的创建线程池的办法

  1. // coreSize 和 maximumPoolSize 一样,使用无界阻塞队列
  2. // 没有任务执行时,不会销毁核心线程,占用一定系统资源
  3. public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
  4. return new ThreadPoolExecutor(nThreads, nThreads,
  5. 0L, TimeUnit.MILLISECONDS,
  6. new LinkedBlockingQueue<Runnable>(),
  7. threadFactory);
  8. }
  9. // 单个线程,使用无界阻塞队列
  10. // 保证任务提交顺序与执行顺序相同
  11. public static ExecutorService newSingleThreadExecutor() {
  12. return new FinalizableDelegatedExecutorService
  13. (new ThreadPoolExecutor(1, 1,
  14. 0L, TimeUnit.MILLISECONDS,
  15. new LinkedBlockingQueue<Runnable>()));
  16. }
  17. // 没有核心线程,放到同步队列中,请求量较大时,创建较多线程
  18. public static ExecutorService newCachedThreadPool() {
  19. return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
  20. 60L, TimeUnit.SECONDS,
  21. new SynchronousQueue<Runnable>());
  22. }
  23. // 延时队列
  24. public ScheduledThreadPoolExecutor(int corePoolSize,
  25. ThreadFactory threadFactory) {
  26. super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
  27. new DelayedWorkQueue(), threadFactory);
  28. }
  29. // 1.8 之后加入,
  30. public static ExecutorService newWorkStealingPool(int parallelism) {
  31. return new ForkJoinPool
  32. (parallelism,
  33. ForkJoinPool.defaultForkJoinWorkerThreadFactory,
  34. null, true);
  35. }

如何设置合理的线程池参数?

corePoolSize 选择

根据任务属于 CPU 密集型还是 IO 密集型确定 corePoolSize
CPU 密集型任务表示需要大量计算的场景,通常设置 corePoolSize = 物理机 CPU 核数 + 1;线程设置过多可能引发线程上下文频繁切换,所以接近物理机 CPU 核心数即可。

  • 为什么 + 1 ? 避免某个线程由于某些原因中断暂停,而浪费 CPU 时钟周期。

IO 密集型任务表示程序对硬盘、内存读写比较频繁的场景,此时 CPU 占用率比较低,一般设置 corePoolSize = 物理机 CPU 核心数 * 2

workQueue 选择

无界队列,即队列的长度没有限制,当使用无界队列时,任务的拒绝策略也就失效了,并且随着任务的累计,服务会有 OOM 的危险,所以谨慎使用,LinkedBlockingQueue 是常用的无界队列。
有界队列,有界队列初始化时需要指定长度,能够有效避免资源耗尽的情况,其中 ArrayBlockingQueue 和 PriorityBlockingQueue 都是有界阻塞队列, ArrayBlockingQueue 满足 FIFO 先进先出原则;PriorityBlockingQueue 里面的元素根据任务的优先级进行排序
同步移交队列,SynchronousQueue 是一个不存储元素的阻塞队列,他必须等待被添加的元素被消费之后才能继续添加元素。一般情况下,它要求 maximumPoolSize = Integer.MAX_VALUE,这样线程池就会创建新的线程去处理任务。

RejectedExecutionHandler 选择

若任务不太重要可以抛弃时,那么建议使用 DiscardPolicy、DiscardOldestPolicy 策略
这个参数的设定尽量使用默认值,因为线程池出现拒绝策略的情况,一般可以通过设置前面的参数来避免。

Atomic

基本类型
使用原子的方式更新基本类型

  • AtomicInteger:整型原子类
  • AtomicLong:长整型原子类
  • AtomicBoolean:布尔型原子类

数组类型
使用原子的方式更新数组里的某个元素

  • AtomicIntegerArray:整型数组原子类
  • AtomicLongArray:长整型数组原子类
  • AtomicReferenceArray:引用类型数组原子类

引用类型

  • AtomicReference:引用类型原子类
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
  • AtomicMarkableReference :原子更新带有标记位的引用类型

对象的属性修改类型

  • AtomicIntegerFieldUpdater:原子更新整型字段的更新器
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器
  • AtomicReferenceFieldUpdater:原子更新引用类型字段的更新器

AtomicInteger

  1. // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用)
  2. private static final Unsafe unsafe = Unsafe.getUnsafe();
  3. private static final long valueOffset;
  4. static {
  5. try {
  6. valueOffset = unsafe.objectFieldOffset
  7. (AtomicInteger.class.getDeclaredField("value"));
  8. } catch (Exception ex) { throw new Error(ex); }
  9. }
  10. private volatile int value;

AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。

AbstractQueuedSynchronizer

AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH(Craig,Landin and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。

https://www.cnblogs.com/waterystone/p/4920797.html
https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html

CountDownLatch

CountDownLatch 的作用就是 允许 count 个线程阻塞在一个地方,直至所有线程的任务都执行完毕