Android 入门(十)异步线程池

要求:熟练使用线程、线程池和同步线程java

线程 Thread

在 Java 中线程的实现就是 Thread 类,通常实现线程有两种方式,一种是继承 Thread,一种是实现 Runnable 接口。bash

// 继承 Thread
private class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        Log.i(TAG, "MyThread: " + Thread.currentThread().getName());
    }
}
MyThread myThread = new MyThread();
myThread.start();
复制代码
// 实现 Runnable 接口
private class MyRunnable implements Runnable {
    @Override
    public void run() {
        Log.i(TAG, "MyRunnable: " + Thread.currentThread().getName());
    }
}
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
复制代码
// 实现 Runnable 接口,使用匿名内部类
new Thread(new Runnable() {
    @Override
    public void run() {
        Log.i(TAG, "MyRunnable: " + Thread.currentThread().getName());
    }
}).start();
复制代码

AsyncTask 异步任务

AsyncTask 是一个轻量级的异步任务类,它能够在线程池中执行后台任务,而后把执行的进度和结果传递给主线程,而且在主线程中更新 UI。多线程

AsyncTask 是一个抽象类,public abstract class AsyncTask<Params, Progress, Result>并发

其中,参数1 Params:异步任务的入参;参数2 Progress:执行任务的进度;参数3 Result:后台任务执行的结果。dom

还提供了四个核心方法:异步

  • 方法一:onPreExcute() 在主线程中执行,任务开启前的准备工做;
  • 方法二:doInBackground(Params... params) 是一个抽象方法,开启子线程执行后台任务;
  • 方法三:onProgressUpdate(Progress values) 在主线程中执行,更新 UI 进度;
  • 方法四:onPosExecute(Result result) 在子线程中执行,异步任务执行完成以后再执行,它的参数是 onProgressUpdate 方法的返回值。

简单使用:ide

private static class MyAsyncTask extends AsyncTask<String, Integer, String> {

        private String mName;

        public MyAsyncTask(String name) {
            mName = name;
        }

        @Override
        protected String doInBackground(String... strings) {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
            Log.i(mName, dateFormat.format(new Date(System.currentTimeMillis())));
            return null;
        }
    }
复制代码

IntentService

IntentService 是一个实现了 HandlerThread 的 Service 抽象类。而 HandlerThread 是一个容许 Handler 的特殊线程。函数

简单使用:性能

private class MyIntentService extends IntentService {
    public MyIntentService(String name) {
        super(name);
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        Log.i(TAG, "onHandleIntent: ");
    }
}
复制代码

线程池

线程池就是一次建立多个线程,而后重用这几个线程。ui

线程池的优势:

  • 重用线程池中的线程,避免频繁地建立和销毁带来的性能消耗;
  • 有效控制线程的最大并发数量,防止线程过大抢占资源形成系统堵塞;
  • 能够对线程进行必定的管理。

ThreadPoolExecutor

ExecutorService 是最基础的线程池接口,ThreadPoolExecutor 类是对线程池的具体实现。下面经过经常使用的构造函数来分析一下用法。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
复制代码

先解释参数:

  • corePoolSize 表示线程池中核心线程的数量,在默认状况下,即便核心线程没有任务在执行,它也是存在的。若是将 ThreadPoolExecutor 的 allowCoreThreadTimeOut 属性设置为 true,那么闲置的核心线程就会有超时策略,这个时间由 keepAliveTime 决定,keepAliveTime 时间内若是没有回应则该线程会被终止。反之,线程不会超时;
  • maximumPoolSize 表示线程池的最大线程数,当任务数量超过最大线程数时,其余的任务可能会被阻塞。最大线程数 = 核心线程 + 非核心线程。非核心线程只有当核心线程不够用的且线程池有空余时才会被建立,执行完任务后会被销毁;
  • keepAliveTime 非核心线程的超时时长,当执行时间超过这个时间时,非核心线程就会被回收。当 allowCoreThreadTimeOut 为 true 时,此属性也做用在核心线程;
  • unit 枚举时间单位,TimeUnit;
  • workQueue 线程池中的任务队列,咱们提交给线程池的 runnable 会被存储在这个对象上。

线程池的分配遵循以下规则:

  • 当核心线程数量没有达到 maximumPoolSize,会启动核心线程执行任务;
  • 当核心线程数量达到了 maximumPoolSize,那么任务会被放到任务队列中排队等待;
  • 若是任务队列已满,可是线程池中线程数量没有达到 maximumPoolSize,那么启动一个非核心线程来处理任务。
  • 若是在任务队列已满,而且线程数量达到了 maximumPoolSize,那么线程池会拒绝执行该任务,还会调用RejectedtionHandler的rejectedExecution方法来通知调用者。
下面四种线程池都是直接或者间接使用 ThreadPoolExecutor 实现

FixedThreadPool

经过 Executors.newFixedThreadPool(int nThreads) 方法建立,nThreads 就是固定的核心线程数,而且全部线程都是核心线程。它们没有超时机制,而且排队任务无限制,由于是核心线程,全部响应快,也不用担忧线程被回收。

// 拥有固定的线程数,每一个线程都是核心线程
private ExecutorService mFixedThreadPool = Executors.newFixedThreadPool(5);

// 使用方法
for (int i = 0; i < 3; i++) {
    mFixedThreadPool.execute(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:ms", Locale.CHINA);
            Log.i(TAG, dateFormat.format(new Date(System.currentTimeMillis())));
            Log.i(TAG, "run: FixedThreadPool");
        }
    });
}
复制代码

CachedThreadPool

经过 Executors.newCachedThreadPool() 方法建立。它是一个拥有无限多线程的线程池,而且全部线程都不是核心线程。当有新的任务来的时候没有空闲的线程,会直接建立新的线程执行任务。全部线程的超时时间都是 60s,因此当线程空闲时必定会被系统回收,因此理论上该线程池不会有占用系统资源的无用线程。

// 拥有无限多数量的线程池,全部线程都不是核心线程
private ExecutorService mCachedThreadPool = Executors.newCachedThreadPool();

// 使用方法
for (int i = 0; i < 3; i++) {
    mCachedThreadPool.execute(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:ms", Locale.CHINA);
            Log.i(TAG, dateFormat.format(new Date(System.currentTimeMillis())));
            Log.i(TAG, "run: CachedThreadPool");
        }
    });
}
复制代码

ScheduledThreadPool

经过 Executors.newScheduledThreadPool(int corePoolSize) 建立,该线程池拥有固定的核心线程和无限量的非核心线程。而且非核心线程的超时时间为 0s,也就是说一旦空闲就会立马被回收。这类线程适合执行定时任务和固定周期的重复任务。

// 拥有固定的核心线程,还有无限多的非核心线程
private ExecutorService mScheduledThreadPool = Executors.newScheduledThreadPool(5);

// 使用方法
for (int i = 0; i < 3; i++) {
    mScheduledThreadPool.execute(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:ms", Locale.CHINA);
            Log.i(TAG, dateFormat.format(new Date(System.currentTimeMillis())));
            Log.i(TAG, "run: ScheduledThreadPool");
        }
    });
}
复制代码

SingleThreadExecutor

经过 Executors.newSingleThreadExecutor() 建立,它只有一个核心线程,而且确保传进来的任务会被顺序执行。它的意义是,统一全部外界任务到同一线程中,让调用者忽略线程同步问题。

// 只有一个核心线程
private ExecutorService mSingleThreadExecutor = Executors.newSingleThreadExecutor();

// 使用方法
for (int i = 0; i < 3; i++) {
    mSingleThreadExecutor.execute(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:ms", Locale.CHINA);
            Log.i(TAG, dateFormat.format(new Date(System.currentTimeMillis())));
            Log.i(TAG, "run: SingleThreadExecutor");
        }
    });
}
复制代码

线程池的经常使用方法有,shutDown() 关闭线程池,须要执行完已经提交的任务;shutDownNow() 关闭线程池,而且尝试结束已经提交的任务;allowCoreThreadTimeOut(boolen) 容许核心线程闲置超时回收;execute() 提交任务无返回值;submit() 提交任务有返回值。

线程同步

synchronized

synchronized 锁住的是对象,而且须要锁住全局惟一的对象。因此 synchronized(this){} 方法不能奏效。

通常的作法是:

// 先建立一个全局惟一而且不会改变的对象
private final Object mLock = new Object();

// 锁住想要顺序执行的代码
for (int i = 0; i < 3; i++) {
    mFixedThreadPool.execute(new Runnable() {
        @Override
        public void run() {
            synchronized (mLock) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:ms", Locale.CHINA);
                Log.i(TAG, dateFormat.format(new Date(System.currentTimeMillis())));
                Log.i(TAG, "run: FixedThreadPool");
            }
        }
    });
}
复制代码

或者使用这个类对应的 Class 对象,如:

public void method(){
    synchronized(MainActivity.class){
    // Do something
    }
}
复制代码

还可使用 static synchronize 方法修饰须要锁住的方法,static 方法中没法直接使用 this,也就是说它锁住的不是 this,而是类的 Class 对象,因此 static synchronize 也至关于全局锁。

public static synchronized void method(){
    // Do something
}
复制代码

Object 的 wait、notify、notifyAll 方法

wait 方法:线程自动释放占有的对象锁,并等待 notify; notify 方法:唤醒一个正在 wait 当前对象锁的线程,并让他拿到对象锁; notifyAll 方法:唤醒全部正在 wait 当前对象锁的线程。

notify 和 notifyAll 的区别:notify 是唤醒一个正在 wait 当前对象锁的线程,而 notifyAll 是唤醒全部。notify 是本地方法,具体唤醒的是哪个线程,由 java 虚拟机定,notifyAll 唤醒的线程只是跳出 wait 状态,接下来它们还会竞争对象锁。

java中的wait、notify、notifyAll

Java 的 wait、notify、notifyAll

这三个方法是 Object 自带的,而且在调用这三个方法时须要得到这个对象的锁,不然就会报错:

java.lang.IllegalMonitorStateException:current thread not owner
复制代码

这三个方法以下:

  • wait:线程自动释放其占有的对象锁,并等待 notify。
  • notify:唤醒一个正在 wait 当前对象锁的线程,并让它拿到锁。notify 是一个本地方法,具体唤醒哪个线程由虚拟机决定。
  • notifyAll:唤醒全部正在 wait 当前对象锁的线程。notifyAll 执行后并非全部线程立马能往下执行,它们只是跳出 wait 状态,仍是会竞争对象锁。

参考:java中的wait、notify、notifyAll

CountDownLatch 更能优雅的线程同步

CountDownLatch 是一个线程辅助类,这个类可让线程等待其余线程完成一组操做以后才能执行,不然就一直等待。

首先使用一个整型参数来初始化,表示等待其余线程的数量,使用 await() 方法让线程开始等待其余线程执行完毕,每个线程执行完以后调用 countDown() 方法,这会让 CountDownLatch 内部的计数器减 1,当计数器变为 0 的时候,CountDownLatch 将唤醒全部调用 await() 方法并进入 WAITING 状态的线程。

简单使用:

private void testCountDownLatch() {
        // 建立一个视频会,而且须要 10 我的参加
        VideoConference videoConference = new VideoConference(10);
        Thread threadConference = new Thread(videoConference);
        threadConference.start();   // 开启会议窗口

        // 建立 10 个参会人
        Thread[] threads = new Thread[10];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(new Participant("P-" + i, videoConference));
        }

        // 链接视频会议
        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }
    }

    private class VideoConference implements Runnable {
        private CountDownLatch mController;

        public VideoConference(int number) {
            mController = new CountDownLatch(number);
        }

        public void arrive(String name) {
            System.out.printf("%s has arrived.\n", name);
            mController.countDown();
            System.out.printf("VideoConference: Waiting for %d participants.\n", mController.getCount());
        }

        @Override
        public void run() {
            System.out.printf("VideoConference: Initialization: %d participants.\n", mController.getCount());

            try {
                mController.await();
                System.out.print("VideoConference: All the participants have come.\n");
                System.out.print("VideoConference: Let's start...");

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private class Participant implements Runnable {
        private String mName;
        private VideoConference mVideoConference;

        public Participant(String name, VideoConference videoConference) {
            mName = name;
            mVideoConference = videoConference;
        }

        @Override
        public void run() {
            long duration = (long) (Math.random() * 10);

            try {
                TimeUnit.SECONDS.sleep(duration);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            mVideoConference.arrive(mName);
        }
    }
复制代码
相关文章
相关标签/搜索