线程
Java 多线程和线程同步
进程和线程
进程和线程
- 操作系统中运⾏多个软件
 - ⼀个运⾏中的软件可能包含多个进程
 - ⼀个运⾏中的进程可能包含多个线程
 
CPU 线程和操作系统线程
- CPU 线程
- 多核 CPU 的每个核各⾃独⽴运⾏,因此每个核⼀个线程
 - 「四核⼋线程」:CPU 硬件⽅在硬件级别对 CPU 进⾏了⼀核多线程的⽀持(本质上依然是每个核⼀个线程)
 
 - 操作系统线程:操作系统利⽤时间分⽚的⽅式,把 CPU 的运⾏拆分给多条运⾏逻辑,即为操作系统的线程
 - 单核 CPU 也可以运⾏多线程操作系统
 
- CPU 线程
 线程是什么:按代码顺序执⾏下来,执⾏完毕就结束的⼀条线
- UI 线程为什么不会结束?因为它在初始化完毕后会执⾏死循环,循环的内容是刷新界⾯
 
多线程的使用
Thread 和 Runnable
1  | Thread thread = new Thread() {  | 
1  | Runnable runnable = new Runnable() {  | 

ThreadFactory
1  | ThreadFactory factory = new ThreadFactory() {  | 
Executor 和线程池
常⽤: newCachedThreadPool()
1  | Runnable runnable = new Runnable() {  | 
短时批量处理: newFixedThreadPool()
1  | ExecutorService executor = Executors.newFixedThreadPool(20);  | 
Callable 和 Future
1  | Callable<String> callable = new Callable<String>() {  | 
线程同步与线程安全
synchronized
synchronized ⽅法
1  | private synchronized void count(int newValue) {  | 
synchronized 代码块
1  | private void count(int newValue) {  | 
1  | synchronized (monitor1) {  | 
synchronized 的本质
保证方法内部或代码块内部资源(数据)的互斥访问。即同一时间、由同一个Monitor监视的代码,最多只能有一个线程在访问

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

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

Lock/ReentrantReadWriteLock
同样是「加锁」机制。但使⽤⽅式更灵活,同时也更麻烦⼀些。
1
2
3
4
5
6
7
8
9Lock 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
23ReentrantReadWriteLock 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();
}
}

线程安全问题的本质:
在多个线程访问共同的资源时,在某⼀个线程对资源进⾏写操作的中途(写⼊已经开始,但还没结束),其他线程对这个写了⼀半的资源进⾏了读操作,或者基于这个写了⼀半的资源进⾏了写操作,导致出现数据错误。
锁机制的本质:
通过对共享资源进⾏访问限制,让同⼀时间只有⼀个线程可以访问资源,保证了数据的准确性。
不论是线程安全问题,还是针对线程安全问题所衍⽣出的锁机制,它们的核⼼都在于==共享的资源==,⽽不是某个⽅法或者某⼏⾏代码。
线程异步
应用启动时,系统会为应用创建一个名为“主线程”的执行线程( UI 线程)。 此线程非常重要,因为它负责将事件分派给相应的用户界面小部件,其中包括绘图事件。 此外,它也是应用与 Android UI 工具包组件(来自 android.widget 和 android.view 软件包的组件)进行交互的线程。
系统不会为每个组件实例创建单独的线程。运行于同一进程的所有组件均在 UI 线程中实例化,并且对每个组件的系统调用均由该线程进行分派。 因此,响应系统回调的方法(例如,报告用户操作的 onKeyDown() 或生命周期回调方法)始终在进程的 UI 线程中运行。
Android 的单线程模式必须遵守两条规则:
- 不要阻塞 
UI线程 - 不要在 
UI线程之外访问Android UI工具包 
为解决此问题,Android 提供了几种途径来从其他线程访问 UI 线程:
Activity.runOnUiThread(Runnable)View.post(Runnable)View.postDelayed(Runnable, long)
AsyncTask
AsyncTask封装了Thread和Handler,并不适合特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池。
基本使用
| 方法 | 说明 | 
|---|---|
onPreExecute() | 
异步任务执行前调用,用于做一些准备工作 | 
doInBackground(Params...params) | 
用于执行异步任务,此方法中可以通过publishProgress方法来更新任务的进度,publishProgress会调用onProgressUpdate方法 | 
onProgressUpdate | 
在主线程中执行,后台任务的执行进度发生改变时调用 | 
onPostExecute | 
在主线程中执行,在异步任务执行之后 | 
1  | import android.os.AsyncTask;  | 
- 异步任务的实例必须在
UI线程中创建,即AsyncTask对象必须在UI线程中创建。 execute(Params... params)方法必须在UI线程中调用。- 不要手动调用
onPreExecute(),doInBackground(),onProgressUpdate(),onPostExecute()这几个方法。 - 不能在
doInBackground()中更改UI组件的信息。 - 一个任务实例只能执行一次,如果执行第二次将会抛出异常。
 execute()方法会让同一个进程中的AsyncTask串行执行,如果需要并行,可以调用executeOnExcutor方法。
工作原理
AsyncTask.java
1  | 
  | 
sDefaultExecutor 是一个串行的线程池,一个进程中的所有的 AsyncTask 全部在该线程池中执行。AysncTask 中有两个线程池(SerialExecutor 和 THREAD_POOL_EXECUTOR)和一个 Handler(InternalHandler),其中线程池 SerialExecutor 用于任务的排队,THREAD_POOL_EXECUTOR 用于真正地执行任务,InternalHandler 用于将执行环境从线程池切换到主线程。
AsyncTask.java
1  | private static Handler getMainHandler() {  | 

HandlerThread
HandlerThread 集成了 Thread,却和普通的 Thread 有显著的不同。普通的 Thread 主要用于在 run 方法中执行一个耗时任务,而 HandlerThread 在内部创建了消息队列,外界需要通过 Handler 的消息方式通知 HanderThread 执行一个具体的任务。
HandlerThread.java
1  | 
  | 
IntentService
IntentService 可用于执行后台耗时的任务,当任务执行后会自动停止,由于其是 Service 的原因,它的优先级比单纯的线程要高,所以 IntentService 适合执行一些高优先级的后台任务。在实现上,IntentService 封装了 HandlerThread 和 Handler。
IntentService.java
1  | 
  | 
IntentService 第一次启动时,会在 onCreatea 方法中创建一个 HandlerThread,然后使用的 Looper 来构造一个 Handler 对象 mServiceHandler,这样通过 mServiceHandler 发送的消息最终都会在 HandlerThread 中执行。每次启动 IntentService,它的 onStartCommand 方法就会调用一次,onStartCommand 中处理每个后台任务的 Intent,onStartCommand 调用了 onStart 方法:
IntentService.java
1  | private final class ServiceHandler extends Handler {  | 
可以看出,IntentService 仅仅是通过 mServiceHandler 发送了一个消息,这个消息会在 HandlerThread 中被处理。mServiceHandler 收到消息后,会将 Intent 对象传递给 onHandlerIntent 方法中处理,执行结束后,通过 stopSelf(int startId) 来尝试停止服务。(stopSelf() 会立即停止服务,而 stopSelf(int startId) 则会等待所有的消息都处理完毕后才终止服务)。

线程池
线程池的优点有以下:
- 重用线程池中的线程,避免因为线程的创建和销毁带来性能开销。
 - 能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。
 - 能够对线程进行管理,并提供定时执行以及定间隔循环执行等功能。
 
java 中,ThreadPoolExecutor 是线程池的真正实现:
ThreadPoolExecutor.java
1  | /**  | 
| 类型 | 创建方法 | 说明 | 
|---|---|---|
| FixedThreadPool | Executors.newFixedThreadPool(int nThreads) | 一种线程数量固定的线程池,只有核心线程并且不会被回收,没有超时机制 | 
| CachedThreadPool | Executors.newCachedThreadPool() | 一种线程数量不定的线程池,只有非核心线程,当线程都处于活动状态时,会创建新线程来处理新任务,否则会利用空闲的线程,超时时长为60s | 
| ScheduledThreadPool | Executors.newScheduledThreadPool(int corePoolSize) | 核心线程数是固定的,非核心线程数没有限制,非核心线程闲置时立刻回收,主要用于执行定时任务和固定周期的重复任务 | 
| SingleThreadExecutor | Executors.newSingleThreadExecutor() | 只有一个核心线程,确保所有任务在同一线程中按顺序执行 | 
FutureTask
