android asyncTask多线程优化
AsyncTask的线程优化,我们先了解线程和它在java中的怎么使用的。然后分析android中的实现方法。在模拟实验存在的问题。给出解决方法。
1.线程
单线程只有一个顺序执行流,多线程则可以包括多个顺序执行,多个顺序流之间互不干扰。
1.1进程
进程是处于运行过程中的程序,并具有一定独立功能,是系统进行资源分配和调度的一个独立单位。
特征:
- 独立性:进程是系统中独立存在的实体,可以拥有自己独立的资源,每个进程都有自己私有的地址空间(独立的代码和数据空间)。但是进程间的切换会有较大的开销。
- 动态性:进程是一个正在系统中动态指令集合。并在进程中加入了时间概念。进程具有自己的生命周期和各种不同的状态。
- 并发性:多个进程可以在单个处理器上并发执行,多个线程之间不会互相影响。并行指同一时刻,有多条指令在多个处理器上同时执行。并发指在同一时刻,只能有一条指令执行,但多个进程,使得在宏观上具有多个进程同时执行的效果。
1.2线程
线程也被称作轻量级进程。线程是进程的执行单元。就像进程在操作系统中的地位一样,线程在程序中是独立的,并发的执行流。当进程被初始化之后,主线程就被创建了。通常一个程序只有一个主进程,但我们也可以在该进程内创建多条顺序执行流,这些顺序执行流就是Thread,每条Thread也是互相独立的。
- 一个线程可以拥有自己的堆,栈,自己的程序计算器和自己的局部变量,但不再拥有系统资源,它与父进程的其他线程共享该进程所有的全部资源。
- 多个进程共享父进程全部资源,因此我们必须确保一个进程不会妨碍同一进程的其他线程。
- 线程是独立运行的,它并不知道进程中是否还有其他线程的存在。线程的运行是抢占式。当前运行的进程在任何时候都可能被挂起,以便另一个线程可以运行。
- 一个线程可以创建和撤销另一个线程,同一个进程(Process)的多个线程(Thread)之间可以并发执行。
2.Thread和Runnable
2.1 Thread创建和启动
- 1.定义子Thread并重写run()。
- 2.创建线程对象
- 3.线程对象用start方法启动线程
static class FirstThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(this.getName() + " " + i);
}
}
}
2.2 实现Runnable接口创建线程类
1.定义实现Runnable接口的类,重写run方法
public class SecondThread implements Runnable
2.创建Runnable实现类的对象,并以此作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象。
SecondThread st = new SecondThread();
3.调用线程对象的start方法来启动该线程
new Thread(st,"TXB").start();
2.3 Thread和Runnable比较
优缺点 | 继承Thread | 实现Runnable |
---|---|---|
优点 | 简单,直接使用this.getName()来获取当前线程(因为本身是一个线程类对象) | 1.只是实现了Runnable接口,还可以继承其他类2.多个线程共享一个target对象,非常适合多个线程来处理同一份资源的情况 |
缺点 | 因为线程类已经继承了Thread,所以不能再继承其他父类了 | 略微复杂,要使用Thread.currentThread()来获取当前线程 |
2.4 线程的生命周期
- 新建(new)
- 就绪(runnable)
- 运行(running)
- 阻塞(blocked)
- 死亡(dead)
==抢占式调度策略==
系统会给每个可执行的线程一小段的时间来处理任务;当该时间段使用完,系统就会剥夺该线程所占据的资源,让其他线程获得执行的机会。在选择下一个线程时,系统会考虑线程的优先级。
就绪和运行状态之间的转换通常不受程序控制,而是由系统线程调度所导致。
2.5 join
当在某个程序执行流中A调用其他线程的join方法,A(调用join方法的那个线程)将被阻塞,知道join方法加入的join线程完成为止。
public class Join {
public static class JoinThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public static void main(String[] args) {
JoinThread jt = new JoinThread();
new Thread(jt,"TXB").start();
for(int i=0;i<100;i++){
System.out.println("i:" + i);
if(i == 20){
Thread joinThread = new Thread(jt, "joinThread");
joinThread.start();
try {
joinThread.join();
} catch (InterruptedException e) {
System.out.println("e:" + e);
}
}
}
}
}
上面程序一共有3条线程:
执行结果主线程开始之后启动了名为“TXB”的线程,该子线程将会和main线程并发执行
当主线程的循环变量i等于20时,启动了名为“joinThread”的线程,然后这个线程join进了main线程。注意:此时“joinThread”不会和main线程并发执行,而是main线程必须等该线程执行结束后才可以向下执行。在“joinThread”执行时,实际上只有两条子线程(“TXB” 和 “joinThread”)并发执行,而main线程处于等待(阻塞)状态知道“joinThread”执行完。
2.6 后台程序(Daemon Thread)
-
指在后台运行的 线程,任务是为其他的线程提供服务。 JVM的垃圾回收线程就是典型的后台线程。
-
特征:如果所有的前台线程都死亡,那么后台线程会自动死亡。当整个虚拟机中只剩下后台线程时,程序就没有继续运行的必要了,所以虚拟机也就退出了。
-
设置指定线程为后台线程: 调用Thread对象的setDaemon()方法
-
前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程
public class DaemonThread {
public static class Daemon implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public static void main(String[] args) {
Daemon d = new Daemon();
Thread t = new Thread(d, "DaemonThread");
t.setDaemon(true);
t.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
执行结果
上面代码在main方法里先将t设置为后台线程,然后启动该线程(==要将某个线程设置为后台线程,必须在该线程启动之前设置,setDaemon(true)必须在start()之前调用,否则会引发IllegalThreadStateException==)。本来该线程和ing该执行到i = 99才会结束,但是实际上它无法运行到99,因为主线程(程序中唯一的前台线程)运行结束后,JVM会自动退出,后台线程也就自动死亡了。
2.7 sleep和yield
sleep方法:Thread类的静态方法,让当前正在执行的线程暂停一段时间,并且进入阻塞状态。当当前线程调用sleep方法进入阻塞状态之后,在它sleep的时间里,它不会获得执行的机会。就算系统中没有其他可运行的程序,处于sleep的线程也不会运行,因此sleep方法常用于暂停程序的运行。
static void sleep(long millis)
static void sleep(long millis,int nanos)
yield:和sleep有点类似,也是Thread类的一个静态方法。它也可以让当前正在执行的线程暂停,但不会使线程阻塞,只是将线程转入就绪状态。
sleep | yield |
---|---|
sleep暂停当前线程之后,会给其他线程执行机会,并不考虑线程优先级 | yield方法暂停当前线程之后,==只有和当前线程优先级相同或者更高的处于就绪状态(runnable)的线程才能有执行的机会== |
sleep方法会将线程转入阻塞状态,知道经过了设定的阻塞时间才会转到就绪状态 | yield方法不会将线程转入阻塞状态,它只是强制让当前线程从运行状态(runnig)转到就绪状态(runnable)。因此完全有可能某个线程调用yield暂停之后又马上获得CPU资源被执行 |
sleep方法会抛出InterruptedException异常 | 不抛异常 |
sleep方法比yiled方法具有更好的移植性 | 通常不要依靠yield来控制并发线程的执行 |
2.9生产者和消费者
死锁出现的原因
产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生.
- 1.互斥条件:线程要求对所分配的资源进行排他性控制,即在一段时间内某 资源仅为一个进程所占有.此时若有其他进程请求该资源.则请求进程只能等待.
- 2.不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的线程自己来释放(只能是主动释放).
- 3.请求和保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求线程被阻塞,但对自己已获得的资源保持不放.
- 4.循环等待条件:存在一种线程资源的循环等待链,链中每一个线程已获得的资源同时被链中下一个线程所请求。
// 产品
static class ProductObject {
// 线程操作变量可见
public volatile static String value;
}
// 生产者线程
static class Producer extends Thread {
Object lock;
public Producer(Object lock) {
this.lock = lock;
}
@Override
public void run() {
// 不断生产产品
while (true) {
synchronized (lock) { // 互斥锁
// 产品还没有被消费,等待
if (ProductObject.value != null) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 产品已经消费完成,生产新的产品
ProductObject.value = "NO:" + System.currentTimeMillis();
System.out.println("生产产品:" + ProductObject.value);
lock.notify(); // 生产完成,通知消费者消费
}
}
}
}
// 消费者线程
static class Consumer extends Thread {
Object lock;
public Consumer(Object lock) {
this.lock = lock;
}
@Override
public void run() {
while (true) {
synchronized (lock) {
// 没有产品可以消费
if (ProductObject.value == null) {
// 等待,阻塞
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费产品:" + ProductObject.value);
ProductObject.value = null;
lock.notify(); // 消费完成,通知生产者,继续生产
}
}
}
}
3. Callabe , Future ,线程池
Callable是Runnable接口的增强版,Callable也提供了一个call()方法作为线程执行体
call()方法可以有返回值
call()方法可以声明抛出异常
Callable问题:
Callable接口并不是Runnable接口的子接口,而Thread的构造方法里形参的类型Runnable,所以Callable对象不能直接做为Thread的target;而且call方法不能直接调用,而是作为线程执行体被调用。
为了解决这几个问题:
java提供了Future接口来代替Callable接口的call方法,并为Futrue接口提供一个FutureTast实现类,这个实现了Future接口,也实现了Runnable接口。
线程池
线程池在系统启动时就创建了大量空闲的线程,程序将一个Runnable对象传给线程池,线程池就会启动一条线程来执行该对象的run方法,当run方法执行结束之后,该线程不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run方法。
Executors工厂类来生产线程池
方法 | 描述 |
---|---|
newCachedThreadPool() | 创建一个具有缓存功能的线程池,系统根据需要创建线程,这线程会被缓存在线程池中 |
newFixedThreadPool(int nThreads) | 创建一个可重用的、具有固定线程数的线程池 |
newSingleThreadExecutor() | 创建一个只有单线程的线程池,相当于newFixedThreadPool(int nThreads)传入参数为1 |
newScheduledThreadPool(int corePoolSize) | 创建具有固定线程数的线程池,可以在指定延迟后执行线程任务。corePoolSize指池中所保存的线程数,即使线程是空闲的也被保存在线程池里 |
newSingleThreadScheduledExecutor() | 创建只有固定线程的线程池,可以在指定延迟后执行线程任务。 |
前三个方法返回一个ExecutorService对象,该对象代表一个线程池,可以执行Runnable或Callable对象所代表的线程。
后两个方法返回一个ScheduledExecutorService,是ExecutorService的子类,可以在指定延迟后执行线程任务。
使用线程池来执行线程任务的步骤:
- 调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池。
- 创建Runnable或Callable实现类的实例,作为线程任务
- 调用ExecutorService对象的submit方法来提交Runnable或Callable任务
- 当不想再提交任何任务时调用ExecutorService对象的shutdown方法来关闭线程池
public static void main(String[] args) {
Task work = new Task();
FutureTask<Integer> future = new FutureTask<Integer>(work){
//异步任务执行完成,回调
@Override
protected void done() {
try {
System.out.println("done:"+get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
};
//线程池(使用了预定义的配置)
ExecutorService executor = Executors.newCachedThreadPool();
//executor.submit(future);
executor.execute(future);
// new Thread(future,"TXB").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
//取消异步任务
//future.cancel(true);
try {
//阻塞,等待异步任务执行完毕
System.out.println(future.get()); //获取异步任务的返回值
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
//异步任务
static class Task implements Callable<Integer>{
//返回异步任务的执行结果
@Override
public Integer call() throws Exception {
int i = 0;
for (; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName() + "_"+i);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return i;
}
}
4.android中的AsyncTask
在android中调用流程
AsyncTask流程
模拟android中的AsyncTask
public static void main(String[] args) {
int CPU_COUNT = Runtime.getRuntime().availableProcessors(); //可用的CPU个数
int CORE_POOL_SIZE = CPU_COUNT + 1; //5
int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; //9
int KEEP_ALIVE = 1;
//任务队列(128)
final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
//线程工厂
ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
String name = "Thread #" + mCount.getAndIncrement();
System.out.println(name);
return new Thread(r, name);
}
};
//线程池
Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
//执行异步任务
//如果当前线程池中的数量大于corePoolSize,缓冲队列workQueue已满,
//并且线程池中的数量等于maximumPoolSize,新提交任务由Handler处理。
//RejectedExecutionException
for (int i = 0; i < 200; i++) {
//相当于new AsyncTask().execute();
THREAD_POOL_EXECUTOR.execute(new MyTask());
}
}
static class MyTask implements Runnable{
@Override
public void run() {
//System.out.println(Thread.currentThread().getName());
while(true){
try {
System.out.println(Thread.currentThread().getName());
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
问题
会出现这问题:这个问题产生的原因是默认情况任务队列只分配了128,如果我们生存了200个任务,就会出现这个问题。还有一个关键是任务阻塞了,也就是很耗时的时候。
怎么解决了:我们可以进行线程池扩容
Executor THREAD_POOL_EXECUTOR = Executors.newFixedThreadPool(25);
在android中调用异步任务时使用new MyTask().executeOnExecutor(exec);
在这有可能出现内存泄露的问题。
产生的原因是如果在子线程中一直处理一些事情,时间比较长,activity在旋转或退出的时候,这个线程还好执行,并保存activity的引用,让其无法释放。
解决方法:
@Override
protected void onDestroy() {
super.onDestroy();
task.cancel(true); // 取消任务
}
class MyTask extends AsyncTask<Void, Integer, Void> {
int i = 0;
@Override
protected Void doInBackground(Void... params) {
while (!isCancelled()) {
// Log.d("jason", String.valueOf(i++));
SystemClock.sleep(1000);
}
return null;
}
}