Android 中java多线程编程及注意事项
开启线程方式:
//方式1
public class MyThread extends Thread{
@Override
public void run() {
super.run();
//do my work
}
}
new MyThread().start();
//方式2:
public class MyRunnable implements Runnable{
@Override
public void run() {
//do my work
}
}
new Thread(new MyRunnable()).start();
//方式3
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "result";
}
}
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
当然还有一些 HandlerThread、ThreadPool、AsyncTask 等也可以开启新线程,都是基于Thread的封装
线程状态:
等待和阻塞的区别:简单理解,等待是线程判断条件不满足,主动调用指令进入的一种状态;而阻塞是被动进入,而且只有synchronized关键字修饰时可能触发。
还一种说法:将IO、sleep、synchronized等进入的线程block状态称为阻塞,他们的共同点是让出cpu,但不释放已经拿到的锁,参考:blog.csdn.net/weixin_3104…
线程安全
不共享数据:ThreadLocal
ThreadLocal<String> threadLocal = new ThreadLocal<>();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("线程1数据");
//do some work ...
String data = threadLocal.get();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("线程2数据");
//do some work ...
String data = threadLocal.get();
}
});
原理:
没个线程里都有一个threadlocalmap数组, 调用threadlocalset方法时,以threadlocal变量hash值做key,将对于value存入数组中。
共享数据:
1.jvm关键字 synchronized, 注:非公平、可重入
private List<Integer> shareData = new ArrayList<>();
public void trySync(int data) {
synchronized (this) {//锁TestSyncData对象, thread1, thread2 ...
//操作共享数据
shareData.add(data);
}
//do something later ...
}
注意坑点:锁范围,存活周期不一样
public void trySync1() {
synchronized (this) {//锁TestSyncData对象
}
}
//锁TestSyncData对象
public synchronized void trySync2() {
}
//锁TestSyncData类
public synchronized static void trySync3() {
}
//锁TestSyncData类
public void trySync4() {
synchronized (TestSyncData.class){
}
}
有耗时任务获取锁,后续收尾处理一定要考虑锁释放耗时问题;比如单例对象在主线程释放时,一定要注意锁能否及时拿到。
如果不能确定,考虑将锁降级存活时长,比如用栈内锁,线程安全型bean
synchronized原理详见:blog.csdn.net/weixin_3960…
2.wait-notify 函数,java祖先类Object的成员函数
public void testWait() {
//1.创建同步对象
Object lock = new Object();
new Thread(new Runnable() {
@Override
public void run() {
//2. do something hard ...
shareData = new ShareData();
//3. 唤醒原线程
synchronized (lock){
lock.notify();
}
//5.some other logic
}
}).start();
//4.原线程等待
synchronized (lock){
try {
lock.wait(45000);
} catch (InterruptedException e) {
//6.线程中断触发
e.printStackTrace();
}
}
if (shareData != null) {
//do something happy ...
}
}
坑点多多:
notify不能定向唤醒,只能随机唤醒一个wait的线程(保证notify的数量>=wait数量),使用的时候一定要保证没有多个线程处于wait状态;如果想定向唤醒,考虑使用 ReentrantLock的Condition
标记5的地方最好不要做其他逻辑,可能不会执行到,尽量保证notify是耗时任务里的最后逻辑
标记6的地方注意wait会被线程中断,而跳出同步逻辑,如果需要可以使用抗扰动写法:
while (shareData == null) {//4 抗线程扰动写法
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
业务销毁的时候,如果不需要等数据返回,可以直接notifyAll,提前结束线程任务,释放对象
3.基于aqs(队列同步器)接口的一系列同步锁或组件 ReentrantLock、Semaphore、CountDownLantch;通过队列+计数+CAS
private ReentrantLock lock = new ReentrantLock();
private List<Integer> shareData = new ArrayList<>();
public void testLock() {//非标准
lock.lock();
shareData.add(1);
lock.unlock();
}
坑点:注意手动释放,以免死锁; 同步逻辑会有exception,标准最好用try-catch-finally
线程安全的数据类型:StringBuffer、CopyOnWriteArrayList、concurrentxxx、BlockingQueue、Atomicxxx(CAS)
有些基于锁,有些基于无锁化的CAS(compare and swap),有些两者混合
cas原理参考:cloud.tencent.com/developer/a…
坑点:
- CopyOnWriteArrayList:先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加删除元素,添加删除完元素之后,再将原容器的引用指向新的容器,整个过程加锁,保证了写的线程安全。然而我们还是出现了多线程的数组越界异常
//不安全写法
for (int i = 0; i < copyOnWriteArrayList.size(); i++) {
copyOnWriteArrayList.get(i);
}
//安全写法
Iterator iterator = copyOnWriteArrayList.iterator();
while (iterator.hasNext()){
iterator.next();
}
Atomicxxx 注意使用场景
ABA问题 AtomicStampedReference,AtomicMarkableReference
开销问题
只能保证一个共享变量的原子操作 AtomicReference
阻塞队列(BlockingQueue): 生产者与消费者模式里,生产者与消费者解耦,生产者与消费者性能均衡问题
有界:定义最大容量 无界:不定义
线程池:线程管理与统一调度
创建:
ThreadPoolExecutor(int corePoolSize, //核心线程数
int maximumPoolSize, //最大线程数
long keepAliveTime, //空闲线程存活时间
TimeUnit unit,
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程池工厂
RejectedExecutionHandler handler) //拒绝策略
线程比较稀缺,需合理配置,根据任务特性
cpu密集型:内存中取数据计算 (最大线程数 <=Runtime.getRuntime().availableProcessors()+1) 注:+1 虚拟内存-页缺失
IO密集型:网络通信、读写磁盘 (最大线程数 <= cpu核心*2), 高阻塞低占用
混合型:以上两者,如果两者执行时间相当,拆分线程池;否则视权重大的分配。具体时长可以本地测试或者根据下面的经验表预估后选择
执行:注意任务存放位置顺序 1、2、3、4(重点)
拒绝策略(饱和策略):
关闭:
awaitTermination(long timeout, TimeUnit unit)//阻塞
shutDown()//中断没有执行任务的线程
shutdownNow()//中断所有线程,协作式处理,只是发出中断信号
isShutdown()//是否关闭