线程池源码剖析

2023-05-16

线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销。----摘自维基百科

我们在Android或者Java开发中,日常所使用的就是ThreadPoolExecutor了,我们先来看下如何使用一个线程池来代替多线程开发。

使用线程池

// 创建一个核心线程数为5,最大线程数为10,空闲线程存活时间为60s的线程池对象
val threadPoolExecutor = ThreadPoolExecutor(
    5, 10, 60,
    TimeUnit.MINUTES,
    ArrayBlockingQueue<Runnable>(100),
    RejectedExecutionHandler { _, _ -> println("reject submit thread to thread pool") }
)

// 测试
for (i in 1..10) {
    threadPoolExecutor.execute { println("execute thread is:${Thread.currentThread().name}") }
}

// 结果
// execute thread is:pool-1-thread-1
// execute thread is:pool-1-thread-1
// execute thread is:pool-1-thread-1
// execute thread is:pool-1-thread-1
// execute thread is:pool-1-thread-5
// execute thread is:pool-1-thread-5
// execute thread is:pool-1-thread-4
// execute thread is:pool-1-thread-3
// execute thread is:pool-1-thread-2
// execute thread is:pool-1-thread-1
复制代码

从结果就可以看出来,执行时间操作,但是只创建了5个线程,另外5次都是复用线程的。这样就达到了复用存在的线程、减少对象的创建和销毁的额外开销;并且可以控制最大线程数,也就是控制了最大并发数。

知道如何使用一个线程池还不够,我们需要看看ThreadPoolExecutor是如何创建、复用这些线程的。下面我们看看创建ThreadPoolExecutor对象的几个参数:

构造方法

    /**
     * 创建一个ThreadPoolExecutor对象
     *
     * @param corePoolSize 核心线程数,这些线程会一直在线程池中,除非设置了 allowCoreThreadTimeOut
     * @param maximumPoolSize 最大线程数,运行线程创建的最大值
     * @param keepAliveTime 当线程数>核心线程数的时候,这个值就是空闲且非核心线程存活的时间
     * @param unit keepAliveTime的单位
     * @param workQueue 保存task的队列,直到执行execute()方法执行
     * @param threadFactory ThreadFactory是一个接口,里面只有Thread newThread(Runnable r)方法,用来创建线程,
     *                      默认采用Executors.defaultThreadFactory()
     * @param handler 拒绝处理任务时的策略,如果线程池满了且所有线程都不处于空闲状态,
     *                通过RejectedExecutionHandler接口的rejectedExecution(Runnable r, ThreadPoolExecutor executor)来处理传进来的Runnable
     *                系统提供了四种:CallerRunsPolicy(), AbortPolicy(), DiscardPolicy(), DiscardOldestPolicy()
     *                默认采用new AbortPolicy()
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler){
        if (corePoolSize < 0 ||
                maximumPoolSize <= 0 ||
                maximumPoolSize < corePoolSize ||
                keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
复制代码

我在方法头注释中我都一一解释了几个参数的作用,还有几点需要注意的就是:

  • 核心线程数不能小于0;
  • 最大线程数不能小于0;
  • 最大线程数不能小于核心线程数;
  • 空闲线程的存活时间不能小于0;

通过上面的解释我们很明白的知道前面几个参数的作用,但是最后两个参数我们并不能通过表面的解释通晓它,既然不能通过表象看懂他俩,那就看看默认的实现是如何做的,这样在接下来的源码分析中很有帮助。

ThreadFactory:线程工厂

ThreadFactory 是一个接口,里面只由唯一的 Thread newThread(Runnable r); 方法,此方法是用来创建线程的,从接口中我们得到的就只有这么多,下面我们看看 Executors 默认的 DefaultThreadFactory 类:

// 静态内部类
static class DefaultThreadFactory implements ThreadFactory {
    // 线程池的标识,从1开始没创建一个线程池+1
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    // 线程组
    private final ThreadGroup group;
    // 线程名中的结尾标识,从1开始每创建一个线程+1
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    // 线程名
    private final String namePrefix;

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}
复制代码

RejectedExecutionHandler:拒绝处理任务的策略

RejectedExecutionHandler 也是一个接口,并且也只提供了唯一的 void rejectedExecution(Runnable r, ThreadPoolExecutor executor); 方法。我们可以自定义策略,也可以用上面提到的封装好的四种策略,先看一下四种策略分别怎么拒绝任务的:

  • CallerRunsPolicy

    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() {
        }
    
        /**
         * 如果线程池还没关闭,那么就再次执行这个Runnable
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }
    复制代码
  • AbortPolicy

    public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() {
        }
    
        /**
         * 这个策略就是抛出异常,不做其他处理
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                    " rejected from " +
                    e.toString());
        }
    }
    复制代码
  • DiscardPolicy

    public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() {
        }
    
        /**
         * 什么也不做,也就是抛弃了这个Runnable
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }
    复制代码
  • DiscardOldestPolicy

    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() {
        }
    
        /**
         * 1. 线程池未关闭
         * 2. 获取队列中的下一个Runnable
         * 3. 获取到了,但是不对它进行处理,也就是抛弃它
         * 4. 执行我们传过来的这个Runnable
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }
    复制代码

重要的参数

除了上述构造方法中的几个参数外,线程池还有几个比较核心的参数,如下:

public class ThreadPoolExecutor extends AbstractExecutorService {

    // ctl 的低29位表示线程池中的线程数,高3位表示当前线程状态
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    // 29
    private static final int COUNT_BITS = Integer.SIZE - 3;
    // (2^29) -1
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // 运行状态:接受新任务并处理排队的任务
    private static final int RUNNING    = -1 << COUNT_BITS;
    // 关闭状态:不接受新任务,但处理排队的任务
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    // 停止状态:不接受新任务,不处理排队的任务,中断正在进行的任务
    private static final int STOP       =  1 << COUNT_BITS;
    // 整理状态:整理状态,所有任务已终止,workerCount为零,线程将运行terminate()方法
    private static final int TIDYING    =  2 << COUNT_BITS;
    // 终止状态:terminate()方法执行完成
    private static final int TERMINATED =  3 << COUNT_BITS;

    // 表示线程是否允许或停止
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    // 线程的有效数量
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }
    
    ......后面的源码暂时省略
}
复制代码

execute:执行

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // 如果运行中的线程数小于核心线程数,执行addWorker(command, true)创建新的核心Thread执行任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 1. 已经满足:运行中的线程数大于核心线程数,但是小于最大线程数
    // 2. 需要满足:线程池在运行状态
    // 3. 需要满足:添加到工作队列中成功
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 如果线程不在运行状态,就从工作队列中移除command
        // 并且执行拒绝策略
        if (!isRunning(recheck) && remove(command))
            reject(command);
        // 线程池处于运行状态,但是没有线程,则addWorker(null, false)
        // 至于这里为什么要传入一个null,因为在最外层的if条件中我们已经将Runnable添加到工作队列中了
        // 而且在runWorker()源码中也可以得到答案,如果传入的Runnable为空,就会去工作队列中取task。
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 执行addWorker()创建新的非核心线程Thread执行任务
    // addWorker() 失败,执行拒绝策略
    else if (!addWorker(command, false))
        reject(command);
}
复制代码

从上面源码中可以看出,execute()一个新的任务,主要有以下这几种情况:

  1. 核心线程未满,直接新建核心线程并执行任务;
  2. 核心线程满了,工作队列未满,将任务添加到工作队列中;
  3. 核心线程和工作队列都满,但是最大线程数未达到,新建线程并执行任务;
  4. 上面条件都不满足,那么就执行拒绝策略。

更形象的可以看下方流程图:

添加任务的流程图

addWorker(Runnable , boolean):添加Worker

private boolean addWorker(Runnable firstTask, boolean core) {
    // 标记外循环,比如在内循环中break retry就直接跳出外循环
    retry:
    for (; ; ) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // 直接返回false有以下3种情况:
        // 1. 线程池状态为STOP、TIDYING、TERMINATED
        // 2. 线程池状态不是running状态,并且firstTask不为空
        // 3. 线程池状态不是running状态,并且工作队列为空
        if (rs >= SHUTDOWN &&
                !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
            return false;

        for (; ; ) {
            int wc = workerCountOf(c);
            // 如果添加的是核心线程,但是运行的线程数大于等于核心线程数,那么就不添加了,直接返回
            // 如果添加的是非核心线程,但是运行的线程数大于等于最大线程数,那么也不添加,直接返回
            if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 增加workerCount的值 +1
            if (compareAndIncrementWorkerCount(c))
                // 跳出外循环
                break retry;
            c = ctl.get();  // 重新检查线程池状态
            if (runStateOf(c) != rs)
                continue retry;
            // 重新检查的状态和之前不合,再次从外循环进入
        }
    }

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            // 线程池重入锁
            final ReentrantLock mainLock = this.mainLock;
            // 获得锁
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());
                // 线程池在运行状态或者是线程池关闭同时Runnable也为空
                if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    // 想Worker中添加新的Worker
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                // 释放锁
                mainLock.unlock();
            }
            // 如果添加成功,启动线程
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (!workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}
复制代码

addWorker() 主要就是在满足种种条件(上述源码中解释了)后,新建一个Worker对象,并添加到HashSet<Worker> workers中去,最后调用新建Worker对象的Thread变量的start()方法。

Worker类

Worker是一个继承了AQS并实现了Runnable的内部类,我们重点看看它的run()方法,因为上面addWorker()中,t.start()触发的就是它的run()方法:

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable {
    /**
     * This class will never be serialized, but we provide a
     * serialVersionUID to suppress a javac warning.
     */
    private static final long serialVersionUID = 6138294804551838833L;

    /**
     * Thread this worker is running in.  Null if factory fails.
     */
    final Thread thread;
    /**
     * Initial task to run.  Possibly null.
     */
    Runnable firstTask;
    /**
     * Per-thread task counter
     */
    volatile long completedTasks;

    /**
     * Creates with given first task and thread from ThreadFactory.
     *
     * @param firstTask the first task (null if none)
     */
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        // 这边是把Runnable传给了Thread,也就是说Thread.run()就是执行了下面的run()方法
        this.thread = getThreadFactory().newThread(this);
    }

    /**
     * Delegates main run loop to outer runWorker
     */
    public void run() {
        runWorker(this);
    }
}
复制代码

run()方法实际调用了runWorker(Worker)方法

runWorker(Worker)方法:

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // 释放锁,允许中断
        boolean completedAbruptly = true;
        try {
            // 1. worker中的task不为空
            // 2. 如果worker的task为空,那么取WorkerQueue的task
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                        (Thread.interrupted() &&
                                runStateAtLeast(ctl.get(), STOP))) &&
                        !wt.isInterrupted())
                    wt.interrupt();
                try {
                    // 这是一个空方法,可由子类实现
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        // 执行task
                        task.run();
                    } 
                    .... 省略
                    // 这是一个空方法,可由子类实现
                    finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }
复制代码

getTask():

```java
private Runnable getTask() {
    // 进入死循环
    for (; ; ) {
        try {
            // 为true的条件:
            // allowCoreThreadTimeOut=true: 核心线程需根据keepAliveTime超时等待
            // 核心线程数已满
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            // 如果timed为true,执行BlockQueue.poll(),这个操作在取不到task的时候会等待keepAliveTime,然后返回null
            // 如果timed为false,执行BlockQueue.take(),这个操作在队列为空的时候一直阻塞
            Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
            if (r != null)
                return r;
        }
    }
}
```
复制代码

线程池的源码按照上述的几个方法(execute(runnable) -> addWorker(runnable,core) -> Worker -> runWorker(worker) -> getTask())的顺序来分析,你就可以很清晰的将运作过程了解清楚,同事构造方法和几个重要的参数一定要懂,不然对于后面的源码分析很受阻碍,相信大家通过这篇文章可以加深对线程池的理解。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

线程池源码剖析 的相关文章

  • AudioTrack分析

    第一部分 AudioTrack分析 一 目的 本文的目的是通过从Audio系统来分析Android的代码 xff0c 包括Android自定义的那套机制和一些常见类的使用 xff0c 比如Thread xff0c MemoryBase等 分
  • git 报错信息:SSL certificate problem: certificate has expired 解决方案

    执行命令 git config global http sslVerify false 再次执行 git pull 成功拉取
  • 获取本机号码及sim卡信息

    一 SIM卡存储的数据可分为四类 xff0c 它们分别是 xff1a 第一类是固定存放的数据 这类数据在移动电话机被出售之前由SIM卡中心写入 xff0c 包括国际移动用户识别号 xff08 IMSI xff09 鉴权密钥 xff08 KI
  • Socket粘包问题的3种解决方案

    在 Java 语言中 xff0c 传统的 Socket 编程分为两种实现方式 xff0c 这两种实现方式也对应着两种不同的传输层协议 xff1a TCP 协议和 UDP 协议 xff0c 但作为互联网中最常用的传输层协议 TCP xff0c
  • Android aar包的使用 打包aar后报错ClassNotFoundException,aar中有dependencies依赖的情况;

    示例 xff1a 现有A xff0c B两个library module都是本地的 xff0c A引用B xff0c 我把A打成了aar库 xff0c 打出来之后却没有B的内容 xff0c 导致我的主工程引用A之后 xff0c 执行某个应用
  • MFC架构下的DirectX8

    MFC架构下的DirectX8 第一章 MFC框架 DX8MFC 这里的MFC框架指的是一个符合游戏开发应用的框架 xff0c 当然你也可以写一个符合你要求的MFC框架 如果你对MFC比较熟悉的话可以直接从第二章开始阅读 本框架是以后几个例
  • 解决:There is no tracking information for the current branch. Please specify which branch you want to

    报警信息 xff1a There is no tracking information for the current branch Please specify which branch you want to rebase agains
  • git删除远程分支和本地分支

    1 xff09 使用命令git branch a 查看所有分支 注 xff1a 其中 xff0c remote origin master表示的是远程分支 xff08 2 xff09 删除远程分支 注 xff1a 如上所示 xff0c 使用
  • Java 集合 List 与 Array 的转换

    List 转 Array 使用集合转数组的方法 xff0c 必须使用集合的 toArray T array xff0c 传入的是类型完全一样的数组 xff0c 大小就是 list size 反例 xff1a 直接使用 toArray 无参方
  • C语言中.h和.c文件解析

    简单的说其实要理解C文件与头文件 xff08 即 h xff09 有什么不同之处 xff0c 首先需要弄明白编译器的工作过程 xff0c 一般说来编译器会做以下几个过程 xff1a 1 预处理阶段 2 词法与语法分析阶段 3 编译阶段 xf
  • CMake 使用

    1 前言 当在做 Android NDK 开发时 xff0c 如果不熟悉用 CMake 来构建 xff0c 读不懂 CMakeLists txt 的配置脚本 xff0c 很容易就会踩坑 xff0c 遇到编译失败 xff0c 一个很小的配置问
  • Android 存储优化 —— MMKV 集成与原理

    前言 APP 的性能优化之路是永无止境的 这里学习一个腾讯开源用于提升本地存储效率的轻量级存储框架 MMKV 目前项目中在轻量级存储上使用的是 SharedPreferences 虽然 SP 兼容性极好 但 SP 的低性能一直被诟病 线上也
  • Android JNI(一)——NDK与JNI基础

    本片文章大纲如下 xff1a 1 导读2 什么是NDK3 为什么使用NDK4 NDK到SO5 JNI 一 导读 在Android OS上开发应用程序 xff0c Google提供了两种开发包 xff1a SDK和NDK 你可以从Google
  • Android JNI学习(四)——JNI的常用方法的中文API

    一 Interface Function Table 接口函数表 每个函数都可以通过JNIEnv参数访问 xff0c JNIEnv类型是指向一个存放所有JNI接口指针的指针 xff0c 其定义如下 xff1a typedef const s
  • 详解android项目配置签名文件的完整流程

    1 背景 接手的项目近期需要上线 xff0c 于是复习了一下项目签名文件配置流程 xff0c 这里做个系统性总结 2 最终目标 根据需求为debug包与release包配置签名文件 xff0c 快速满足中小型项目的需要 3 创建签名文件 要
  • ArrayList循环删除陷阱及迭代器介绍

    一 ArrayList循环删除陷阱 模板测试代码如下 xff1a public class ArrayListRemove public static void main String args ArrayList lt String gt
  • 启动加载器BootLoader

    启动加载器 使用u boot在qemu上模拟执行u boot命令 在嵌入式操作系统中 xff0c BootLoader是在操作系统内核运行之前运行 可以初始化硬件设备 建立内存空间映射图 xff0c 从而将系统的软硬件环境带到一个合适状态
  • EventBus3.0详细分析

    在进入主题之前 xff0c 我们先保持着这样几个疑问 xff0c EventBus的使用三要素里 xff0c 我们为什么要去定义事件方法 xff0c 并且用到了 64 subscribe 注解 xff1f EventBus getDefau
  • git 拉取远程分支到本地

    步骤 xff1a 1 新建一个空文件 xff0c 文件名为file 2 初始化 git init 3 自己要与origin master建立连接 xff08 下划线为远程仓库链接 xff09 git remote add origin gi
  • module中依赖arr

    在高版本的AndroidStudio并且使用了版本的gradle出现了上述问题可以按着如下引用 在你工程根目录下新建一个文件夹YouLib xff0c 将你的aar文件放入 xff0c 然后在该目录下新建一个build gradle文件 在

随机推荐

  • Android Studio aar包引用方式

    主工程 第一种方式 1 将aar包复制到app libs目录下 2 在app的build gradle文件中配置如下 xff1a android repositories flatDir dirs 39 libs 39 implementa
  • MacOs “无法打开***,因为无法验证开发者...”

    在终端执行如下指令 sudo spctl master disable 执行完上面指令后 xff0c 在 安全性与隐私 设置的 允许从以下位置下载的App 中会新增一个任何来源 xff0c 如下面的对比图 xff0c 然后应用在运行中就不会
  • mac编译android源码-创建磁盘映像

    因为mac默认的磁盘环境是不区分大小的 xff0c 而git并不支持此类文件系统 xff0c 所以我们需要创建我们所需要的磁盘映像用来存放下载的源码 首先你需要找在mac上的磁盘工具 xff0c 一般是在应用程序列表 其他文件夹里面 2 如
  • android源码编译-如何在Mac中卸载openjdk15

    说明 之前在mac上使用intellij idea时 xff0c 由于没有在Mac上安装过jdk xff0c 所以就在intellij idea中下载了openjdk15版本 后来觉得想要换一个旧点的版本 xff0c 就想卸载了openjd
  • Mac OS查看和设置JAVA_HOME

    下载java https www java com zh CN download 1 查看JAVA版本 打开Mac电脑 xff0c 查看JAVA版本 xff0c 打开终端Terminal xff0c 通过命令行查看笔者的java版本 xff
  • Android源码编译–jdk版本查询

    2 1 Android源码所需JDK版本 根参考资料 1 的说明 xff0c 在android src build core main mk中对jdk的版本进行查询 xff0c 以确定当前系统是否安装了特定版本的jdk xff0c 因此可以
  • android源码编译 ninja: build stopped: subcommand failed.

    接着编译 make j8 线程加多少个具体看机器配置 xff0c 问题也最可能是这一步骤引起的 xff0c 如果是虚拟机的话 xff0c 建议不要加线程 xff0c 直接使用make执行
  • Ubuntu环境下完美安装python模块numpy,scipy,matplotlib

    不同的ubuntu版本安装过这三个模块几次了 xff0c 然而总是出现各种问题 xff0c 最近一次是在ubuntu 16 04 LTS server版本安装的 xff0c 总的来说安装的比较顺利 先把pip安装好 sudo apt get
  • prebuilts/misc/darwin-x86/bison/bison: Bad CPU type in executable

    方案一 cd external bison touch patch high sierra patch vim patch high sierra patch With format string strictness High Sierr
  • android源码编译 坑

    bash lunch command not found 先调用 build envsetup sh 再执行 lunch Can not find SDK Can not find SDK 10 6 at Developer SDKs Ma
  • 获取当前MacOSX SDK

    xcrun show sdk path 打印出 Library Developer CommandLineTools SDKs MacOSX sdk xcrun show sdk version 打印出 10 15 4 xcode sele
  • Mac OS10.12 编译Android源码8.1

    内容 介绍mac os10 12拉取android源码 xff0c 并且编译后 xff0c 刷入手机的过程 下载的rom是android 8 1 xff0c 手机是pixel 准备工作 硬盘大小 本人Mac磁盘空间只有256GB xff0c
  • android源码 xcode版本,【Android】AOSP源码下载及编译 for mac

    本文记录了AOSP在Mac系统上下载和编译的过程 采用的系统是 macOS 10 13 1 所使用的AOSP分支是 android 8 1 0 r7 系统预留空间 大于200G 一 环境配置 环境配置 xff0c 官网给出了非常全的教程 x
  • (Android 9.0)Activity启动流程源码分析

    前言 熟悉Activity的启动流程和运行原理是一个合格的应用开发人员所应该具备的基本素质 xff0c 其重要程度就不多做描述了 同时 xff0c 知识栈应该不断的更新 xff0c 最新发布的Android 9 0版本相较于之前的几个版本也
  • Lifecycle 源码详解

    Lifecycle 是 Jetpack 整个家族体系内最为基础的内容之一 xff0c 正是因为有了 Lifecycle 的存在 xff0c 使得如今开发者搭建依赖于生命周期变化的业务逻辑变得简单高效了许多 xff0c 使得我们可以用一种统一
  • git常用命令

    1 拉取远程所有分支 git clone xxx git branch r grep v 39 gt 39 while read remote do git branch track 34 remote origin 34 34 remot
  • Android应用启动流程分析

    1 前言 网上看过很多Activity启动过程的源码解析 xff0c 很多文章会贴上一大段代码 xff0c 然后从startActivity 函数开始深究整个源码的调用栈 个人感觉这类文章代码细节太多 xff0c 反而容易迷失在源码调用之中
  • 从一个分支cherry-pick多个commit到其他分支

    在branch1开发 xff0c 进行多个提交 xff0c 这是切换到branch2 xff0c 想把之前branch1分支提交的commit都 复制 过来 xff0c 怎么办 xff1f 单个commit只需要git cherry pic
  • IntWritable详解

    1 Hadoop数据类型如下图 xff1a 由上图的Writable层次结构图可以看到绝大多数的数据类型都实现了Writable WritableComparable接口 xff0c 在此先分析一下这两个接口情况 自顶下下逐步分析 Writ
  • 线程池源码剖析

    线程池 xff08 英语 xff1a thread pool xff09 xff1a 一种线程使用模式 线程过多会带来调度开销 xff0c 进而影响缓存局部性和整体性能 而线程池维护着多个线程 xff0c 等待着监督管理者分配可并发执行的任