Android并发相关知识点

JMM

JMM是多线程内存模型,在JMM中将内存分为主内存工作内存,主内存是所有线程共享的内存,工作内存是每个线程私有的内存。线程对变量的操作都在工作内存中进行,锁的作用就是将线程工作内存的变量进行同步。

8种原子操作

主内存 工作内存 线程 1. lock(锁定主内存中的变量,确保其他线程无法访问) 2. read(从主内存读取变量值到工作内存) 3. load(将变量值载入工作内存) 4. use(线程从工作内存中读取变量值进行操作) 5. assign(线程将操作结果赋值给工作内存中的变量) 6. store(将工作内存中的变量值存储回主内存) 7. write(将变量值写入主内存) 8. unlock(解锁主内存中的变量,允许其他线程访问) 主内存 工作内存 线程

线程安全

  • 可见性:确保线程对变量的修改对其他线程可见。

  • 原子性:确保操作不被中断。

  • 有序性:保指令的执行顺序符合代码逻辑顺序。

线程生命周期

新建
就绪
运行
阻塞
终止

线程间通信

wait/notify/notifyAll

waitnotifynotifyAllObject 类的方法,用于线程间的通信。它们必须在同步代码块中使用。

wait和sleep的区别
特性waitsleep
所属类Object 类的方法Thread 类的静态方法
是否需要同步必须在 synchronized 同步代码块中使用可以在任何地方使用,无需同步
是否释放锁释放锁,允许其他线程获取锁不释放锁,但让出CPU资源
线程状态线程进入 WAITINGTIMED_WAITING 状态线程进入 TIMED_WAITING 状态
唤醒方式notifynotifyAll 唤醒通过指定的时间结束等待
为什么 waitnotify 需要在同步代码块中执行

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不保证原子性
AtomicCAS保证原子性
volatile保证有序性和可见性
是(部分操作)
  • 可重入锁:允许同一个线程多次获取同一个锁而不会发生死锁

  • 非公平锁:多个线程竞争同一个锁时,不保证线程获取锁的顺序

其他同步机制

CAS(Compare-And-Swap)

CAS是一种用于实现无锁编程的机制,通过比较预期值来更新变量,确保操作的原子性。其核心逻辑如下:

  1. 读取当前值:获取变量的当前值。
  2. 计算新值:根据当前值计算新的值。
  3. 比较并替换:如果当前值与预期值一致,则将变量更新为新值;否则,重新尝试。

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. 线程1读取变量值为A。
  2. 在线程1执行CAS操作之前,线程2将变量值改为B,再改回A。
  3. 线程1执行CAS操作时,发现变量值仍然是A,于是成功更新变量。
  4. 但实际上,变量的值可能已经发生了变化(从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:控制同时访问特定资源的线程数量,适用于限流和资源管理的场景。

累加器

LongAdderLongAccumulator 是 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开发中处理异步任务的推荐方式,代码更简洁且易于维护。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值