多线程

多线程


常见的耗时的操作如下:

  • 网络请求
  • 文件读写
  • 创建、删除、更新数据库中的内容
  • SharedPreferences读写
  • 图片处理
  • 文本解析

对耗时任务其实并没有一个清晰固定的定义,但是只要应用中出现了卡顿的情况,比如按钮反应慢,动画卡壳,这些都算是
耗时任务。特别是,在UI线程中对动画效果的计算比点击按钮更敏感

运行环境

一个Android应用中的线程使用java.lang.Thread来表示。它是Android中最基础的运行环境。一个线程存活的时间取决于任务
的长短。线程支持实现了java.lang.Runnable接口的任务的执行。

private class MyTask implements Runnable{ 
     public void run(){ 
     int i = 0; // Stored on the thread local task 
     } 
  }

run()方法中的所有本地变量都将会被存储在这个线程的内存栈中。当初始化和开始了一个线程之后,这个任务就开始执行
了。
Thread myThread = new Thread(new MyTask()); myThread.start();
在操作系统层面,一个线程有一个指令指针和一个栈指针。指令指针指向下一条要执行的指令,栈指针指向了一块私有的内存区域——对其他线程是不可用的——这里存储了线程的本地数据。线程本地数据从代码上来看,其实就是定义在run方法里的变量。

CPU在某一刻只能执行从一个线程中执行代码,但是一个系统通常有多个要求同时执行的线程,比如一个同时运行多个应用的系统。从用户的感受来说,应用可以并行执行,CPU必须将他的处理时间在多个应用线程之间进行分享。调度器用来处理对CPU执行时间的分配。调度策略可以有多种实现方式,但是它主要是基于线程优先级:具有较高优先级的线程会先于较低
优先级的线程获得CPU。在java中,线程的优先级被设置为1——10,但是,如果不明确设定优先级,默认的优先级是5

myThread.setPriority(8);

然而,如果调度过程只依赖于优先级的高低,那么优先级较低的线程可能无法获得足够的运行时间来完成任务。因此,调度器也应该在改变执行线程的时候将每个线程的运行时间考虑在内。运行线程的切换被称为contextswitch(上下文切换)。当运
行中的线程的状态被存储完成后,context switch开始,这使得随后可以恢复这个线程的状态,然后,这个线程进入wait(等
待/挂起)状态。接下来调度器将会恢复一个等待中的线程开始执行。

单线程应用

每个应用至少有一个线程。如果没有其他的线程,那么所有的代码都会运行在同一个执行序列中,每条指令都必须等待在它之前的所有指令都运行完成后,它自己才能运行。
优点:

  • 单线程执行环境是一种比较简单的编程模型。

缺点:

  • 但是在很多情况下,单线程模型并不能满足需要,因为某些指令的执行时机可能会由于之前的指令而被明显的推迟,即使这些指令的执行并不依赖与前面的指令。降低了应用的性能和响应性。

解决方案:

  • 为了解决这些问题,应用需要把代码查分到多个执行路径中,也就是多个线程。

多线程应用

  • 在多线程环境中,应用程序代码可以被拆分到多个执行路径中,所以看起来好像多个操作在同时运行。
  • 如果需要执行的线程数量超出了可执行的数量,真正的并发不会被实现,但是,调度器可以迅速地在不同线程之间进行切换,以至于每个代码路径都可以分到一定的执行时间。
  • 多线程是必须的,提高性能的同时也带来了一些问题——编程复杂性提高,内存消耗增大,不确定的程序运行顺序。资源消耗增大
  • 每个线程需要拥有一块私有的内存空间,主要用来存储自己的本地变量,

多线程的方式

  • 当我们启动一个App的时候,Android系统会启动一个Linux Process,该Process包含一个Thread,称为UI Thread或Main Thread。通常一个应用的所有组件都运行在这一个Process中,当一个组件在启动的时候,如果该process已经存在了,那么该组件就直接通过这个process被启动起来,并且运行在这个process的UI Thread中。
  • UI Thread中运行着许多重要的逻辑,如系统事件处理,用户输入事件处理,UI绘制,Service,Alarm等,如下图:
2839011-23a52dcb6f73550f.png

Android提供常见的操作多线程的方式

1. Handler+Thread
2. AsyncTask
3. Rxjava

各个方式的优缺点:

handler sendMessage用法
图解

2839011-7036df5ffea97ec2.png

优缺点:

1. Handler用法简单明了,可以将多个异步任务更新UI的代码放在一起,清晰明了
2. 处理单个异步任务代码略显多

适用范围:

 多个异步任务的更新UI

AsyncTask
优缺点:

1. 处理单个异步任务简单,可以获取到异步任务的进度
2. 可以通过cancel方法取消还没执行完的AsyncTask
3. 处理多个异步任务代码显得较多

适用范围:

单个异步任务的处理

ThreadPoolExecutor
Executors提供了四种创建ExecutorService的方法,他们的使用场景如下:

1. Executors.newCachedThreadPool()
   创建一个定长的线程池,每提交一个任务就创建一个线程,直到达到池的最大长度,这时线程池会保持长度不再变化
2. Executors.newFixedThreadPool()
   创建一个可缓存的线程池,如果当前线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,当需要增加时,
    它可以灵活的添加新的线程,而不会对池的长度作任何限制
3. Executors.newScheduledThreadPool()
   创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer
4. Executors.newSingleThreadExecutor()
   创建一个单线程化的executor,它只创建唯一的worker线程来执行任务

适用范围

 批处理任务

Rxjava

1. Rx的全称:

Reactive Extensions(响应式扩展)。Rx基于观察者模式,目的是提供一致的编程接口,帮助开发者更方便的处理异步数据流。ReactiveX结合了观察者模式、迭代器模式和函数式编程的精华。而Rxjava是针对java语言的一个异步的响应式编程库。

2. 怎么使用Rxjava


1863579-b959455ac1f04ec3.png

基础:

  • Rxjava最核心的两个东西Observables(被观察者,事件源)和Subscribers(观察者)。Observables发出一系列事件,Subscribers处理这些事情。这些事情可以是触摸事件、web接口调用数据...
  • 一个Observable可以发出零个或者多个事件,直到结束或出错,没发出一个事件,就会调用Subscriber的onNext() 方法,
  • Rxjava看起来像设计模式中的观察者模式,但有一点不同,如果一个Observable没有任何的Subscriber,那么这个Observable就不会发出任何事件。
  • Hello World
                  Observable<String> observable=Observable.create(new  Observable.OnSubscribe<String>() {
                @Override
                public void call(Subscriber<? super String> subscriber) {
                    subscriber.onNext("Hello World");
                    subscriber.onCompleted();
                }
            });
            Subscriber<String> subscriber=new Subscriber<String>() {
                @Override
                public void onCompleted() {
       }
       @Override
                public void onError(Throwable e) {
    
                }
    
                @Override
                public void onNext(String s) {
                    System.out.print(s);
                }
            };
            observable.subscribe(subscriber);
        }

lambda可以使代码变得简洁: > ```Observable.just("Hello World")

            .subscribe(s->System.out.println(s));
            `
1863579-433c53337adbaf31.png

操作符(operators)

  • 操作符就是为了解决对Observable对象的变换的问题,操作符用于在Observable和最终的Subscriber之间修改Observable发出的事件。RxJava提供了很多很有用的操作符。
    比如map操作符,就是用来把把一个事件转换为另一个事件的。
Observable.just("Hello, world!")  
  .map(new Func1<String, String>() {  //string->string
      @Override  
      public String call(String s) {  
          return s + " -Dan";  
      }  
  })  
  .subscribe(s -> System.out.println(s));  
  
  Observable.just("Hello World")
                .map(s -> s.hashCode())         //string -> integer
                .subscribe(s->System.out.println(Integer.toString(s)));

操作符

假如有这样一个方法:
根据输入的字符返回一个网站的url列表类似于搜索引擎

public static Observable<List<String>> query(String s){
        List<String> urls=new ArrayList<>();
        urls.add("www");
        urls.add("http");
        urls.add("https");
        Observable<List<String>> observable=Observable.create(new Observable.OnSubscribe<List<String>>() {
            @Override
            public void call(Subscriber<? super List<String>> subscriber) {
                subscriber.onNext(urls);
            }
        });
        return observable;
    }
query(u).subscribe(urls-> {
            for (String url:urls)
            System.out.println(url);
        });
        query(u).subscribe(urls->{
        Observable.from(urls)
                .subscribe(url->System.out.println(url));
      });

Observable.from()方法他接收一个集合作为输入,然后每次输出一个元素给subscriber;(Observable.from也是Observable(被观察者对象))

query(u).subscribe(urls->{
        Observable.from(urls)
                .subscribe(url->System.out.println(url));
      });/*虽然去掉了for each循环,但是代码依然看起来很乱。多个嵌套的subscription不仅看起来很丑,难以修改,更严重的是它会破坏某些RxJava的特性。*/

改进——flatMap()
Observable.flatMap()接收一个Observable的输出作为输入,输出另一个Observable。

query("Hello World")
                .flatMap(new Func1<List<String>, Observable<String>>() {
                    @Override
                    public Observable<String> call(List<String> list) {
                        return  Observable.from(list);
                    }
                }).subscribe(url->System.out.println(url));

lambda简化代码

query("s")
                .flatMap(urls->Observable.from(urls))
                .subscribe(url->System.out.println(url));
flatMap()也可以返回任何它想返回的对象

丰富的操作符

  • filter过滤
  • take()输出最多指定数量的结果
  • doOnNext()允许我们在每次输出一个元素之前做一些额外的事情
    代码实例:
query("Hello, world!")  
    .flatMap(urls -> Observable.from(urls))  
    .flatMap(url -> getTitle(url))  
    .filter(title -> title != null)  
    .take(5)  
    .doOnNext(title -> saveTitle(title))  
    .subscribe(title -> System.out.println(title)); 

上述示例的功能过滤掉返回的title列表种null的值
输出最多5个结果
在println之前把每个标题保存到磁盘上

错误处理

  • onComplete()onError()函数
  • 伪代码
 Observable.just("Hello, world!")
    .map(s -> potentialException(s))
    .map(s -> anotherPotentialException(s))
    .subscribe(new Subscriber<String>() {
        @Override
        public void onNext(String s) { System.out.println(s); }
        @Override
        public void onCompleted() { System.out.println("Completed!"); }
         @Override
        public void onError(Throwable e) { System.out.println("error!"); }
    });

代码中的potentialException() 和 anotherPotentialException()有可能会抛出异常。每一个Observerable对象在终结的时候都会调用onCompleted()或者onError()方法,所以Demo中会打印”Completed!”或者"error!”。

  • 优点:
    • 只要有异常发生onError()一定会被调用这极大的简化了错误处理。只需要在一个地方处理错误即可以。
    • 操作符不需要处理异常将异常处理交给订阅者来做,Observerable的操作符调用链中一旦有一个抛出了异常,就会直接执行onError()方法。
    • 你能够知道什么时候订阅者已经接收了全部的数据。

调度器

多线程的实现——subscribeOn()指定观察者代码运行的线程,使用observerOn()指定订阅者运行的线程:

 myObservableServices.retrieveImage(url)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(bitmap -> myImageView.setImageBitmap(bitmap));
1863579-9b47a29c4e93f43b.png

多种线程:

• Schedulers.immediate,默认,当前线程,立即执行

• Schedulers.trampoline,当前线程,要等其他任务先执行

• Schedulers.newThread,启用一个新线程

• Schedulers.io,擅长读写文件、数据库、网络信息,一个无数量上限的线程池

• Schedulers.computation,擅长cpu密集型计算,固定线程池,不要做io操作,会浪费cpu

• AndroidSchedulers.mainThread,Android主线程

相比其他多线程的方式,rxjava只需要添加两个操作符其他代码保持不变就可以实现多线程。而AsyncTask等需要找出需要并发的部分

灵活地切换线程
1863579-ab448f99561e79c5.png

•subscribeOn只能定义一次,除非是在定义doOnSubscribe
•observeOn可以定义多次,决定后续代码所在的线程

订阅(subscriptions)

Observable.subscribe(),会返回一个SubScription对象,代表了被观察者和订阅者之间的联系。
在后面可以通过调用unsubscribe()取消订阅,从而达到避免内存泄露的目的

Subscription s = query("s")
                .flatMap(urls -> Observable.from(urls))
                .subscribe(url -> System.out.println(url));
//ctrl+Alt+V快速得到返回对象
s.unsubscribe();//取消订阅

Android中使用Rxjava——RxAndroid

AndroidSchedulers提供了针对Android的线程系统的调度器

在UI线程执行某些代码只需要调用AndroidSchedulers.mainThread();

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

推荐阅读更多精彩内容

  • 又来到了一个老生常谈的问题,应用层软件开发的程序员要不要了解和深入学习操作系统呢? 今天就这个问题开始,来谈谈操...
    tangsl阅读 4,088评论 0 23
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,952评论 1 18
  • 一、认识多任务、多进程、单线程、多线程 要认识多线程就要从操作系统的原理说起。 以前古老的DOS操作系统(V 6....
    GT921阅读 1,011评论 0 3
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,442评论 1 15
  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 7,333评论 3 87