Android 线程&线程池

目录

一.线程形态

1.1.HandlerThread

1.1.1.简介

1.1.2.适用场景

1.1.3.DEMO

1.2.IntentService

1.2.1.简介

1.2.2.适用场景

1.2.3.DEMO

1.2.4.总结

1.3.RxAsyncTask

二.线程池ThreadPoolExecutor

2.1.为什么要用线程池

2.2.线程池逻辑构成

2.3.线程池参数

2.3.1.int corePoolSize => 该线程池中核心线程数最大值

2.3.2.int maximumPoolSize => 该线程池中线程总数最大值

2.3.3.long keepAliveTime => 该线程池中非核心线程闲置超时时长

2.3.4.TimeUnit unit => keepAliveTime 的单位

2.3.5.BlockingQueue workQueue => 该线程池中的任务队列

2.3.6.ThreadFactory threadFactory

2.3.7.RejectedExecutionHandler handler

2.4.使用

2.5.线程池等待队列

2.5.1.有限队列

2.5.2.无限队列

2.6.常见线程池

2.6.1.CachedThreadPool

2.6.2.FixedThreadPool

2.6.3.ScheduledThreadPool

2.6.4.SingleThreadExecutor


一.线程形态

1.1.HandlerThread

1.1.1.简介

HandlerThread继承了Thread,是一个包含 Looper 的消息循环的 Thread,我们可以直接使用这个 Looper 创建 Handler。在run方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环,避免在子线程中对Looper手动繁琐的操作(Looper.prepare() ——> Looper.loop()),让我们可以直接在线程中使用 Handler 来处理异步任务。

(1)HandlerThread本身是一个Thread,需要start()启动。

(2)HandlerThread在run() 方法中为本线程创建了Looper(Looper.prepare()),调用 onLooperPrepared 后开启了循环(Looper.loop())

(3)HandlerThread需要在子类中重写 onLooperPrepared,做Looper启动前的初始化工作

(4)HandlerThread可以指定优先级,注意这里的参数是 Process.XXX 而不是 Thread.XXX

(5)HandlerThread = Thread + Looper,适合在子线程中执行耗时的、可能有多个任务的操作的场景,比如说多个网络请求操作,或者多文件 I/O 等等。

个人理解:HandlerThread是一个可以通过Looper + Message 实现在子线程(Looper 和 MessageQueue在子线程,所以Looper循环消息,触发消息处理方法也是在子线程)中依次循环执行任务的线程类。

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    private @Nullable Handler mHandler;
    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    /**
     * Constructs a HandlerThread.
     * @param name
     * @param priority The priority to run the thread at. The value supplied must be from
     * {@link android.os.Process} and not from java.lang.Thread.
     */
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    /**

     * 子类需要重写的方法,在这里做一些执行前的初始化工作
     * Call back method that can be explicitly overridden if needed to execute some
     * setup before Looper loops.
     */
    protected void onLooperPrepared() {
    }

//调用 start() 后就会执行的 run()
    @Override
    public void run() {
        mTid = Process.myTid();

   //创建了 Looepr
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();//Looper 已经创建,唤醒阻塞在获取 Looper 的线程
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();//开始循环
        mTid = -1;
    }
    /**

     * 获取当前线程的 Looper

     * 如果线程不是正常运行的就返回 null

     * 如果线程启动后,Looper 还没创建,就 wait() 等待 创建 Looper 后 notify
     * This method returns the Looper associated with this thread. If this thread not been started
     * or for any reason isAlive() returns false, this method will return null. If this thread
     * has been started, this method will block until the looper has been initialized. 
     * @return The looper.
     */
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
    /**
     * @return a shared {@link Handler} associated with this thread
     * @hide
     */
    @NonNull
    public Handler getThreadHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }
    /**
     * Quits the handler thread's looper.
     * @return True if the looper looper has been asked to quit or false if the
     * thread had not yet started running.
     *
     * @see #quitSafely
     */
    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }
    /**
     * Quits the handler thread's looper safely.
     * @return True if the looper looper has been asked to quit or false if the
     * thread had not yet started running.
     */
    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }
    /**
     * Returns the identifier of this thread. See Process.myTid().
     */
    public int getThreadId() {
        return mTid;
    }
}

1.1.2.适用场景

为某个任务 / 回调单独开启线程,并提供由Handler + Looper组成的任务(Message)调度机制。HandlerThread = Thread + Looper,适合在子线程中执行耗时的、可能有多个任务的操作的场景,比如说多个网络请求操作,或者多文件 I/O 等等。

1.1.3.DEMO

使用 HandlerThread 实现子线程完成多个下载任务。

DownloadThread,它有两个 Handler 类型的成员变量,一个是用于在子线程传递、执行任务,另一个用于外部传入,在主线程显示下载状态:

public class DownloadThread extends HandlerThread implements Handler.Callback {
    private final String KEY_URL = "url";
    public static final int TYPE_START = 1;
    public static final int TYPE_FINISHED = 2;
    /**
     * 外部传入,通知主线程显示下载状态
     */
    private Handler mUIHandler;
    /**
     * 内部创建,子线程传递、执行任务
     */
    private Handler mWorkerHandler;
    /**
     * download list
     */
    private List<String> mDownloadUrlList;
    public DownloadThread(final String name) {
        super(name);
    }
    /**
     * 执行初始化任务
     */
    @Override
    protected void onLooperPrepared() {
        super.onLooperPrepared();
        mWorkerHandler = new Handler(getLooper(), this); //使用子线程中的 Looper
        if (mUIHandler == null) {
            throw new IllegalArgumentException("Not set UIHandler!");
        }
        // 将接收到的任务消息挨个添加到消息队列中
        for (String url : mDownloadUrlList) {
            Message message = mWorkerHandler.obtainMessage();
            Bundle bundle = new Bundle();
            bundle.putString(KEY_URL, url);
            message.setData(bundle);
            mWorkerHandler.sendMessage(message);
        }
    }
    public void setDownloadUrls(String... urls) {
        mDownloadUrlList = Arrays.asList(urls);
    }
    /**
     * 获取主线程 Handler
     */
    public Handler getUIHandler() {
        return mUIHandler;
    }
    /**
     * 注入主线程 Handler
     */
    public DownloadThread setUIHandler(final Handler UIHandler) {
        mUIHandler = UIHandler;
        return this;
    }
    /**
     * 子线程中执行任务,完成后发送消息到主线程
     */
    @Override
    public boolean handleMessage(final Message msg) {
        if (msg == null || msg.getData() == null) {
            return false;
        }
        String url = (String) msg.getData().get(KEY_URL);
        //下载开始,通知主线程
        Message startMsg = mUIHandler.obtainMessage(TYPE_START, "\n 开始下载 @" + url);
        mUIHandler.sendMessage(startMsg);
        SystemClock.sleep(2000);    //模拟下载
        //下载完成,通知主线程
        Message finishMsg = mUIHandler.obtainMessage(TYPE_FINISHED, "\n 下载完成 @" + url);
        mUIHandler.sendMessage(finishMsg);
        return true;
    }
    @Override
    public boolean quitSafely() {
        mUIHandler = null;
        return super.quitSafely();
    }
}

创建一个子线程 mWorkerHandler,在 onLooperPrepared()中初始化 Handler,使用的是 HandlerThread 创建的 Looper 。同时将外部传入的下载 url 以 Message 的方式发送到子线程中的 MessageQueue 中。

当调用 DownloadThread.start() 时,子线程中的 Looper 开始工作,会按顺序取出消息队列中的队列处理,然后调用子线程的 Handler 处理(handleMessage()),在这个方法中进行耗时任务,然后通过 mUIHandler 将下载状态信息传递到主线程

在外部Activity(Fragment)实现Hander.Callback接口,调用:

mUIHandler = new Handler(this);
mDownloadThread = new DownloadThread("下载线程");
mDownloadThread.setUIHandler(mUIHandler);
mDownloadThread.setDownloadUrls("http://pan.baidu.com/s/1qYc3EDQ",
        "http://bbs.005.tv/thread-589833-1-1.html",
        "http://list.youku.com/show/id_zc51e1d547a5b11e2a19e.html?");
mDownloadThread.start();

同时在Activiy的onDestroy()方法中:

mDownloadThread.quitSafely();

1.2.IntentService

1.2.1.简介

IntentService 是一个服务,同时是一个抽象类,继承自普通Service同时又在内部创建了一个HandlerThread 。IntentService 使用工作线程逐一处理所有启动请求。如果你不需要在 Service 中执行并发任务,IntentService 是最好的选择。

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;
    // 内部创建的 Handler
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message msg) {
            // 调用这个方法处理数据
            onHandleIntent((Intent) msg.obj);
            // 处理完Service就自尽了
            stopSelf(msg.arg1);
        }
    }
    // 子类需要重写的构造函数,参数是服务的名称
    public IntentService(String name) {
        super();
        mName = name;
    }
    // 设置当前服务被意外关闭后是否重新启动
    // 如果设置为 true,onStartCommand() 方法将返回 Service.START_REDELIVER_INTENT,这样当
    // 当前进程在 onHandleIntent() 方法返回前销毁时,会重启进程,重新使用之前的 Intent 启动这个服务
    // (如果有多个 Intent,只会使用最后的一个)
    // 如果设置为 false,onStartCommand() 方法返回 Service.START_NOT_STICKY,当进程销毁后也不重启服务
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        // 创建时启动一个 HandlerThread
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
        // 拿到 HandlerThread 中的 Looper,然后创建一个子线程中的 Handler
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        // 将 intent 和 startId 以消息的形式发送到 Handler
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }
    /**
     * You should not override this method for your IntentService. Instead,
     * override {@link #onHandleIntent}, which the system calls when the IntentService
     * receives a start request.
     *
     * @see android.app.Service#onStartCommand
     */
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }
    @Override
    public void onDestroy() {
        mServiceLooper.quit();    //值得学习的,在销毁时退出 Looper
    }
    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

(1)创建了一个 HandlerThread 为默认的工作线程

(2)使用 HandlerThread 的 Looper 创建了一个 Handler,这个 Handler 执行在子线程

(3)在 onStartCommand() 中调用 onStart(),然后在 onStart() 中将 intent 和 startId 以消息的形式发送到 ServiceHandler

(4)ServiceHandler处理消息时(handleMessage),将消息中的msg.objIntent按顺序传递给 onHandleIntent() 方法

(5)在处理完所有启动请求后自动停止服务,不需要我们调用 stopSelf()

那么在 handleMessage 方法中不是调用了一次 onHandleIntent() 后就调用 stopSelf() 了吗,这不是只能执行一个任务么?

stopSelf() 方法传递了一个 id,这个 id 是启动服务时 IActivityManager 分配的 id,当我们调用 stopSelf(id) 方法结束服务时,IActivityManager 会对比当前 id 是否为最新启动该服务的 id,如果是就关闭服务。

子线程中onHandleIntent方法经历了耗时操作,当本次stopSelf(id)调用之前,很可能onStartCommand会多次调用,那么本次传入的id,已经不是最新启动服务的id了。

因此只有当最后一次启动 IntentService 的任务执行完毕才会关闭这个服务。

注意:只有onHandleIntent方法是执行在子线程的,因为处理消息的looper是子线程HandlerThread提供的looper。其余方法均在主线程运行。

1.2.2适用场景

IntentService可用于执行后台耗时的任务,当任务执行后它会自动停止,同时由于IntentService是服务的原因,这导致他的优先级比单纯的线程要高很多,所以IntentService比较适合执行一些高优先级的后台任务。

1.2.3.DEMO

1.多文件下载:

由于最终每个任务的处理都会调用 onHandleIntent(),因此使用 IntentService 也很简单,只需实现 onHandleIntent() 方法,在这里执行对应的后台工作即可。

/**
 * Description:使用 IntentService 实现下载
 */
public class DownloadService extends IntentService {
    private static final String TAG = "DownloadService";
    public static final String DOWNLOAD_URL = "down_load_url";
    public static final int WHAT_DOWNLOAD_FINISHED = 1;
    public static final int WHAT_DOWNLOAD_STARTED = 2;
    public DownloadService() {
        super(TAG);
    }
    private static Handler mUIHandler;
    public static void setUIHandler(final Handler UIHandler) {
        mUIHandler = UIHandler;
    }
    /**
     * 这个方法运行在子线程
     *
     * @param intent
     */
    @Override
    protected void onHandleIntent(final Intent intent) {
        String url = intent.getStringExtra(DOWNLOAD_URL);
        if (!TextUtils.isEmpty(url)) {
            sendMessageToMainThread(WHAT_DOWNLOAD_STARTED, "\n " + DateUtils.getCurrentTime() + " 开始下载任务:\n" + url);
            try {
                Bitmap bitmap = downloadUrlToBitmap(url);
                SystemClock.sleep(1000);    //延迟一秒发送消息
                sendMessageToMainThread(WHAT_DOWNLOAD_FINISHED, bitmap);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 发送消息到主线程
     *
     * @param id
     * @param o
     */
    private void sendMessageToMainThread(final int id, final Object o) {
        if (mUIHandler != null) {
            mUIHandler.sendMessage(mUIHandler.obtainMessage(id, o));
        }
    }
    /**
     * 下载图片
     *
     * @param url
     * @return
     * @throws Exception
     */
    private Bitmap downloadUrlToBitmap(String url) throws Exception {
        HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
        BufferedInputStream in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
        Bitmap bitmap = BitmapFactory.decodeStream(in);
        urlConnection.disconnect();
        in.close();
        return bitmap;
    }
}

在 onHandleIntent() 中接收任务,开始下载,同时将状态返回给主线程

Activity主调代码:

public void downloadImage() {
    DownloadService.setUIHandler(new Handler(this));
    Intent intent = new Intent(this, DownloadService.class);
    for (String url : urlList) {
        intent.putExtra(DownloadService.DOWNLOAD_URL, url);
        startService(intent);
    }
    mBtnDownload.setEnabled(false);
}

主线程中:

(1)设置 UI 线程的 Handler 给 IntentService

(2)使用 startService(intent) 启动 IntentService 执行图片下载任务

(3)在 Handler 的 handleMessage 中根据消息类型进行相应处理

在第一次启动 IntentService 后,IntentService 仍然可以接受新的请求,接受到的新的请求被放入了工作队列中,等待被串行执行。

2.使用广播更新UI:

public class MyIntentService extends IntentService {
    /**
     * 是否正在运行
     */
    private boolean isRunning;
    /**
     *进度
     */
    private int count;
    /**
     * 广播
     */
    private LocalBroadcastManager mLocalBroadcastManager;
    public MyIntentService() {
        super("MyIntentService");
        Logout.e("MyIntentService");
    }
    @Override
    public void onCreate() {
        super.onCreate();
        Logout.e("onCreate");
        mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        Logout.e("onHandleIntent");
        try {
            Thread.sleep(1000);
            isRunning = true;
            count = 0;
            while (isRunning) {
                count++;
                if (count >= 100) {
                    isRunning = false;
                }
                Thread.sleep(50);
                sendThreadStatus("线程运行中...", count);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    /**
     * 发送进度消息
     */
    private void sendThreadStatus(String status, int progress) {
        Intent intent = new Intent(IntentServiceActivity.ACTION_TYPE_THREAD);
        intent.putExtra("status", status);
        intent.putExtra("progress", progress);
        mLocalBroadcastManager.sendBroadcast(intent);
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        Logout.e("线程结束运行..." + count);
    }
}

1.2.4.总结

IntentService是一个串行执行异步任务、会自尽的 Service,优先级比较高,在后台不会轻易被系统杀死;它可以接收多个 Intent 请求,然后在子线程中按顺序执行。

适合于执行由UI触发的处理多任务的后台Service任务,并可以把后台任务执行的情况通过一定的机制反馈给UI。

IntentService,在onHandlerIntent()的回调里面来处理塞到IntentService的任务。所以IntentService就不仅仅具备了异步线程的特性,还同时保留了Service不受主页面生命周期影响的特点。我们可以在IntentService里面通过设置闹钟间隔性的触发异步任务,例如刷新数据,更新缓存的图片等。

首先,因为IntentService内置的是HandlerThread作为异步线程,所以每一个交给IntentService的任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。

其次,通常使用到IntentService的时候,我们会结合使用BroadcastReceiver把工作线程的任务执行结果返回给主UI线程。使用广播容易引起性能问题,我们可以使用LocalBroadcastManager来发送只在程序内部传递的广播,从而提升广播的性能。我们也可以使用runOnUiThread()快速回调到主UI线程。

最后,包含正在运行的IntentService的程序相比起纯粹的后台程序更不容易被系统杀死,该程序的优先级是介于前台程序与纯后台程序之间的。

1.3.RxAsyncTask

利用RxJava重新实现异步任务:

public abstract class RxAsyncTask<Param, Progress, Result> {
    private final String TAG = this.getClass().getSimpleName();
    private Flowable<Progress[]> mFlowable;
    public RxAsyncTask() {
    }
    @SafeVarargs
    private final void rxTask(final Param... params) {
        Flowable.create(new FlowableOnSubscribe<Result>() {
            @Override
            public void subscribe(FlowableEmitter<Result> e) throws Exception {
                e.onNext(RxAsyncTask.this.call(params));
                e.onComplete();
            }
        }, BackpressureStrategy.BUFFER).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Result>() {
                    @Override
                    public void onSubscribe(Subscription s) {
                        s.request(Long.MAX_VALUE);
                    }
                    @Override
                    public void onNext(Result result) {
                        RxAsyncTask.this.onResult(result);
                    }
                    @Override
                    public void onError(Throwable t) {
                        RxAsyncTask.this.onError(t);
                    }
                    @Override
                    public void onComplete() {
                        RxAsyncTask.this.onCompleted();
                    }
                });
    }
    protected abstract Result call(Param... params);
    /**
     * 任务开始之前调用(在当前调用者所在线程执行)
     */
    protected void onPreExecute() {
    }
    /**
     * 执行结果返回
     */
    protected void onResult(Result result) {
    }
    /**
     * 进度更新
     */
    protected void onProgressUpdate(Progress... progresses) {
    }
    /**
     * RxJava中的onComplete回调
     */
    protected void onCompleted() {
    }
    /**
     * RxJava中的onError回调
     */
    protected void onError(Throwable e) {
    }
    /**
     * 进度更新 子线程转主线程 回调给 onProgressUpdate()方法
     */
    @SuppressLint("CheckResult")
    protected void publishProgress(final Progress... progresses) {
        if (mFlowable == null) {
            mFlowable = Flowable.create(new FlowableOnSubscribe<Progress[]>() {
                @Override
                public void subscribe(FlowableEmitter<Progress[]> e) throws Exception {
                    e.onNext(progresses);
                }
            }, BackpressureStrategy.BUFFER).observeOn(AndroidSchedulers.mainThread());
        }
        mFlowable.subscribe(new Consumer<Progress[]>() {
            @Override
            public void accept(Progress[] progress) throws Exception {
                onProgressUpdate(progresses);
            }
        });
    }
    @SafeVarargs
    public final void execute(Param... params) {
        onPreExecute();
        rxTask(params);
    }
}

二.线程池ThreadPoolExecutor

2.1.为什么要用线程池

1.创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率

例如:

记创建线程消耗时间 T1,执行任务消耗时间 T2,销毁线程消耗时间 T3,如果 T1+T3>T2,那么是不是说开启一个线程来执行这个任务太不划算了!

线程池缓存线程,可用已有的闲置线程来执行新任务,避免了 T1+T3 带来的系统开销。

2.线程并发数量过多,抢占系统资源从而导致阻塞

我们知道线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况。运用线程池能有效的控制线程最大并发数,避免以上的问题。

3.对线程进行一些简单的管理

比如:延时执行、定时循环执行的策略等,运用线程池都能进行很好的实现。

大多数实际场景中是这样的:处理某一次请求的时间是非常短暂的,但是请求数量是巨大的。这种技术背景下,如果我们为每一个请求都单独创建一个线程,那么物理机的所有资源基本上都被操作系统创建线程、切换线程状态、销毁线程这些操作所占用,用于业务请求处理的资源反而减少了。所以最理想的处理方式是,将处理请求的线程数量控制在一个范围,既保证后续的请求不会等待太长时间,又保证物理机将足够的资源用于请求处理本身。

另外,一些操作系统是有最大线程数量限制的。当运行的线程数量逼近这个值的时候,操作系统会变得不稳定。这也是我们要限制线程数量的原因。

2.2.线程池逻辑构成

Java语言为我们提供了两种基础线程池的选择:ScheduledThreadPoolExecutor和ThreadPoolExecutor。它们都实现了ExecutorService接口(注意,ExecutorService接口本身和“线程池”并没有直接关系,它的定义更接近“执行器”)。

线程池逻辑结构:

构成线程池的几个重要元素:

等待队列:顾名思义,就是你调用线程池对象的submit()方法或者execute()方法,要求线程池运行的任务(这些任务必须实现Runnable接口或者Callable接口)。但是出于某些原因线程池并没有马上运行这些任务,而是送入一个队列等待执行。

核心线程:线程池主要用于执行任务的是“核心线程”,“核心线程”的数量是你创建线程时所设置的corePoolSize参数决定的。如果不进行特别的设定,线程池中始终会保持corePoolSize数量的线程数(不包括创建阶段)。

非核心线程:一旦任务数量过多(由等待队列的特性决定),线程池将创建“非核心线程”临时帮助运行任务。你设置的大于corePoolSize参数小于maximumPoolSize参数的部分,就是线程池可以临时创建的“非核心线程”的最大数量。这种情况下如果某个线程没有运行任何任务,在等待keepAliveTime时间后,这个线程将会被销毁,直到线程池的线程数量重新达到corePoolSize

2.3.线程池参数

在 Java 中,线程池的概念是 Executor 这个接口,具体实现为 ThreadPoolExecutor类。对线程池的配置,就是对 ThreadPoolExecutor 构造函数的参数的配置。

ThreadPoolExecutor提供了四个构造方法,共7个可选参数:

2.3.1.int corePoolSize => 该线程池中核心线程数最大值

核心线程:

线程池新建线程的时候,如果当前线程总数小于 corePoolSize,则新建的是核心线程,如果超过 corePoolSize,则新建的是非核心线程。

核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。

如果指定 ThreadPoolExecutor 的 allowCoreThreadTimeOut 这个属性为 true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉。

很好理解吧,正常情况下你不干活我也养你,因为我总有用到你的时候,但有时候特殊情况(比如我自己都养不起了),那你不干活我就要把你干掉了。

2.3.2.int maximumPoolSize => 该线程池中线程总数最大值

线程总数 = 核心线程数 + 非核心线程数。

2.3.3.long keepAliveTime => 该线程池中非核心线程闲置超时时长

一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉如果设置allowCoreThreadTimeOut = true,则会作用于核心线程。

2.3.4.TimeUnit unit => keepAliveTime 的单位

TimeUnit 是一个枚举类型,其包括:

NANOSECONDS : 1微毫秒 = 1微秒 / 1000

MICROSECONDS : 1微秒 = 1毫秒 / 1000

MILLISECONDS : 1毫秒 = 1秒 /1000

SECONDS : 秒

MINUTES : 分

HOURS : 小时

DAYS : 天

2.3.5.BlockingQueue workQueue => 该线程池中的任务队列

维护着等待执行的Runnable对象

当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务。

常用的 workQueue 类型:

(1)SynchronousQueue:

这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现“线程数达到了 maximumPoolSize 而不能新建线程”的错误,使用这个类型队列的时候,maximumPoolSize 一般指定成 Integer.MAX_VALUE,即无限大。

(2)LinkedBlockingQueue:

这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了 maximumPoolSize 的设定失效,因为总线程数永远不会超过 corePoolSize。

(3)ArrayBlockingQueue:

可以限定队列的长度,接收到任务的时候,如果没有达到 corePoolSize 的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了 maximumPoolSize,并且队列也满了,则发生错误。

(4)DelayQueue:

队列内元素必须实现 Delayed 接口,这就意味着你传进去的任务必须先实现 Delayed 接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

2.3.6.ThreadFactory threadFactory

创建线程的方式,这是一个接口,你 new 他的时候需要实现他的 Thread newThread(Runnable r) 方法,一般用不上。

2.3.7.RejectedExecutionHandler handler

抛出异常专用的,比如上面提到的两个错误发生了,就会由这个 handler 抛出异常,你不指定他也有个默认的。

2.4.使用

向ThreadPoolExecutor添加任务

new 一个 ThreadPoolExecutor之后通过 ThreadPoolExecutor.execute(Runnable command)方法 即可向线程池内添加一个任务。

ThreadPoolExecutor的策略

上面介绍参数的时候其实已经说到了ThreadPoolExecutor执行的策略,这里给总结一下,当一个任务被添加进线程池时:

1. 线程数量未达到 corePoolSize,则新建一个线程(核心线程)执行任务

2. 线程数量达到了 corePoolSize,则将任务移入队列等待

3. 队列已满,新建线程(非核心线程)执行任务

4. 队列已满,总线程数又达到了 maximumPoolSize,就会由上面 (RejectedExecutionHandler)抛出异常。

2.5.线程池等待队列

在使用ThreadPoolExecutor线程池的时候,需要指定一个实现了BlockingQueue接口的任务等待队列。在ThreadPoolExecutor线程池的API文档中,一共推荐了三种等待队列,它们是:SynchronousQueue、LinkedBlockingQueue和ArrayBlockingQueue。

队列和栈

队列:是一种特殊的线性结构,允许在线性结构的前端进行删除/读取操作;允许在线性结构的后端进行插入操作;这种线性结构具有“先进先出”的操作特点:
 
但是在实际应用中,队列中的元素有可能不是以“进入的顺序”为排序依据的。例如我们将要讲到的PriorityBlockingQueue队列。 

栈:栈也是一种线性结构,但是栈和队列相比只允许在线性结构的一端进行操作,入栈和出栈都是在一端完成。 

2.5.1.有限队列

SynchronousQueue:

这是一种阻塞队列,其中每个 put 必须等待一个 take,反之亦然。同步队列没有任何内部容量。翻译一下:这是一个内部没有任何容量的阻塞队列,任何一次插入操作的元素都要等待相对的删除/读取操作,否则进行插入操作的线程就要一直等待,反之亦然。

ArrayBlockingQueue:

一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素。这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。

2.5.2.无限队列

LinkedBlockingQueue:

LinkedBlockingQueue是我们在ThreadPoolExecutor线程池中常用的等待队列。它可以指定容量也可以不指定容量。由于它具有“无限容量”的特性,所以我还是将它归入了无限队列的范畴(实际上任何无限容量的队列/栈都是有容量的,这个容量就是Integer.MAX_VALUE)。

LinkedBlockingQueue的实现是基于链表结构,而不是类似ArrayBlockingQueue那样的数组。但实际使用过程中,不需要关心它的内部实现,如果指定了LinkedBlockingQueue的容量大小,那么它反映出来的使用特性就和ArrayBlockingQueue类似了。

LinkedBlockingDeque:

LinkedBlockingDeque是一个基于链表的双端队列。LinkedBlockingQueue的内部结构决定了它只能从队列尾部插入,从队列头部取出元素;但是LinkedBlockingDeque既可以从尾部插入/取出元素,还可以从头部插入元素/取出元素。

PriorityBlockingQueue:

PriorityBlockingQueue是一个按照优先级进行内部元素排序的无限队列。存放在PriorityBlockingQueue中的元素必须实现Comparable接口,这样才能通过实现compareTo()方法进行排序。优先级最高的元素将始终排在队列的头部;PriorityBlockingQueue不会保证优先级一样的元素的排序,也不保证当前队列中除了优先级最高的元素以外的元素,随时处于正确排序的位置。

LinkedTransferQueue:

LinkedTransferQueue也是一个无限队列,它除了具有一般队列的操作特性外(先进先出),还具有一个阻塞特性:LinkedTransferQueue可以由一对生产者/消费者线程进行操作,当消费者将一个新的元素插入队列后,消费者线程将会一直等待,直到某一个消费者线程将这个元素取走,反之亦然。

2.6.常见线程池

Java 通过 Executors 提供了四种线程池,这四种线程池都是直接或间接配置 ThreadPoolExecutor 的参数实现的。

2.6.1.CachedThreadPool

可缓存线程池:

1. 线程池内部没有核心线程,线程数无限制

2. 有空闲线程则复用空闲线程,若无空闲线程则新建线程

3. 一定程度减少频繁创建/销毁线程,减少系统开销

4. 空闲线程超时会被销毁

创建方法:

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

源代码:      

public static ExecutorService newCachedThreadPool() {

              return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L,

                            TimeUnit.SECONDS, new SynchronousQueue<Runnable>());

}

2.6.2.FixedThreadPool

定长线程池:

1. 可控制线程最大并发数(同时执行的线程数)

2. 该线程池的最大线程数等于核心线程数,所以在默认情况下,该线程池的线程不会因为闲置状态超时而被销毁,超出的线程会在队列中等待

3.如果当前线程数小于核心线程数,并且也有闲置线程的时候提交了任务,这时也不会去复用之前的闲置线程,会创建新的线程去执行任务。如果当前执行任务数大于了核心线程数,大于的部分就会进入队列等待。等着有闲置的线程来执行这个任务。

创建方法:         

// nThreads = 最大线程数,即maximunPoolSize

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(nThreads)

源代码:      

public static ExecutorService newFixedThreadPool(int nThreads) {

              return new ThreadPoolExecutor(nThreads, nThreads, 0L,

                            TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

}

2.6.3.ScheduledThreadPool

定长(核心线程)线程池:

支持定时及周期性任务执行。

1. 不仅设置了核心线程数,最大线程数也是 Integer.MAX_VALUE

2. 这个线程池是唯一个有延迟执行和周期执行任务的线程池

2.6.4.SingleThreadExecutor

单线程化的线程池:

1. 有且仅有一个工作线程执行任务

2. 所有任务按照指定顺序执行,即遵循队列的入队出队规则

创建方法:

ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();

源代码:      

public static ExecutorService newSingleThreadExecutor() {

              return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(

                            1, 1, 0L, TimeUnit.MILLISECONDS,

                            new LinkedBlockingQueue<Runnable>()));

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值