多线程-《Android开发进阶从小工到专家》读书笔记
条评论3.1 Android 中的消息机制
- 3.1.1 处理消息的手段——Handler、Looper 与 MessageQueue
在 Android 应用启动时,会默认有一个主线程(UI 线程),在这个线程中会关联一个消息队列,所有的操作都会被封装成消息然后交给主线程来处理。为了保证主线程不会主动退出,会将获取消息的操作放在一个死循环中,这样程序就相当于一直在执行死循环,因此不会退出。
3.2 Android 中的多线程
3.2.1 多线程的实现——Thread 和 Runnable
Thread 也是一个 Runnable,它实现了 Runnable 接口,在 Thread 类中有一个 Runnable 类型的 target 字段,代表要被执行在这个子线程中的任务。3.2.3 与多线程相关的方法——Callable、Future 和 FutureTask
- **Callable
**:与 Runnable 的功能大致相似,不过它有一个泛型参数 V,该接口有一个返回值(V)的 call()函数。 - **Future
**:为线程池制定了一个可管理的任务标准。提供了对 Runnable 或 Callable 任务的执行结果进行取消(cancel)、查询是否完成(isDone)、获取结果(get)、设置结果操作(set)。get 方法会阻塞,直到任务返回结果。 - FutureTask:是 Future
的实现类。实现了 RunnableFuture ,而 RunnableFuture 实现了 Runnable、Future 这两个接口。FutureTask 会像 Thread 包装 Runnable 那样对 Runnable 和 Callable 进行包装,Runnable 与 Callable 由构造函数注入。总的来说,FutureTask 既是 Future、Runnable,又包装了 Callable(如果是 Runnable 最终也会被转换为 Callable),它是两者的合体。
- **Callable
3.2.4 构建服务器应用程序的有效方法——线程池
线程池的使用准则:
线程池的最佳大小取决于可用处理器的数目以及工作队列中的任务的性质。若在一个具有 N 个处理器的系统上只有一个工作队列,其中全部是计算性质的任务,在线程池具有 N 或 N+1 个线程时一般会获得最大的 CPU 利用率;对于那些可能需要等待 I/O 完成的任务(例如,从套接字读取 HTTP 请求的任务),需要让线程池的大小超过可用处理器的数目,因为并不是所有线程都一直在工作。3.2.5 同步集合
程序中的优化策略——CopyOnWriteArrayList
Copy-On-Write 是一种用于程序设计中的优化策略,基本思路:从多个线程共享一个列表,当某个线程想要修改这个列表的元素时,会把列表中的元素 Copy 一份,然后进行修改,修改完成后再将新的元素设置给这个列表。- 优点:可以对 CopyOnWrite 容器进行并发读,不需要加锁。
- 缺点:在添加、移除元素时占用的内存空间翻了一倍,以空间换时间。
从 JDK1.5 开始提供了两个使用 CopyOnWrite 机制实现的并发容器:CopyOnWriteArrayList,CopyOnWriteArraySet。
提高并发效率——ConcurrentHashMap
HashTable 在竞争激烈的环境下效率低下的原因是:所有访问 HashTable 的线程都必须竞争同一把锁。假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,这就是 ConcurrentHashMap 所使用的锁分段技术。当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。有效的方法——BlockingQueue
BlockingQueue 在 JDK 中有多个实现:ArrayBlockingQueue:数组实现的、线程安全的、有界的阻塞队列。按 FIFO 原则对元素进行排序。
LinkedBlockingQueue:单向链表实现的阻塞队列。FIFO。吞吐量通常高于基于数组的队列。
LinkedBlockingDeque:双向链表实现的双向并发阻塞队列。支持 FIFO、FILO。可以指定队列的容量,若不指定,默认容量大小为 Integer.MAX_VALUE。
3.2.6 同步锁
- 3.2.6.1 同步机制关键字——synchronized
同步方法:public synchronized void method() {...}
同步块:public void method() { synchronized(this) {...} }
同步 class 对象:public void method() { synchronized(Demo.class) {...} }
同步静态方法:public synchronized static void method() {...}
前两种锁的是对象,后两种锁的是class 对象。对于锁 class 对象,它的作用是防止多个线程同时访问添加了 synchronized 锁的代码块;对于锁对象,作用是防止其他线程访问同一个对象中的 synchronized 代码块或函数。
- 3.2.6.2 显示锁——ReetrantLock 与 Condition
与内置锁 synchronized 相比,(1)获取和释放的灵活性(2)轮循锁和定时锁(3)公平性
常用形式:
1
2
3
4
5
6
7
8
9Lock lock = new ReetrantLock();
public void doSth() {
lock.lock();
try{
//...
} finally {
lock.unlock();
}
}lock 必须在 finally 块中释放。而 synchronized,JVM 将确保锁会获得自动释放。
在 ReetrantLock 类中还有一个函数 newCondition(),该函数用于获取 Lock 上的一个条件,也就是说 Condition 是和 Lock 绑定的。Condition 用于实现线程间的通信,它是为了解决 Object.wait()、notify()、notifyAll()难以使用的问题。
通过 ReetrantLock 与 Condition 来实现一个简单的阻塞队列:p91
3.2.6.3 信号量 Semaphore
semaphore 是一个计数信号量,它的本质是一个“共享锁”。信号量维护了一个信号量许可集,线程可以通过调用 aquire()来获取信号量的许可。当信号量中有可用的许可时,线程能获取该许可(semaphore.acquire());否则线程必须等待,直到有可用的许可为止。线程可以通过 release()来释放它所持有的信号量许可。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class SemaphoreTest {
static int time = 0;
public static void main(String[] args) {
final ExecutorService executorService = Executors.newFixedThreadPool(3);
final Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 5; i++) {
executorService.submit(new Runnable() {
public void run() {
try {
semaphore.acquire();
System.out.println("剩余许可:" + semaphore.availablePermits());
Thread.sleep(2000);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}输出:
1
2
3
4
5剩余许可:1
剩余许可:1
剩余许可:0
剩余许可:2
剩余许可:1- 3.2.6.4 循环栅栏 CyclicBarrier
CyclicBarrier 是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点。因为该 barrier在释放等待线程后可以重用,所以称它为循环的 barrier。
示例:
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
30public class CyclicBarrierTest {
private static final int SIZE = 5;
private static CyclicBarrier mCyclicBarrier;
public static void main(String[] args) {
mCyclicBarrier = new CyclicBarrier(SIZE, new Runnable() {
public void run() {
System.out.println(" --->满足条件,执行特定操作。参与者:" + mCyclicBarrier.getParties());
}
});
//新建5个任务
for (int i = 0; i < SIZE; i++) {
new WorkerThread().start();
}
}
static class WorkerThread extends Thread {
public void run() {
System.out.println(Thread.currentThread().getName() + "等待CyclicBarrier");
try {
//将mCyclicBarrier的参与者数量加1
mCyclicBarrier.await();
} catch (BrokenBarrierException | InterruptedException e) {
e.printStackTrace();
}
//mCyclicBarrier的参与者数量等于5时,才继续往后执行
System.out.println(Thread.currentThread().getName() + "继续执行");
}
}
}输出:
1
2
3
4
5
6
7
8
9
10
11Thread-0等待CyclicBarrier
Thread-1等待CyclicBarrier
Thread-2等待CyclicBarrier
Thread-3等待CyclicBarrier
Thread-4等待CyclicBarrier
--->满足条件,执行特定操作。参与者:5
Thread-4继续执行
Thread-0继续执行
Thread-1继续执行
Thread-2继续执行
Thread-3继续执行只有当 5 个线程都调用了 mCyclicBarrier 函数之后,后续的代码才会执行。例子中在 5 个函数都就位后首先会执行一个 Runnable,也就是 CyclicBarrier 构造函数的第二个参数。
由此可知,CyclicBarrier 实际上相当于可以用于多个线程等待,直到某个条件被满足。对于上述示例来说,这里的条件就是有指定个数的线程调用了 mCyclicBarrier.await()函数。- 3.2.6.5 闭锁 CountDownLatch
CountDownLatch 也是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待,直到条件被满足。
示例:
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
35public class CountDownLatchTest {
private static int LATCH_SIZE = 5;
public static void main(String[] args) {
try {
CountDownLatch latch = new CountDownLatch(LATCH_SIZE);
//新建5个任务
for (int i = 0; i < LATCH_SIZE; i++) {
new WorkerThread(latch).start();
}
System.out.println("主线程等待");
//主线程等待线程池中5个任务完成
latch.await();
System.out.println("主线程继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class WorkerThread extends Thread {
CountDownLatch mLatch;
public WorkerThread(CountDownLatch latch) {
mLatch = latch;
}
public void run() {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "执行操作");
//将CountDownLatch的数值减1
mLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}输出:
1
2
3
4
5
6
7主线程等待
Thread-0执行操作
Thread-1执行操作
Thread-2执行操作
Thread-3执行操作
Thread-4执行操作
主线程继续执行main 函数中创建了一个数量为 5 的 CountDownLatch 对象,任务创建、启动 5 个 WorkerThread 对象,然后调用 CountDownLactch 对象的 await 函数使主线程进入等待状态。当 5 个 WorkerThread 都调用了 CountDownLactch 对象的 countDown()后,主线程就会被唤醒。
CountDownLatch 和 CyclicBarrier 的不同点
- 3.2.6.1 同步机制关键字——synchronized
- CountDownLatch 的作用是允许 1 个或 N 个线程等待其他线程完成执行,而 CyclicBarrier 则是允许 N 个线程相互等待。
- CountDownLatch 的计数器无法被重置,CyclicBarrier 的计数器可以被重置后使用,因此称为是循环的 barrier。