要求:熟练使用线程、线程池和同步线程java
在 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 是一个轻量级的异步任务类,它能够在线程池中执行后台任务,而后把执行的进度和结果传递给主线程,而且在主线程中更新 UI。多线程
AsyncTask 是一个抽象类,public abstract class AsyncTask<Params, Progress, Result>并发
其中,参数1 Params:异步任务的入参;参数2 Progress:执行任务的进度;参数3 Result:后台任务执行的结果。dom
还提供了四个核心方法:异步
简单使用: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 是一个实现了 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
线程池的优势:
ExecutorService 是最基础的线程池接口,ThreadPoolExecutor 类是对线程池的具体实现。下面经过经常使用的构造函数来分析一下用法。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
复制代码
先解释参数:
线程池的分配遵循以下规则:
下面四种线程池都是直接或者间接使用 ThreadPoolExecutor 实现
经过 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");
}
});
}
复制代码
经过 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");
}
});
}
复制代码
经过 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");
}
});
}
复制代码
经过 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(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
}
复制代码
wait 方法:线程自动释放占有的对象锁,并等待 notify; notify 方法:唤醒一个正在 wait 当前对象锁的线程,并让他拿到对象锁; notifyAll 方法:唤醒全部正在 wait 当前对象锁的线程。
notify 和 notifyAll 的区别:notify 是唤醒一个正在 wait 当前对象锁的线程,而 notifyAll 是唤醒全部。notify 是本地方法,具体唤醒的是哪个线程,由 java 虚拟机定,notifyAll 唤醒的线程只是跳出 wait 状态,接下来它们还会竞争对象锁。
这三个方法是 Object 自带的,而且在调用这三个方法时须要得到这个对象的锁,不然就会报错:
java.lang.IllegalMonitorStateException:current thread not owner
复制代码
这三个方法以下:
参考:java中的wait、notify、notifyAll
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);
}
}
复制代码