安卓-线程

线程

Java 多线程和线程同步

进程和线程

  • 进程和线程

    • 操作系统中运⾏多个软件
    • ⼀个运⾏中的软件可能包含多个进程
    • ⼀个运⾏中的进程可能包含多个线程
  • CPU 线程和操作系统线程

    • CPU 线程
      • 多核 CPU 的每个核各⾃独⽴运⾏,因此每个核⼀个线程
      • 「四核⼋线程」:CPU 硬件⽅在硬件级别对 CPU 进⾏了⼀核多线程的⽀持(本质上依然是每个核⼀个线程)
    • 操作系统线程:操作系统利⽤时间分⽚的⽅式,把 CPU 的运⾏拆分给多条运⾏逻辑,即为操作系统的线程
    • 单核 CPU 也可以运⾏多线程操作系统
  • 线程是什么:按代码顺序执⾏下来,执⾏完毕就结束的⼀条线

    • UI 线程为什么不会结束?因为它在初始化完毕后会执⾏死循环,循环的内容是刷新界⾯

多线程的使用

Thread 和 Runnable

1
2
3
4
5
6
7
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("Thread started!");
}
};
thread.start();
1
2
3
4
5
6
7
8
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Thread with Runnable started!");
}
};
Thread thread = new Thread(runnable);
thread.start();

Thread

ThreadFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ThreadFactory factory = new ThreadFactory() {
int count = 0;
@Override
public Thread newThread(Runnable r) {
count ++;
return new Thread(r, "Thread-" + count);
}
};
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "
started!");
}
};
Thread thread = factory.newThread(runnable);
thread.start();
Thread thread1 = factory.newThread(runnable);
thread1.start();

Executor 和线程池

常⽤: newCachedThreadPool()
1
2
3
4
5
6
7
8
9
10
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Thread with Runnable started!");
}
};
Executor executor = Executors.newCachedThreadPool();
executor.execute(runnable);
executor.execute(runnable);
executor.execute(runnable);
短时批量处理: newFixedThreadPool()
1
2
3
4
5
ExecutorService executor = Executors.newFixedThreadPool(20);
for (Bitmap bitmap : bitmaps) {
executor.execute(bitmapProcessor(bitmap));
}
executor.shutdown();

Callable 和 Future

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Callable<String> callable = new Callable<String>() {
@Override
public String call() {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Done!";
}
};
ExecutorService executor = Executors.newCachedThreadPool();
Future<String> future = executor.submit(callable);
try {
String result = future.get();
System.out.println("result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}

线程同步与线程安全

synchronized

synchronized ⽅法
1
2
3
4
5
6
7
private synchronized void count(int newValue) {
x = newValue;
y = newValue;
if (x != y) {
System.out.println("x: " + x + ", y:" + y);
}
}
synchronized 代码块
1
2
3
4
5
6
7
8
9
private void count(int newValue) {
synchronized (this) {
x = newValue;
y = newValue;
if (x != y) {
System.out.println("x: " + x + ", y:" + y);
}
}
}
1
2
3
4
5
synchronized (monitor1) {
synchronized (monitor2) {
name = x + "-" + y;
}
}
synchronized 的本质
  • 保证方法内部或代码块内部资源(数据)的互斥访问。即同一时间、由同一个Monitor监视的代码,最多只能有一个线程在访问

    synchronized互斥访问

  • 保证线程之间对监视资源的数据同步。即,任何线程在获取到Monitor后的第一时间,会先将共享内存中的数据复制到自己的缓存中;任何线程在释放Monitor的第一时间,会先将缓存中的数据复制到共享内存中。

    synchronized数据同步

volatile
  • 保证加了 volatile 关键字的字段的操作具有原⼦性和同步性,其中原⼦性相当于实现了针对单⼀字段的线程间互斥访问。因此 volatile 可以看做是简化版的 synchronized。
  • volatile 只对基本类型 (byte、char、short、int、long、float、double、boolean) 的赋值操作和对象的引用赋值操作有效。
java.util.concurrent.atomic包:
  • 下面有AtomicInteger``AtomicBoolean等类,作用和volatile基本一致,可以看做是通用版的volatile。

    1
    2
    3
    AtomicInteger atomicInteger = new AtomicInteger(0);
    ...
    atomicInteger.getAndIncrement();

AtomicInteger、Unsafe

Lock/ReentrantReadWriteLock
  • 同样是「加锁」机制。但使⽤⽅式更灵活,同时也更麻烦⼀些。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Lock lock = new ReentrantLock();
    ...

    lock.lock();
    try {
    x++;
    } finally {
    lock.unlock();
    }

    finally 的作⽤:保证在⽅法提前结束或出现 Exception 的时候,依然能正常释放锁。

  • ⼀般并不会只是使⽤ Lock ,⽽是会使⽤更复杂的锁,例如 ReadWriteLock

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    Lock readLock = lock.readLock();
    Lock writeLock = lock.writeLock();
    private int x = 0;
    private void count() {
    writeLock.lock();
    try {
    x++;
    } finally {
    writeLock.unlock();
    }
    }
    private void print(int time) {
    readLock.lock();
    try {
    for (int i = 0; i < time; i++) {
    System.out.print(x + " ");
    }
    System.out.println();
    } finally {
    readLock.unlock();
    }
    }

ReentrantLock

线程安全问题的本质:

在多个线程访问共同的资源时,在某⼀个线程对资源进⾏写操作的中途(写⼊已经开始,但还没结束),其他线程对这个写了⼀半的资源进⾏了读操作,或者基于这个写了⼀半的资源进⾏了写操作,导致出现数据错误。

锁机制的本质:

通过对共享资源进⾏访问限制,让同⼀时间只有⼀个线程可以访问资源,保证了数据的准确性。

不论是线程安全问题,还是针对线程安全问题所衍⽣出的锁机制,它们的核⼼都在于共享的资源,⽽不是某个⽅法或者某⼏⾏代码。

线程异步

应用启动时,系统会为应用创建一个名为“主线程”的执行线程( UI 线程)。 此线程非常重要,因为它负责将事件分派给相应的用户界面小部件,其中包括绘图事件。 此外,它也是应用与 Android UI 工具包组件(来自 android.widgetandroid.view 软件包的组件)进行交互的线程。

系统不会为每个组件实例创建单独的线程。运行于同一进程的所有组件均在 UI 线程中实例化,并且对每个组件的系统调用均由该线程进行分派。 因此,响应系统回调的方法(例如,报告用户操作的 onKeyDown() 或生命周期回调方法)始终在进程的 UI 线程中运行。

Android 的单线程模式必须遵守两条规则:

  • 不要阻塞 UI 线程
  • 不要在 UI 线程之外访问 Android UI 工具包

为解决此问题,Android 提供了几种途径来从其他线程访问 UI 线程:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable, long)

AsyncTask

AsyncTask封装了ThreadHandler,并不适合特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池。

基本使用

方法 说明
onPreExecute() 异步任务执行前调用,用于做一些准备工作
doInBackground(Params...params) 用于执行异步任务,此方法中可以通过publishProgress方法来更新任务的进度,publishProgress会调用onProgressUpdate方法
onProgressUpdate 在主线程中执行,后台任务的执行进度发生改变时调用
onPostExecute 在主线程中执行,在异步任务执行之后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import android.os.AsyncTask;

public class DownloadTask extends AsyncTask<String, Integer, Boolean> {

@Override
protected void onPreExecute() {
super.onPreExecute();
}

@Override
protected Boolean doInBackground(String... strings) {
return null;
}

@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}

@Override
protected void onPostExecute(Boolean aBoolean) {
super.onPostExecute(aBoolean);
}
}
  • 异步任务的实例必须在UI线程中创建,即AsyncTask对象必须在UI线程中创建。
  • execute(Params... params)方法必须在UI线程中调用。
  • 不要手动调用onPreExecute()doInBackground()onProgressUpdate()onPostExecute()这几个方法。
  • 不能在doInBackground()中更改UI组件的信息。
  • 一个任务实例只能执行一次,如果执行第二次将会抛出异常
  • execute() 方法会让同一个进程中的 AsyncTask 串行执行,如果需要并行,可以调用 executeOnExcutor 方法。

工作原理

AsyncTask.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}

@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}

mStatus = Status.RUNNING;

onPreExecute();

mWorker.mParams = params;
exec.execute(mFuture);

return this;
}

sDefaultExecutor 是一个串行的线程池,一个进程中的所有的 AsyncTask 全部在该线程池中执行。AysncTask 中有两个线程池(SerialExecutorTHREAD_POOL_EXECUTOR)和一个 HandlerInternalHandler),其中线程池 SerialExecutor 用于任务的排队,THREAD_POOL_EXECUTOR 用于真正地执行任务,InternalHandler 用于将执行环境从线程池切换到主线程。

AsyncTask.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private static Handler getMainHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler(Looper.getMainLooper());
}
return sHandler;
}
}

private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}

@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}


private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}

AsyncTask源码

HandlerThread

HandlerThread 集成了 Thread,却和普通的 Thread 有显著的不同。普通的 Thread 主要用于在 run 方法中执行一个耗时任务,而 HandlerThread 在内部创建了消息队列,外界需要通过 Handler 的消息方式通知 HanderThread 执行一个具体的任务。

HandlerThread.java

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}

IntentService

IntentService 可用于执行后台耗时的任务,当任务执行后会自动停止,由于其是 Service 的原因,它的优先级比单纯的线程要高,所以 IntentService 适合执行一些高优先级的后台任务。在实现上,IntentService 封装了 HandlerThreadHandler

IntentService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.

super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();

mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}

IntentService 第一次启动时,会在 onCreatea 方法中创建一个 HandlerThread,然后使用的 Looper 来构造一个 Handler 对象 mServiceHandler,这样通过 mServiceHandler 发送的消息最终都会在 HandlerThread 中执行。每次启动 IntentService,它的 onStartCommand 方法就会调用一次,onStartCommand 中处理每个后台任务的 IntentonStartCommand 调用了 onStart 方法:

IntentService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}

@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}

···

@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}

可以看出,IntentService 仅仅是通过 mServiceHandler 发送了一个消息,这个消息会在 HandlerThread 中被处理。mServiceHandler 收到消息后,会将 Intent 对象传递给 onHandlerIntent 方法中处理,执行结束后,通过 stopSelf(int startId) 来尝试停止服务。(stopSelf() 会立即停止服务,而 stopSelf(int startId) 则会等待所有的消息都处理完毕后才终止服务)。

IntentService

线程池

线程池的优点有以下:

  • 重用线程池中的线程,避免因为线程的创建和销毁带来性能开销。
  • 能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。
  • 能够对线程进行管理,并提供定时执行以及定间隔循环执行等功能。

java 中,ThreadPoolExecutor 是线程池的真正实现:

ThreadPoolExecutor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize 核心线程数
* @param maximumPoolSize 最大线程数
* @param keepAliveTime 非核心线程闲置的超时时长
* @param unit 用于指定 keepAliveTime 参数的时间单位
* @param 任务队列,通过线程池的 execute 方法提交的 Runnable 对象会存储在这个参数中
* @param threadFactory 线程工厂,用于创建新线程
* @param handler 任务队列已满或者是无法成功执行任务时调用
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
···
}
类型 创建方法 说明
FixedThreadPool Executors.newFixedThreadPool(int nThreads) 一种线程数量固定的线程池,只有核心线程并且不会被回收,没有超时机制
CachedThreadPool Executors.newCachedThreadPool() 一种线程数量不定的线程池,只有非核心线程,当线程都处于活动状态时,会创建新线程来处理新任务,否则会利用空闲的线程,超时时长为60s
ScheduledThreadPool Executors.newScheduledThreadPool(int corePoolSize) 核心线程数是固定的,非核心线程数没有限制,非核心线程闲置时立刻回收,主要用于执行定时任务和固定周期的重复任务
SingleThreadExecutor Executors.newSingleThreadExecutor() 只有一个核心线程,确保所有任务在同一线程中按顺序执行

FutureTask