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)
Thread生命周期

==抢占式调度策略==

系统会给每个可执行的线程一小段的时间来处理任务;当该时间段使用完,系统就会剥夺该线程所占据的资源,让其他线程获得执行的机会。在选择下一个线程时,系统会考虑线程的优先级。
就绪和运行状态之间的转换通常不受程序控制,而是由系统线程调度所导致。

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;
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容

  • 写在前面的话: 这篇博客是我从这里“转载”的,为什么转载两个字加“”呢?因为这绝不是简单的复制粘贴,我花了五六个小...
    SmartSean阅读 4,730评论 12 45
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,454评论 1 15
  • Java 多线程 线程和进程的区别 线程和进程的本质:由CPU进行调度的并发式执行任务,多个任务被快速轮换执行,使...
    安安zoe阅读 2,200评论 1 18
  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 7,353评论 3 87
  • 随着年龄的增长 对bling bling的东西不再有好感 反而喜欢简单的线条 卡通的图案 这便是所谓的装嫩神器吧 ...
    PWong阅读 156评论 0 0