JMM
JMM是多线程内存模型,在JMM中将内存分为主内存和工作内存,主内存是所有线程共享的内存,工作内存是每个线程私有的内存。线程对变量的操作都在工作内存中进行,锁的作用就是将线程工作内存的变量进行同步。
8种原子操作
线程安全
-
可见性:确保线程对变量的修改对其他线程可见。
-
原子性:确保操作不被中断。
-
有序性:保指令的执行顺序符合代码逻辑顺序。
线程生命周期
线程间通信
wait/notify/notifyAll
wait、notify 和 notifyAll 是 Object 类的方法,用于线程间的通信。它们必须在同步代码块中使用。
wait和sleep的区别
| 特性 | wait | sleep |
|---|---|---|
| 所属类 | Object 类的方法 | Thread 类的静态方法 |
| 是否需要同步 | 必须在 synchronized 同步代码块中使用 | 可以在任何地方使用,无需同步 |
| 是否释放锁 | 释放锁,允许其他线程获取锁 | 不释放锁,但让出CPU资源 |
| 线程状态 | 线程进入 WAITING 或 TIMED_WAITING 状态 | 线程进入 TIMED_WAITING 状态 |
| 唤醒方式 | 被 notify 或 notifyAll 唤醒 | 通过指定的时间结束等待 |
为什么 wait 和 notify 需要在同步代码块中执行
wait 需要将线程信息写到锁对象mark word 中,这样 notify 方法才能根据这些信息唤醒等待的线程。
Condition
与Lock配合使用,提供更灵活的线程等待和通知机制。
-
实例代码
public class ConditionExample { private final ReentrantLock lock = new ReentrantLock(); private final Condition condition = lock.newCondition();//创建一个唤醒条件 public void producer() { lock.lock(); try { condition.signalAll(); // 唤醒所有等待该条件的线程 } finally { lock.unlock(); } } public void consumer() throws InterruptedException { lock.lock(); try { condition.await(); // 等待通知 } finally { lock.unlock(); } } }
jion
等待一个线程终止,常用于一个线程等待另一个线程完成任务。
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
});
Thread t2 = new Thread(() -> {
});
t1.start();
t2.start();
t1.join();
t2.join();
// t1,t2线程都执行完才打印
System.out.println("Final counter value: " + example.get());
}
线程池
相关类
| 相关类 | 描述 |
|---|---|
| Executor | 顶层接口 ,提供了一种思想:将任务提交和任务执行进行解耦。用户只需提交 Runnable对象,无需关注任务怎么执行。 |
| ExecutorService | 增加了一些能力:(1)扩充执行任务的能力,补充可以为一个或一批异步任务生成 Future的方法;(2)提供了管控线程池的方法,比如停止线程池的运行。 |
| AbstractExecutorService | 将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。 |
| ThreadPoolExecutor | 实现最复杂的运行部分。一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。 |
线程池核心参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
...
}
| 参数 | 描述 |
|---|---|
| corePoolSize | 核心线程数:线程池中保留的线程数,即使它们是空闲的,除非设置 allowCoreThreadTimeOut。 |
| maximumPoolSize | 最大线程数:线程池中允许的最大线程数 |
| keepAliveTime | 线程空闲时间:如果经过 keepAliveTime 时间后,超过核心线程数的线程还没有接受到新的任务,那就回收。 |
| unit | 单位:keepAliveTime 的时间单位 |
| workQueue | 存放待执行任务的阻塞队列:当提交的任务数超过核心线程数后,再提交的任务就存放在这里。 |
| threadFactory | 线程工厂:执行程序创建新线程时使用的工厂。我们可以自定义线程工厂,通过线程工厂的名字可以快速定位来自哪个线程工厂。 |
| handler | 拒绝策略:当队列里面放满了任务、最大线程数的线程都在工作时,这时继续提交的任务线程池就处理不了,应该执行怎么样的拒绝策略。 |
ExecutorService:用于管理线程池,复用线程以减少创建和销毁的开销。execute(Runnable command):提交不返回结果的任务。submit(Callable<T> task):提交可能返回结果的任务,返回一个Future对象。
- 线程池的创建:
newFixedThreadPool:创建固定大小的线程池。newCachedThreadPool:创建一个可缓存的线程池。newSingleThreadExecutor:创建单线程的线程池。newScheduledThreadPool:创建支持定时任务的线程池。
- 阻塞队列:
ArrayBlockingQueue:基于数组的有界阻塞队列,先进先出(FIFO)。LinkedBlockingQueue:基于链表的可选界阻塞队列,适用于生产者-消费者模型。PriorityBlockingQueue:基于优先级的无界阻塞队列,元素按自然顺序或指定的比较器顺序排列。SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等待一个移除操作,反之亦然。
线程同步机制
| 特性/机制 | 描述 | 是否可重入 | 是否公平 | 是否支持原子操作 | 是否确保可见性 | 是否确保有序性 |
|---|---|---|---|---|---|---|
synchronized | 内置锁,简单易用 | 是 | 否 | 是 | 是 | 是 |
Lock | 显式锁,更灵活 | 视具体实现而定 | 视具体实现而定 | 是 | 是 | 是 |
ReentrantLock | 可重入锁,支持公平锁和非公平锁 | 是 | 是/否(可选) | 是 | 是 | 是 |
ReentrantReadWriteLock | 读写锁,允许多个读线程同时访问 | 是 | 是/否(可选) | 是 | 是 | 是 |
StampedLock | 提供乐观读锁、写锁和悲观读锁 | 是 | 是/否(可选) | 是 | 是 | 是 |
volatile | 不保证原子性 | 否 | 否 | 否 | 是 | 是 |
Atomic类 | CAS保证原子性volatile保证有序性和可见性 | 是(部分操作) | 否 | 是 | 是 | 是 |
-
可重入锁:允许同一个线程多次获取同一个锁而不会发生死锁
-
非公平锁:多个线程竞争同一个锁时,不保证线程获取锁的顺序
其他同步机制
CAS(Compare-And-Swap)
CAS是一种用于实现无锁编程的机制,通过比较预期值来更新变量,确保操作的原子性。其核心逻辑如下:
- 读取当前值:获取变量的当前值。
- 计算新值:根据当前值计算新的值。
- 比较并替换:如果当前值与预期值一致,则将变量更新为新值;否则,重新尝试。
CAS机制的核心在于确保操作的原子性,避免了传统锁机制的开销,同时提高了并发性能。
AtomicInteger与CAS
AtomicInteger是Java中常用的原子类,通过compareAndSet方法实现了CAS机制。以下是模拟CAS过程的代码示例:
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
private AtomicInteger counter = new AtomicInteger(0);
public void add(int value) {
int currentValue;
int newValue;
do {
currentValue = counter.get(); // 获取当前值
newValue = currentValue + value; // 计算新值
} while (!counter.compareAndSet(currentValue, newValue)); // CAS操作
}
}
ABA问题
在CAS机制中,存在一个潜在问题——ABA问题。具体来说:
- 线程1读取变量值为A。
- 在线程1执行CAS操作之前,线程2将变量值改为B,再改回A。
- 线程1执行CAS操作时,发现变量值仍然是A,于是成功更新变量。
- 但实际上,变量的值可能已经发生了变化(从A到B再回到A),而线程1却无法感知。
这种情况下,线程1可能会基于错误的假设进行操作,从而导致逻辑错误。
解决ABA问题:AtomicStampedReference
AtomicStampedReference是Java提供的一个类,用于解决ABA问题。它通过引入一个**版本号(Stamp)**来区分变量值的变化,即使变量值从A变回A,版本号也会发生变化。
以下是使用AtomicStampedReference解决ABA问题的代码示例:
public class AtomicStampedReferenceExample {
private AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(0, 0); // 初始值为0,版本号为0
public void add(int value) {
int stamp = atomicStampedReference.getStamp(); // 获取当前版本号
int currentValue = atomicStampedReference.getReference(); // 获取当前值
int newValue = currentValue + value; // 计算新值
// 使用compareAndSet更新值和版本号
while (!atomicStampedReference.compareAndSet(currentValue, newValue, stamp, stamp + 1)) {
// 如果更新失败(版本号或者值不一致),重新获取当前值和版本号
stamp = atomicStampedReference.getStamp();
currentValue = atomicStampedReference.getReference();
newValue = currentValue + value;
}
}
public int getValue() {
return atomicStampedReference.getReference();
}
}
public static void main(String[] args) {
AtomicStampedReferenceExample example = new AtomicStampedReferenceExample();
example.add(5); // 增加5
example.add(3); // 增加3
System.out.println("Value: " + example.getValue()); // 输出结果
}
CountDownLatch:允许一个或多个线程等待其他线程完成操作,适用于主线程等待多个子线程完成任务的场景。CyclicBarrier:使一组线程相互等待,直到到达某个公共屏障点,适用于多个线程需要同步执行的场景。Semaphore:控制同时访问特定资源的线程数量,适用于限流和资源管理的场景。
累加器
LongAdder 和 LongAccumulator 是 JDK8中提供的高性能累加器类,用于在高并发场景下高效地进行累加操作。它们通过分段锁或无锁的方式解决了传统 AtomicLong 在高并发下的性能瓶颈。
LongAccumulator 是一个更通用的累加器,允许用户自定义累加逻辑。它通过一个 LongBinaryOperator 来定义如何合并多个线程的值。
public class LongAccumulatorExample {
// 自定义 LongBinaryOperator,里面的操作也是原子的
LongBinaryOperator customOperator = (currentValue, newValue) -> {
// 自定义逻辑:将 newValue 乘以 2 后与 currentValue 相加
return currentValue + (newValue * 2);
};
// 定义累加器使用自定义LongBinaryOperator
private LongAccumulator accumulator = new LongAccumulator(customOperator, 0L);
public void add(long value) {
accumulator.accumulate(value); // 累加指定值
}
public long get() {
return accumulator.get(); // 获取当前值
}
}
public static void main(String[] args) throws InterruptedException {
LongAccumulatorExample example = new LongAccumulatorExample();
// 创建多个线程进行累加操作
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
example.add(1);
}
});
t1.join();
System.out.println("Final counter value: " + example.get());
}
Android中的并发
AsyncTask:用于在后台线程执行耗时操作,并将结果回调到主线程。IntentService:用于后台执行耗时任务,任务完成后自动停止。Handler:用于线程间的消息传递,常用于更新UI。HandlerThread:一个继承自Thread的类,内部包含一个Looper,用于在后台线程中处理消息队列。Kotlin协程:现代Android开发中处理异步任务的推荐方式,代码更简洁且易于维护。
986

被折叠的 条评论
为什么被折叠?



