精华内容
下载资源
问答
  • 多线程开发

    千次阅读 2007-07-07 16:12:00
    但随之而来的问题是,如何充分发挥多核平台的处理性能,对于当今的开发人员来说是一个机遇和挑战,这就是为什么我们要强调多线程开发的原因。下面将以一些理论和实例讲解如何进行多线程编程。多线程编程入门 多线程...
     
    

    引语

    随着计算机中央处理器主频的不断上升,能耗问题也成为当今一大主要问题,因为这个问题的出现,随即出现了双核处理器以及多核处理器,大大降低了计算机的能耗。但随之而来的问题是,如何充分发挥多核平台的处理性能,对于当今的开发人员来说是一个机遇和挑战,这就是为什么我们要强调多线程开发的原因。下面将以一些理论和实例讲解如何进行多线程编程。

    多线程编程入门

    Linux系统下的多线程编程

    Linux系统下的多线程编程入门一
    Linux系统下的多线程编程入门二
    Linux系统下的多线程编程入门三
    Linux系统下的多线程编程入门四
    Linux下多线程编程与信号处理易疏忽的一个例子
    Linux多线程编程和Linux 2.6下的NPTL

    讨论话题

    OpenMP并行程序库开发建议 OpenMP学习笔记
    OpenMP与C++:事半功倍获得多线程的好处(上) OpenMP与C++:事半功倍获得多线程的好处(下)
    VC8下编译OpenMP的问题 OPENMP MPICH2哪个并行效率高
    GDI和多线程 多线程开发中的死锁,优化等问题
    并行计算简介和多核编程demo 关于MPI运行时问题
    Fortran 学习资料 关于C++ Compiler
    并行计算 简介pram模型 关于并行计的算问题
    Linux系统下的多线程编程详细解析 多核平台下多任务Usage Model
    展开全文
  • android多线程开发

    千次阅读 2018-09-20 11:47:33
    1.多线程开发 1)多线程开发目的 2)多线程开发注意点 2.工作线程类型 1)Thread 2)intentService 3)handlerThread 4)AsyncTask 5)线程池 a、FixedTreadPool: b、CachedThreadPool: c、...

    目录

     

    1.多线程开发

    1)多线程开发目的

    2)多线程开发注意点

    2.工作线程类型

    1)Thread

    2)intentService

    3)handlerThread

    4)AsyncTask

    5)线程池

    a、FixedTreadPool:

    b、CachedThreadPool:

    c、ScheduledThreadPool.Scheduled:

    d、SingleThreadExecutor:

    e、4种常见线程池的配置

    f、线程池常见方法


    1.多线程开发

    1)多线程开发目的

    当我们应用启动时候,系统会为我们创建一个主线程。主线程可以向我们UI控件分发事件以及绘制页面等事件。所以我们也常称之为UI线程。

    开发的时候要避免在UI线程做一些耗时的操作,因为会阻塞UI线程,导致停止事件的分发,此时的界面就像是卡住一般。更严重的情况,如果耗时操作时间大于5S,程序还会ANR,此时系统会弹窗提示我们程序无响应。

    还有2种情况,不过这2种ANR并不会弹窗提示,仅是输出log而已:

    • 主线程在执行BroadcastReceiver的onReceive函数时10秒内没有执行完毕

           BroadcastReceiver(简称BR)的onReceive函数运行在主线程中,当这个函数超过10秒钟没有返回就会触发ANR

    • 主线程在执行Service的各个生命周期函数时20秒内没有执行完毕

           Service的各个生命周期函数也运行在主线程中,当这些函数超过20秒钟没有返回就会触发ANR。

    因此,将耗时操作放到非UI线程中显得尤为重要,后面说一些开发中常见的工作线程。

    2)多线程开发注意点

    不要在非UI线程更新UI组件,可以尝试以下方式:

    a、Activity.runOnUiThread(Runnable)
    b、View.post(Runnable)    View.postDelayed(Runnable, long)
    c、handler

    2.工作线程类型

    1)Thread

    new Thread(new Runnable() {
            public void run() {
                final Bitmap bitmap = 网络下载的图片;
                //工作线程切换至UI线程更新UI
                mImageView.post(new Runnable() {
                    public void run() {
                        mImageView.setImageBitmap(bitmap);
                    }
                });
            }
        }).start();
    

    2)intentService

    特点:intentService处理完耗时操作自动销毁自身。但是页面在网络订阅事件完成前退出,还是需要手动销毁订阅事件。

    intentService:内部开启了一个Worker Thread(工作线程)处理队列中的Intent,处理完成一个之后在处理第二个,每个请求都会在单独的Worker Thread中处理

    public class MyIntentService extends IntentService {
    
    //构造方法 一定要实现此方法否则Service运行出错。
    public MyIntentService() {
            super("MyIntentService");
    }
    
    
    // onHandleIntent方法-耗时操作的处理
    @Override
    protected void onHandleIntent(Intent intent) {
    try {
    Thread.sleep(20000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    Log.e("MyIntentService","睡眠结束");
    }
    }
    //FIXME 多次启动新任务--发现第一个执行完才会去执行第二个任务
    startService(new Intent(this,MyIntentService.class));
    startService(new Intent(this,MyIntentService.class));

    3)handlerThread

    我的这篇文章中讲到了handlerThread的用法:https://blog.csdn.net/qq_37321098/article/details/81535449

    handlerThread和intentService类似,都会处理完上个任务,再去处理下个任务。

    4)AsyncTask

    AsyncTask也可以看我的这篇:https://blog.csdn.net/qq_37321098/article/details/81625580

    AsyncTask可以串行或者并行的去执行后台任务。内部封装了Handler,4个重要的方法,除了doInBackGround方法是在工作线程运行,其余的都是在主线程运行,省去了线程切换去更新UI的操作。

    5)线程池

    线程池对线程做了统一的管理,不需要我们来一个耗时任务就开一个线程,这样会造成不必要的开销。我们可以根据业务需求选择合适的线程池类型去实现。常见的几种线程池如下:

    a、FixedTreadPool:

    线程数量固定的线程池,仅有核心线程且没有超时策略,所以线程不会被回收。这意味着它能够快速的响应外界请求。

     ExecutorService executorService = Executors.newFixedThreadPool(2);
            executorService.execute(runnable);
            executorService.execute(runable1);
            executorService.execute(runable2);
            executorService.execute(runable3);

    上面代码含义为同时有2个核心线程处理任务,当其中一个任务执行完毕才会去执行下一个。

    b、CachedThreadPool:

    是一种线程数量不定的线程池,只有非核心线程,可以简单理解为最大线程数是无限大的。CachedThreadPool的任务队列相当于一个空集合,这导致任何任务都会被立即执行,比较适合做一些大量的耗时较少的任务。

    ExecutorService executorService = Executors.newCachedThreadPool();
            executorService.execute(runnable);
            executorService.execute(runable1);
            executorService.execute(runable2);
            executorService.execute(runable3);

    上面代码含义为同时执行4个任务,但是顺序不一定和执行的顺序一样。

    c、ScheduledThreadPool.Scheduled:

    核心线程池的数量书固定的且非核心线程池是没有限制的,非核心线程池被闲置时会被立即回收。

    主要用于执行定时任务和具有固定周期的重复任务

     ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
            scheduledExecutorService.schedule(runnable, 2000, TimeUnit.MILLISECONDS);
            scheduledExecutorService.schedule(runable1, 2000, TimeUnit.MILLISECONDS);
            scheduledExecutorService.schedule(runable2, 2000, TimeUnit.MILLISECONDS);
            scheduledExecutorService.schedule(runable3, 2000, TimeUnit.MILLISECONDS);

    上面代码含义为延时2S执行任务

     scheduledExecutorService.scheduleAtFixedRate(runnable,1000,1000,TimeUnit.MILLISECONDS);

    上面代码含义为延迟1S,然后每隔1S执行一次runnable的任务

    d、SingleThreadExecutor:

    只有一个核心线程,所有的任务都是在一个线程里顺序执行。

    ExecutorService executorService=Executors.newSingleThreadExecutor();
                executorService.execute(runnable);

    e、4种常见线程池的配置

    参考自:https://blog.csdn.net/u012702547/article/details/52259529

    Android中常用的线程池都是通过对ThreadPoolExecutor进行不同配置来实现的。举个例子:

    public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }

    看下ThreadPoolExecutor的这个参数最多的构造方法(有4个):

    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler)

    corePoolSize  线程池中核心线程的数量

    maximumPoolSize  线程池中最大线程数量

    keepAliveTime 非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长

    unit 第三个参数的单位,有纳秒、微秒、毫秒、秒、分、时、天等

    workQueue 线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。

    threadFactory  为线程池提供创建新线程的功能,这个我们一般使用默认即可

    handler 拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。

    f、线程池常见方法

    ExecutorService.shutdown():阻止新来的任务提交,对已经提交了的任务不会产生任何影响。通过将线程池的状态改成SHUTDOWN,当再将执行execute提交任务时,如果测试到状态不为RUNNING,则抛出rejectedExecution,从而达到阻止新任务提交的目的。

    ExecutorService.shutdownNow():阻止新来的任务提交,同时会中断当前正在运行的线程,即workers中的线程。另外它还将workQueue中的任务给移除,并将这些任务添加到列表中进行返回。通过将线程池的状态改成STOP,当再将执行execute提交任务时,如果测试到状态不为RUNNING,则抛出rejectedExecution,从而达到阻止新任务提交的目的.

    ExecutorService.awaitTermination(long timeout, TimeUnit unit):这个方法有两个参数,一个是timeout即超时时间,另一个是unit即时间单位。这个方法会使线程等待timeout时长,当超过timeout时间后,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用。例如:

    ExecutorService.isTerminated:当调用shutdown()方法后,并且所有提交的任务完成后返回为true,这个方法会校验ExecutorService当前的状态是否为“TERMINATED”即关闭状态,当为“TERMINATED”时返回true否则返回false。

    ExecutorService service = Executors.newFixedThreadPool(3);  
    service.submit(runnbale);  
    service.submit(runnbale1);  
    service.submit(runnbale2);  
    
    service.shutdown();  
    System.out.println(service.isTerminated());  

    日志显示为false,即没有关闭,因为要等待当前任务都执行完,才会返回false。

    ExecutorService.iisShutdown:当调用shutdown()方法后返回为true。判断是否为阻止新任务接收的状态SHUTDOWN。

    ExecutorService.submit:与execute执行不同,submit的提交是带返回值的,会将Future返回。

     

     

    展开全文
  • 对于iOS多线程开发,我们时刻处于学习之中,在看书中,看文档中,项目开发中,都可以去提高自己。最近刚看完了《Objective-C高级编程 iOS与OS X多线程和内存管理》这本书后,对多线程有了更为深入的理解,故在此做一...

            对于iOS多线程开发,我们时刻处于学习之中,在看书中,看文档中,项目开发中,都可以去提高自己。最近刚看完了《Objective-C高级编程 iOS与OS X多线程和内存管理》这本书后,对多线程有了更为深入的理解,故在此做一个总结与记录。这本书是iOS开发者必读的书之一,写得很不错。书的封面如下,故也称狮子书:


    (1)多线程会遇到的问题


    多线程会出现什么问题呢?当多个线程对同一个数据进行修改的时候,会造成数据不一致的情况;当多个线程互相等待会造成死锁;过多的线程并发会大量消耗内存。所以多线程出现bug会严重影响App的功能和性能。


    (2)多线程的作用

    我们为什么一定要用多线程呢?只要有一个主线程不就可以了么。



    从图中可以看到,如果我们有一个很耗时的操作放到主线程中执行,那么会严重阻塞主线程的执行,导致主界面不能及时响应用户的操作,出现卡死状态。当创建多线程之后,我们可以把耗时操作放到其他线程中去执行,比如网络操作,图片的上传下载等,当执行成功后再回到主线程更新界面即可。所以,使用多线程是必要的。


    (3)GCD中的队列——Dispatch Queue


    Dispatch Queue就是GCD中的调度队列,我们只要把任务添加到队列中去,线程就会按照顺序取出然后去执行,按照先进先出FIFO的原则。



    (4)Dispatch Queue的种类

    GCD中存在两种Dispatch Queue,一种是等待现在执行中处理的Serial Dispatch Queue(串行队列);另一种是不等待现在执行中处理的Concurrent Dispatch Queue(并发队列).

    Serial Dispatch Queue:等待现在执行中处理结束;

    Concurrent Dispatch Queue:不等待现在执行中处理结束,使用多个线程同时执行多个处理。



    Serial Dispatch Queue为什么一定要等待处理结束才去执行下一个处理呢?因为Serial Dispatch Queue只有一个线程,一个线程同一时间当然只能执行一个任务,所以后续任务必须要进行等待。

    Concurrent Dispatch Queue由于可以创建多个线程,所以只要按顺序取出任务即可,把取出的任务放到不同的线程去执行,任务之间是否执行结束没有必然关系,所以不需要等待前一个处理结束,看起来就像是多个任务同时在执行一样,其实也的确是在同时执行。当然这个并发数量系统会有限制,我们代码也可以设置最大并发数。

    看了以上的解释,就知道Serial Dispatch Queue、Concurrent Dispatch Queue和线程的关系了,关系如下:




    (5)多个Serial Dispatch Queue实现并发,以及遇到的问题

          当生成多个Serial Dispatch Queue时,各个Serial Dispatch Queue将并行执行。虽然在一个Serial Dispatch Queue中同时只能执行一个追加处理,但是如果将处理分别追加到4个Serial Dispatch Queue中,各个Serial Dispatch Queue执行一个,即可以同时执行四个处理。


    虽然这种笨办法也可以实现并发,但是也会遇到大问题,那就是消耗大量内存:




    (6)资源竞争问题的解决

    当多个线程对同一数据进行操作时可造成竞争或者数据不一致。最简单的解决办法就是使用Serial Dispatch Queue。Serial Dispatch Queue只创建一个线程,而一次只能执行一个任务,只有当该任务执行结束才能去执行下一个,所以同一时间对某个竞争资源的访问是唯一的。示意图如下:



    (7)生成的Dispatch Queue必须由程序员释放。这是因为Dispatch Queue并没有像Block那样具有作为OC对象来处理的技术。通过dispatch_queue_create函数生成的Dispatch Queue在使用结束后通过dispatch_release释放。看个下面的例子:


    这样立即释放queue是否有问题呢?

    在dispatch_async函数中追加Block到Dispatch Queue后,即使立即释放Dispatch Queue,该Dispatch Queue由于被Block持有也不会被废弃,因而Block能够执行。Block执行结束后会释放Dispatch Queue,这时谁都不持有Dispatch Queue,因此它会被废弃。


    (8)系统标准提供的Dispatch Queue

    -- Main Dispatch Queue:在主线程中执行的queue,因为主线程只有一个,所以Main Dispatch Queue自然就是Serial Dispatch Queue。追加到Main Dispatch Queue的处理在主线程的RunLoop中执行。




    -- Global Dispatch Queue:是所有应用程序都能够使用的Concurrent Dispatch Queue,没有必要通过dispatch_queue_create函数逐个生成Concurrent Dispatch Queue,只要获取Global Dispatch Queue即可。其中有四个优先级,但是用于Global Dispatch Queue的线程并不能保证实时性,因此执行优先级只是大致的判断。

    对于Main Dispatch Queue和Global Dispatch Queue执行dispatch_retain函数和dispatch_release函数不会引起任何变化,也不会有任何问题。这也是获取并使用Global Dispatch Queue比生成、使用、释放Concurrent Dispatch Queue更轻松的原因。


    (9)dispatch_set_target_queue:改变生成的Dispatch Queue的执行优先级

    指定要变更执行优先级的Dispatch Queue为dispatch_set_target_queue函数的第一个参数,指定与要使用的执行优先级相同的Global Dispatch Queue为第二个参数(目标),第一个参数如果指定系统提供的Main Dispatch Queue和Global Dispatch Queue,则不会知道出现什么状态,因此这些均不可指定。


    (10)dispatch_after:延迟执行

    NSEC_PER_SEC:秒

    NSEC_PER_MSEC:毫秒


    (11)dispatch_barrier_async

    会等待追加到Concurrent Dispatch Queue上的并行执行的处理全部结束之后,再将指定的处理追加到该Concurrent Dispatch Queue中。然后在由dispatch_barrier_async函数追加的处理执行完毕后,Concurrent Dispatch Queue才恢复为一般的动作,追加到该Concurrent Dispatch Queue的处理又开始并行执行。示意图如下:

    ..


    (12)dispatch_async

    将指定的block“非同步”的追加到指定的Dispatch Queue中,dispatch_async函数不做任何等待。




    (13)dispatch_sync造成的问题

    一旦调用dispatch_sync函数,那么在指定的处理执行结束之前,该函数不会返回。但是dispatch_sync容易造成死锁。


    该源代码在Main Dispatch Queue即主线程中执行指定的Block,并等待其执行结束。而其实在主线程中正在执行这些源代码,所以无法执行追加到Main Dispatch Queue的Block。下面的例子也一样:


    Main Dispatch Queue中执行的Block等待Main Dispatch Queue中要执行的Block执行结束。当然Serial Dispatch Queue也会引起相同的问题。



    (14)dispatch_apply

    dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API。该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等到全部处理结束。



    因为在Global Dispatch Queue中执行处理,所以各个处理的执行时间不定,是不进行等待的追加任务。但是输出结果中最后的done必定在最后的位置上。这是因为dispatch_apply函数会等待全部处理执行结束。


    由于dispatch_apply函数也与dispatch_sync函数相同,会等待处理执行结束,因此推荐在dispatch_async函数中非同步的执行dispatch_apply函数。




    (15)dispatch_suspend/dispatch_resume

    当追加大量处理到Dispatch Queue时,在追加处理的过程中,有时希望不执行已追加的处理。在这种情况下,只要挂起Dispatch Queue即可。当可以执行时再恢复。

    -- dispatch_suspend函数挂起指定的Dispatch Queue:

    dispatch_suspend(queue);


    dispatch_resume函数恢复指定的Dispatch Queue:

    dispatch_resume(queue);


    这些函数对已经执行的处理没有影响。挂起后,追加到Dispatch Queue中但尚未执行的处理在此之后停止执行。而恢复则使得这些处理能够继续执行。


    (16)Dispatch Semaphore

    当不使用信号量的时候出现的bug.

    .

    这里使用Global Dispatch Queue更新NSMutableArray类对象,所以执行后由内存错误导致应用程序异常结束的概率很高。

    Dispatch Semaphore是持有计数信号,该计数是多线程编程中的计数类型信号。计数为0时等待,计数为1或者大于1时,减去1而不等待。

    创建semaphore:


    参数表示计数的初始值。


    dispatch_semaphore_wait函数等待Dispatch Semaphore的计数值达到或者等于1.当计数值大于等于1,或者在等待中计数值大于等于1时,对该计数进行减法并从dispatch_semaphore_wait函数返回。

    semaphore可以进行以下分支处理:



    dispatch_semaphore_wait函数返回0时,可安全的执行需要进行排他控制的处理。该处理结束时通过dispatch_semaphore_signal函数将Dispatch Semaphore的计数数值加1.

    案例:





    (17)dispatch_once

    dispatch_once函数是保证在应用程序执行中只执行一次指定处理的API。下面这种经常出现的用来初始化的源代码可通过dispatch_once函数简化:


    在多核CPU中,在正在更新表示是否初始化的标志变量时读取,就有可能多次执行初始化处理。而用dispatch_once函数初始化就不用担心了。这就是单例模式,在生成单例对象时使用。


    (18)GCD的基本实现与描述

    苹果官方说明:通常,应用程序中编写的线程管理用的代码要在系统级实现。

    什么是系统级实现?就是在iOS和macOS的核心XNU内核级上实现。因此,无论程序员如何努力编写管理线程的代码,在性能方面也不可能胜过XNU内核级所实现的GCD。

    用于实现Dispatch Queue而使用的软件组件:


    Dispatch Queue没有“取消”这一概念。一旦将处理追加到Dispatch Queue中,就没有办法可以将该处理删除,也没有办法就执行中取消该处理。


    展开全文
  • Android多线程开发总结

    千次阅读 2020-01-16 09:16:48
    多线程开发在Android技术中非常重要,能否熟练掌握这些技术是衡量一个工程师技术水平能力的一个重要标准,也是决定能否开发出高效优质应用的前提条件。下面将分别展开描述以及对比,并结合实际工作场合分析优劣。...

    Android多线程

    多线程开发在Android技术中非常重要,能否熟练掌握这些技术是衡量一个工程师技术水平能力的一个重要标准,也是决定能否开发出高效优质应用的前提条件。下面将分别展开描述以及对比,并结合实际工作场合分析优劣。主要有以下几种:

    • Thread
    • Handler
    • HandlerThread
    • IntentService
    • ThreadPool

    1 Thread(线程)

    1.1 定义

    一个基本的CPU执行单元 & 程序执行流的最小单元

    组成:线程ID + 程序计数器 + 寄存器集合 + 堆栈。比进程更小的可独立运行的基本单位,可理解为:轻量级进程。线程自己不拥有系统资源,与其他线程共享进程所拥有的全部资源。

    1.2 与进程的区别

    • 进程是资源分配的最小单位,线程是程序执行的最小单位。
    • 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
    • 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
    • 但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

    1.3 线程分类

    线程主要分为:守护线程、非守护线程(用户线程)

    1.3.1 守护线程

    • 定义:守护用户线程的线程,即在程序运行时为其他线程提供一种通用服务
    • 常见:如 垃圾回收线程
    • 设置方式:
    //设置该线程为守护线程
    thread.setDaemon(true);
    

    1.3.2 非守护线程(用户线程)

    主要包括:主线程 、子线程(工作线程)。

    a. 主线程(UI线程)

    • 定义:Android系统在程序启动时会自动启动一条主线程
    • 作用:处理四大组件与用户进行交互的事情(如UI、界面交互相关)
    • 注:因为用户随时会与界面发生交互,因此主线程任何时候都必须保持很高的响应速度,所以主线程不允许进行耗时操作,否则会出现ANR

    b. 子线程(工作线程)

    • 定义:手动创建的线程
    • 作用:耗时的操作(网络请求、I/O操作等)

    1.3.3 守护线程 与 非守护线程的区别

    • 区别:虚拟机是否已退出:
    • 当所有用户线程结束时,因为没有守护的必要,所以守护线程也会终止,虚拟机也同样退出;
    • 反过来,只要任何用户线程还在运行,守护线程就不会终止,虚拟机就不会退出

    1.3 线程优先级

    1.3.1 表示

    线程优先级分为10个级别,分别用Thread类常量表示。

    // 譬如:
    Thread.MIN_PRIORITY // 优先级1
    Thread.MAX_PRIORITY // 优先级10
    

    1.3.2 设置

    通过方法setPriority(int grade)进行优先级设置
    默认线程优先级是5,即 Thread.NORM_PRIORITY

    1.4 多线程 - 介绍

    多个线程同时进行,即多个任务同时进行

    其实,计算机任何特定时刻只能执行一个任务;
    多线程只是一种错觉:只是因为JVM快速调度资源来轮换线程,使得线程不断轮流执行,所以看起来好像在同时执行多个任务而已

    1.5 线程调度

    1.5.1 调度方式

    • 当系统存在大量线程时,系统会通过时间片轮转的方式调度线程,因此线程不可能做到绝对的并发
    • 处于就绪状态(Runnable)的线程都会进入到线程队列中等待CPU资源
      同一时刻在线程队列中可能有很多个

    在采用时间片的系统中,每个线程都有机会获得CPU的资源以便进行自身的线程操作;当线程使用CPU资源的时间到后,即时线程没有完成自己的全部操作,JVM也会中断当前线程的执行,把CPU资源的使用权切换给下一个队列中等待的线程。
    被中断的线程将等待CPU资源的下一次轮回,然后从中断处继续执行

    1.5.2 调度优先级

    Java虚拟机(JVM)中的线程调度器负责管理线程,并根据以下规则进行调度:

    • 根据线程优先级(高-低),将CPU资源分配给各线程
    • 具备相同优先级的线程以轮流的方式获取CPU资源
    • 示例

    存在A、B、C、D四个线程,其中:A和B的优先级高于C和D(A、B同级,C、D同级,那么JVM将先以轮流的方式调度A、B,直到A、B线程死亡,再以轮流的方式调度C、D

    1.6 线程的使用

    • 继承Thread类
    // 步骤1:创建线程类 (继承自Thread类)
    class MyThread extends Thread{
    // 步骤2:复写run(),内容 = 定义线程行为
        @Override
        public void run(){
        ... // 定义的线程行为
        }
    }
    // 步骤3:创建线程对象,即 实例化线程类
     MyThread mt=new MyThread(“线程名称”);
    // 步骤4:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起  / 停止
    // 此处采用 start()开启线程
    mt.start();
    
    • 配合Runnable接口创建线程
    // 步骤1:创建线程辅助类,实现Runnable接口
    class MyThread implements Runnable{
       ....
       @Override
    // 步骤2:复写run(),定义线程行为
       public void run(){
    
       }
    }
    // 步骤3:创建线程辅助对象,即 实例化 线程辅助类
    MyThread mt = new MyThread();
    // 步骤4:创建线程对象,即 实例化线程类;线程类 = Thread类;
    // 创建时通过Thread类的构造函数传入线程辅助类对象
    // 原因:Runnable接口并没有任何对线程的支持,我们必须创建线程类(Thread类)的实例,从Thread类的一个实例内部运行
    Thread td=new Thread(mt);
    // 步骤5:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起  / 停止
    // 当调用start()方法时,线程对象会自动回调线程辅助类对象的run(),从而实现线程操作
    td.start();
    
    • 匿名类创建线程

    new Thread(): 在阿里开发手册中明确禁止使用这种方式开启新线程,主要是因为新线程这样开启之后无法主动停止,只适合执行耗时短的轻量级任务

    // 步骤1:采用匿名类,直接 创建 线程类的实例
     new Thread("线程名称") {
     	 // 步骤2:复写run(),内容 = 定义线程行为
         @Override
         public void run() {       
      	 	// 步骤3:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起  / 停止   
       }
     }.start();
    

    1.7 new Thread()的缺点

    在实际多线程开发中,一般不建议直接用new Thread(),性能开销达,不好管理,而要使用下面即将提到的其他多线程技术。

    • 每次new Thread()耗费性能
    • 调用new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪。
    • 不利于扩展,比如如定时执行、定期执行、线程中断

    2 Handler

    2.1 定义

    一套 Android 消息传递机制

    2.2 作用

    在多线程的应用场景中,将工作线程中需更新UI的操作信息 传递到 UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理
    在这里插入图片描述

    2.3 意义

    多个线程并发更新UI的同时 保证线程安全

    2.4 相关概念

    Handler 、 Looper 、Message 这三者都与Android异步消息处理线程相关的概念。那么什么叫异步消息处理线程呢?
    在这里插入图片描述

    • 异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环。若消息队列为空,线程则会阻塞等待。
    • 那么Android消息机制主要是指Handler的运行机制,Handler运行需要底层的MessageQueue和Looper支撑。其中MessageQueue采用的是单链表的结构,Looper可以叫做消息循环。由于MessageQueue只是一个消息存储单元,不能去处理消息,而Looper就是专门来处理消息的,Looper会以无限循环的形式去查找是否有新消息,如果有的话,就处理,否则就一直等待着。
    • 我们知道,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,需要注意的是,线程默认是没有Looper的,如果需要使用Handler就必须为线程创建Looper,因为默认的UI主线程,也就是ActivityThread,ActivityThread被创建的时候就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。如果想让该线程具有消息队列和消息循环,并具有消息处理机制,就需要在线程中首先调用Looper.prepare()来创建消息队列,然后调用Looper.loop()进入消息循环。如以下代码所示:
      public class LopperThread extends Thread{
          public Handler mHandler;
          
          @Override
          public void run(){
              Looper.prepare();
              mHandler = new Handler(){
              
                  @Override
                  public void handleMessage(Message msg) {
                      super.handleMessage(msg);
                      //处理消息队列
                  }
              };
              Looper.loop();
          }
      }
      

    2.5 使用方式

    • Handler.sendMessage

    在该使用方式中,又分为2种:新建Handler子类(内部类)、匿名 Handler子类

    /** 
      * 方式1:新建Handler子类(内部类)
    */
    
    // 步骤1:自定义Handler子类(继承Handler类) & 复写handleMessage()方法
    class mHandler extends Handler {
    
        // 通过复写handlerMessage() 从而确定更新UI的操作
        @Override
        public void handleMessage(Message msg) {
         ...// 需执行的UI操作
            
        }
    }
    
    // 步骤2:在主线程中创建Handler实例
    private Handler mhandler = new mHandler();
    
    // 步骤3:创建所需的消息对象
    Message msg = Message.obtain(); // 实例化消息对象
    msg.what = 1; // 消息标识
    msg.obj = "AA"; // 消息内容存放
    
    // 步骤4:在工作线程中 通过Handler发送消息到消息队列中
    // 可通过sendMessage() / post()
    // 多线程可采用AsyncTask、继承Thread类、实现Runnable
    mHandler.sendMessage(msg);
    
    // 步骤5:开启工作线程(同时启动了Handler)
    // 多线程可采用AsyncTask、继承Thread类、实现Runnable
    
    /** 
    * 方式2:匿名内部类
    */
    // 步骤1:在主线程中 通过匿名内部类 创建Handler类对象
    private Handler mhandler = new  Handler(){
        // 通过复写handlerMessage()从而确定更新UI的操作
    	@Override
    	public void handleMessage(Message msg) {
    	    ...// 需执行的UI操作
    	}
    };
    
    // 步骤2:创建消息对象
    Message msg = Message.obtain(); // 实例化消息对象
    msg.what = 1; // 消息标识
    msg.obj = "AA"; // 消息内容存放
    
    // 步骤3:在工作线程中 通过Handler发送消息到消息队列中
    // 多线程可采用AsyncTask、继承Thread类、实现Runnable
    mHandler.sendMessage(msg);
    
    // 步骤4:开启工作线程(同时启动了Handler)
    // 多线程可采用AsyncTask、继承Thread类、实现Runnable
    
    • Handler.post
    // 步骤1:在主线程中创建Handler实例
    private Handler mhandler = new mHandler();
    
    // 步骤2:在工作线程中 发送消息到消息队列中 & 指定操作UI内容
    // 需传入1个Runnable对象
    mHandler.post(new Runnable() {
    	@Override
    	public void run() {
    	   ... // 需执行的UI操作 
    	}
    });
    
    // 步骤3:开启工作线程(同时启动了Handler)
    // 多线程可采用AsyncTask、继承Thread类、实现Runnable
    

    2.6 Handler内存泄漏

    1、内存泄露的定义:本该被回收的对象不能被回收而停留在堆内存中
    2、内存泄露出现的原因:当一个对象已经不再被使用时,本该被回收但却因为有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这就导致了内存泄漏
    3、主线程的Looper对象的生命周期 = 该应用程序的生命周期
    4、在Java中,非静态内部类 & 匿名内部类都默认持有 外部类的引用

    在Handler消息队列 还有未处理的消息 / 正在处理消息时,此时若需销毁外部类MainActivity,但由于引用关系,垃圾回收器(GC)无法回收MainActivity,从而造成内存泄漏。

    2.6.1 解决方案

    • 解决方案1:静态内部类+弱引用

    1、将Handler的子类设置成 静态内部类,同时,还可加上 使用WeakReference弱引用持有Activity实例。原因:弱引用的对象拥有短暂的生命周期。在垃圾回收器线程扫描时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
    2、为了保证Handler中消息队列中的所有消息都能被执行,此处推荐使用解决方案1解决内存泄露问题,即静态内部类 + 弱引用的方式。

    public class MainActivity extends AppCompatActivity {
    
        public static final String TAG = "carson:";
        private Handler showhandler;
    
        // 主线程创建时便自动创建Looper & 对应的MessageQueue
        // 之后执行Loop()进入消息循环
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            //1. 实例化自定义的Handler类对象
           // a. 此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue;
           // b. 定义时需传入持有的Activity实例(弱引用)
            showhandler = new FHandler(this);
            
            // 2. 启动子线程
            new Thread() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // a. 定义要发送的消息
                    Message msg = Message.obtain();
                    msg.what = 2;// 消息标识
                    msg.obj = "BB";// 消息存放
                    // b. 传入主线程的Handler & 向其MessageQueue发送消息
                    showhandler.sendMessage(msg);
                }
            }.start();
        }
    
        // 分析1:自定义Handler子类
        // 设置为:静态内部类
        private static class FHandler extends Handler{
            // 定义 弱引用实例
            private WeakReference<Activity> reference;
            
            // 在构造方法中传入需持有的Activity实例
            public FHandler(Activity activity) {
                // 使用WeakReference弱引用持有Activity实例
                reference = new WeakReference<Activity>(activity); }
    
            // 通过复写handlerMessage() 从而确定更新UI的操作
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 1:
                        Log.d(TAG, "收到线程1的消息");
                        break;
                    case 2:
                        Log.d(TAG, " 收到线程2的消息");
                        break;
                }
            }
        }
    }
    
    • 解决方案2:当外部类结束生命周期时,清空Handler内消息队列

    当 外部类(此处以Activity为例)结束生命周期时,清除 Handler消息队列里的所有消息。

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期
        mHandler.removeCallbacksAndMessages(null);
    }
    

    3 AsyncTask(Handler+线程池(默认串行))

    1、在工作线程中执行任务,如 耗时任务
    2、实现工作线程 & 主线程(UI线程)之间的通信,即:将工作线程的执行结果传递给主线程,从而在主线程中执行相关的UI操作
    3、从而保证线程安全

    3.1 优点

    • 方便实现异步通信

    不需使用 “任务线程(如继承Thread类)+ Handler”的复杂组合

    • 节省资源

    采用线程池的缓存线程 + 复用线程,避免了频繁创建 & 销毁线程所带来的系统资源开销

    3.2 缺点

    • 实现比较繁琐,代码可读性差

    实现一个AsyncTask比较繁琐,而且往往不用的业务需要实现不同的AsyncTask,导致代码可读性差一点,实际上项目用得不多,有其他更好的替代方案。

    3.3 实例

    public class MainActivity extends AppCompatActivity {
        // 线程变量
        MyTask mTask;
        // 主布局中的UI组件
        Button button,cancel; // 加载、取消按钮
        TextView text; // 更新的UI组件
        ProgressBar progressBar; // 进度条
     
        /**
         * 步骤1:创建AsyncTask子类
         * 注:
         *   a. 继承AsyncTask类
         *   b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替
         *      此处指定为:输入参数 = String类型、执行进度 = Integer类型、执行结果 = String类型
         *   c. 根据需求,在AsyncTask子类内实现核心方法
         */
        private class MyTask extends AsyncTask<String, Integer, String> {
            // 方法1:onPreExecute()
            // 作用:执行 线程任务前的操作
            @Override
            protected void onPreExecute() {
                text.setText("加载中");
                // 执行前显示提示
            }
            
            // 方法2:doInBackground()
            // 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果
            // 此处通过计算从而模拟“加载进度”的情况
            @Override
            protected String doInBackground(String... params) {
                try {
                    int count = 0;
                    int length = 1;
                    while (count<99) {
                        count += length;
                        // 可调用publishProgress()显示进度, 之后将执行onProgressUpdate()
                        publishProgress(count);
                        // 模拟耗时任务
                        Thread.sleep(50);
                    }
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            }
    
            // 方法3:onProgressUpdate()
            // 作用:在主线程 显示线程任务执行的进度
            @Override
            protected void onProgressUpdate(Integer... progresses) {
                progressBar.setProgress(progresses[0]);
                text.setText("loading..." + progresses[0] + "%");
            }
    
            // 方法4:onPostExecute()
            // 作用:接收线程任务执行结果、将执行结果显示到UI组件
            @Override
            protected void onPostExecute(String result) {
                // 执行完毕后,则更新UI
                text.setText("加载完毕");
            }
    
            // 方法5:onCancelled()
            // 作用:将异步任务设置为:取消状态
            @Override
            protected void onCancelled() {
                text.setText("已取消");
                progressBar.setProgress(0);
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            // 绑定UI组件
            setContentView(R.layout.activity_main);
            button = (Button) findViewById(R.id.button);
            cancel = (Button) findViewById(R.id.cancel);
            text = (TextView) findViewById(R.id.text);
            progressBar = (ProgressBar) findViewById(R.id.progress_bar);
            /**
             * 步骤2:创建AsyncTask子类的实例对象(即 任务实例)
             * 注:AsyncTask子类的实例必须在UI线程中创建
             */
            mTask = new MyTask();
    
            // 加载按钮按按下时,则启动AsyncTask
            // 任务完成后更新TextView的文本
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    /**
                     * 步骤3:手动调用execute(Params... params) 从而执行异步线程任务
                     * 注:
                     *    a. 必须在UI线程中调用
                     *    b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常
                     *    c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute()
                     *    d. 不能手动调用上述方法
                     */
                    mTask.execute();
                }
            });
    
            cancel = (Button) findViewById(R.id.cancel);
            cancel.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // 取消一个正在执行的任务,onCancelled方法将会被调用
                    mTask.cancel(true);
                }
            });
        }
    }
    

    4 HandlerThread(Handler+Thread)

    HandlerThread内部维护了一个消息队列,避免多次创建和销毁子线程来进行操作。

    • HandlerThread本质上是一个线程类,它继承了Thread;
    • HandlerThread有自己的内部Looper对象,可以进行looper循环;
    • 通过获取HandlerThread的looper对象传递给Handler对象,可以在handleMessage方法中执行异步任务。
    • 创建HandlerThread后必须先调用HandlerThread.start()方法,Thread会先调用run方法,创建Looper对象。

    4.1 实例

    // 步骤1:创建HandlerThread实例对象
    // 传入参数 = 线程名字,作用 = 标记该线程
    HandlerThread mHandlerThread = new HandlerThread("handlerThread");
    
    // 步骤2:启动线程
    mHandlerThread.start();
    
    // 步骤3:创建工作线程Handler & 复写handleMessage()
    // 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
    // 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
    Handler workHandler = new Handler( handlerThread.getLooper() ) {
    	@Override
    	public boolean handleMessage(Message msg) {
    	    ...//消息处理。因为这里是线程中的Looper,是可以做异步延迟的
    	    try {
                 //延时操作
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             // 这里是不能直接更新UI的,如果想要更新UI必须获取到主线程的Handler,才能更新
    	    return true;
    	}
    });
    
    // 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
    // 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
      // a. 定义要发送的消息
      Message msg = Message.obtain();
      msg.what = 2; //消息的标识
      msg.obj = "B"; // 消息的存放
      // b. 通过Handler发送消息到其绑定的消息队列
      workHandler.sendMessage(msg);
    
    // 步骤5:结束线程,即停止线程的消息循环
      mHandlerThread.quit();
    

    4.2 注意点

    • 内存泄漏

    关于Handler的内存泄露上面提到,使用HandlerThread要注意这块

    In Android, Handler classes should be static or leaks might occur.
    
    • 连续发送消息

    使用HandlerThread时只是开了一个工作线程,当你执行sendMessage n下后,只是将n个消息发送到消息队列MessageQueue里排队,等候派发消息给Handler再进行对应的操作。

    5 IntentService

    1、线程任务需按顺序在后台执行,比如离线下载
    2、不符合多个数据同时请求的场景:所有的任务都在同一个Thread looper里执行

    • IntentService是Service的子类,根据需要处理异步请求(以intent表示)。客户端通过调用startService(Intent) 发送请求,该Service根据需要启动,使用工作线程处理依次每个Intent,并在停止工作时停止自身。
    • 它拥有较高的优先级,不易被系统杀死(继承自Service的缘故),因此比较适合执行一些高优先级的异步任务;
    • 它内部通过HandlerThread和Handler实现异步操作
    • 创建IntentService时,只需实现onHandleIntent和构造方法,onHandleIntent为异步方法,可以执行耗时操作;

    5.1 使用步骤

    • 步骤1:定义 IntentService的子类,需复写onHandleIntent()方法
    public class myIntentService extends IntentService {
    
      /** 
        * 在构造函数中传入线程名字
        **/  
        public myIntentService() {
            // 调用父类的构造函数
            // 参数 = 工作线程的名字
            super("myIntentService");
        }
    
       /** 
         * 复写onHandleIntent()方法
         * 根据 Intent实现 耗时任务 操作
         **/  
        @Override
        protected void onHandleIntent(Intent intent) {
    
            // 根据 Intent的不同,进行不同的事务处理
            String taskName = intent.getExtras().getString("taskName");
            switch (taskName) {
                case "task1":
                    Log.i("myIntentService", "do task1");
                    break;
                case "task2":
                    Log.i("myIntentService", "do task2");
                    break;
                default:
                    break;
            }
        }
    
        @Override
        public void onCreate() {
            Log.i("myIntentService", "onCreate");
            super.onCreate();
        }
       /** 
         * 复写onStartCommand()方法
         * 默认实现 = 将请求的Intent添加到工作队列里
         **/  
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Log.i("myIntentService", "onStartCommand");
            return super.onStartCommand(intent, flags, startId);
        }
    
        @Override
        public void onDestroy() {
            Log.i("myIntentService", "onDestroy");
            super.onDestroy();
        }
    }
    
    
    • 步骤2:在Manifest.xml中注册服务
    <service android:name=".myIntentService">
        <intent-filter >
            <action android:name="cn.scu.finch"/>
        </intent-filter>
    </service>
    
    • 步骤3:在Activity中开启Service服务
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
                // 同一服务只会开启1个工作线程
                // 在onHandleIntent()函数里,依次处理传入的Intent请求
                // 将请求通过Bundle对象传入到Intent,再传入到服务里
    
                // 请求1
                Intent i = new Intent("cn.scu.finch");
                Bundle bundle = new Bundle();
                bundle.putString("taskName", "task1");
                i.putExtras(bundle);
                startService(i);
    
                // 请求2
                Intent i2 = new Intent("cn.scu.finch");
                Bundle bundle2 = new Bundle();
                bundle2.putString("taskName", "task2");
                i2.putExtras(bundle2);
                startService(i2);
    
                startService(i);  //多次启动
            }
        }
    

    6 ThreadPool(线程池)

    1、重用存在的线程,减少对象创建、消亡的开销,性能佳
    2、可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,

    6.1 ThreadPoolExecutor构造参数解释

    Executor threadPool = new ThreadPoolExecutor(
                                                CORE_POOL_SIZE,
                                                MAXIMUM_POOL_SIZE,
                                                KEEP_ALIVE,
                                                TimeUnit.SECONDS,
                                                sPoolWorkQueue,
                                                sThreadFactory
                                                );
    
    • corePoolSize: 线程池的核心线程数,默认情况下,核心线程数会一直在线程池中存活,即使它们处理闲置状态。如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会执行超时策略,这个时间间隔由keepAliveTime所指定,当等待时间超过keepAliveTime所指定的时长后,核心线程就会被终止。
    • maximumPoolSize: 线程池所能容纳的最大线程数量,当活动线程数到达这个数值后,后续的新任务将会被阻塞。如果这个无限大永远不会阻塞,除非开辟的线程超过了CPU承受的最大范围。
    • keepAliveTime: 非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长。
    • unit: keepAliveTime这个参数的单位,有纳秒、微秒、毫秒、秒、分、时、天等。常用的有TimeUnit.MILLISECONDS(毫秒),TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分钟)等。
    • workQueue: 线程池中的任务队列,该队列主要用来存储已经被提交但是尚未执行的任务。存储在这里的任务是由ThreadPoolExecutor的execute方法提交来的。
    • threadFactory: 为线程池提供创建新线程的功能,这个我们一般使用默认即可。
    • handler: 拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。

    6.2 实例

    // 1. 创建线程池
    // 创建时,通过配置线程池的参数,从而实现自己所需的线程池
     Executor threadPool = new ThreadPoolExecutor(...);
      // 注:在Java中,已内置4种常见线程池,下面会详细说明
      
    // 2. 向线程池提交任务:execute()
    // 说明:传入 Runnable对象
       threadPool.execute(new Runnable() {
            @Override
            public void run() {
                ... // 线程执行任务
            }
        });
    
    // 3. 关闭线程池shutdown() 
    threadPool.shutdown();
    
    // 关闭线程的原理
    // a. 遍历线程池中的所有工作线程
    // b. 逐个调用线程的interrupt()中断线程(注:无法响应中断的任务可能永远无法终止)
    
    // 也可调用shutdownNow()关闭线程:threadPool.shutdownNow()
    // 二者区别:
    // shutdown:设置 线程池的状态 为 SHUTDOWN,然后中断所有没有正在执行任务的线程
    // shutdownNow:设置 线程池的状态 为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
    // 使用建议:一般调用shutdown()关闭线程池;若任务不一定要执行完,则调用shutdownNow()
    

    6.2 四种线程池

    6.2.1 定长线程池(FixedThreadPool)

    特点:只有核心线程而且不会被回收、线程数量固定、任务队列无大小限制(超出的线程任务会在队列中等待阻塞)
    应用场景:控制线程最大并发数

    // 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    
    // 2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task =new Runnable(){
    	public void run(){
    		System.out.println("执行任务啦");
    	}
    };
            
    // 3. 向线程池提交任务:execute()
    fixedThreadPool.execute(task);
            
    // 4. 关闭线程池
    fixedThreadPool.shutdown();
    

    6.2.2 定时线程池(ScheduledThreadPool )

    核心线程数量固定、非核心线程数量无限制,不会阻塞(闲置时马上回收)
    应用场景:执行定时以及周期性任务

    为什么不用Timer做定时和延时任务?

    • Timer的特点
      1.Timer是单线程模式;
      2.如果在执行任务期间某个TimerTask耗时较久,那么就会影响其它任务的调度;
      3.Timer的任务调度是基于绝对时间的,对系统时间敏感;
      4.Timer不会捕获执行TimerTask时所抛出的异常,由于Timer是单线程,所以一旦出现异常,则线程就会终止,其他任务也得不到执行。
    • ScheduledThreadPoolExecutor的特点
      1.ScheduledThreadPoolExecutor是多线程
      2.多线程,单个线程耗时操作不会影响影响其它任务的调度
      3.基于相对时间,对系统时间不敏感
      4.多线程,单个任务的执行异常不会影响其他线程
    // 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
    
    // 2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task =new Runnable(){
       public void run(){
              System.out.println("执行任务啦");
          }
    };
    // 3. 向线程池提交任务:schedule()
    scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
    scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务
    
    // 4. 关闭线程池
    scheduledThreadPool.shutdown();
    

    6.2.3 可缓存线程池(CachedThreadPool)

    特点:只有非核心线程、线程数量不固定(可无限大)、灵活回收空闲线程(具备超时机制,全部回收时几乎不占系统资源)、新建线程(无线程可用时),不会阻塞
    应用场景:执行大量、耗时少的线程任务

    // 1. 创建可缓存线程池对象
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    
    // 2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task =new Runnable(){
    public void run(){
        System.out.println("执行任务啦");
            }
    };
    
    // 3. 向线程池提交任务:execute()
    cachedThreadPool.execute(task);
    
    // 4. 关闭线程池
    cachedThreadPool.shutdown();
    
    //当执行第二个任务时第一个任务已经完成
    //那么会复用执行第一个任务的线程,而不用每次新建线程。
    

    6.2.4 单线程化线程池(SingleThreadExecutor)

    特点:只有一个核心线程(超过一个任务会阻塞,保证所有任务按照指定顺序在一个线程中执行,不需要处理线程同步的问题)
    应用场景:不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作,文件操作等

    // 1. 创建单线程化线程池
    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    
    // 2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task =new Runnable(){
      public void run(){
            System.out.println("执行任务啦");
                }
     };
    
    // 3. 向线程池提交任务:execute()
    singleThreadExecutor.execute(task);
    
    // 4. 关闭线程池
    singleThreadExecutor.shutdown();
    

    6.3 线程池的注意点

    虽然线程池是构建多线程应用程序的强大机制,但使用它并不是没有风险的。用线程池构建的应用程序容易遭受任何其它多线程应用程序容易遭受的所有并发风险,诸如同步错误和死锁,它还容易遭受特定于线程池的少数其它风险,诸如与池有关的死锁、资源不足和线程泄漏。
    1.死锁
    任何多线程应用程序都有死锁风险。当一组进程或线程中的每一个都在等待一个只有该组中另一个进程才能引起的事件时,我们就说这组进程或线程 死锁了。死锁的最简单情形是:线程 A 持有对象 X 的独占锁,并且在等待对象 Y 的锁,而线程 B 持有对象 Y 的独占锁,却在等待对象 X 的锁。除非有某种方法来打破对锁的等待(Java 锁定不支持这种方法),否则死锁的线程将永远等下去。
    虽然任何多线程程序中都有死锁的风险,但线程池却引入了另一种死锁可能,在那种情况下,所有池线程都在执行已阻塞的等待队列中另一任务的执行结果的任务,但这一任务却因为没有未被占用的线程而不能运行。当线程池被用来实现涉及许多交互对象的模拟,被模拟的对象可以相互发送查询,这些查询接下来作为排队的任务执行,查询对象又同步等待着响应时,会发生这种情况。
    2.资源不足
    线程池的一个优点在于:相对于其它替代调度机制而言,它们通常执行得很好。但只有恰当地调整了线程池大小时才是这样的。线程消耗包括内存和其它系统资源在内的大量资源。除了 Thread 对象所需的内存之外,每个线程都需要两个可能很大的执行调用堆栈。除此以外,JVM 可能会为每个 Java 线程创建一个本机线程,这些本机线程将消耗额外的系统资源。最后,虽然线程之间切换的调度开销很小,但如果有很多线程,环境切换也可能严重地影响程序的性能。
    如果线程池太大,那么被那些线程消耗的资源可能严重地影响系统性能。在线程之间进行切换将会浪费时间,而且使用超出比您实际需要的线程可能会引起资源匮乏问题,因为池线程正在消耗一些资源,而这些资源可能会被其它任务更有效地利用。除了线程自身所使用的资源以外,服务请求时所做的工作可能需要其它资源,例如 JDBC 连接、套接字或文件。这些也都是有限资源,有太多的并发请求也可能引起失效,例如不能分配 JDBC 连接。
    3.并发错误
    线程池和其它排队机制依靠使用 wait() 和 notify() 方法,这两个方法都难于使用。如果编码不正确,那么可能丢失通知,导致线程保持空闲状态,尽管队列中有工作要处理。使用这些方法时,必须格外小心;即便是专家也可能在它们上面出错。而最好使用现有的、已经知道能工作的实现,例如 util.concurrent 包。
    4.线程泄漏
    各种类型的线程池中一个严重的风险是线程泄漏,当从池中除去一个线程以执行一项任务,而在任务完成后该线程却没有返回池时,会发生这种情况。发生线程泄漏的一种情形出现在任务抛出一个 RuntimeException 或一个 Error 时。如果池类没有捕捉到它们,那么线程只会退出而线程池的大小将会永久减少一个。当这种情况发生的次数足够多时,线程池最终就为空,而且系统将停止,因为没有可用的线程来处理任务。
    有些任务可能会永远等待某些资源或来自用户的输入,而这些资源又不能保证变得可用,用户可能也已经回家了,诸如此类的任务会永久停止,而这些停止的任务也会引起和线程泄漏同样的问题。如果某个线程被这样一个任务永久地消耗着,那么它实际上就被从池除去了。对于这样的任务,应该要么只给予它们自己的线程,要么只让它们等待有限的时间。
    5.请求过载
    仅仅是请求就压垮了服务器,这种情况是可能的。在这种情形下,我们可能不想将每个到来的请求都排队到我们的工作队列,因为排在队列中等待执行的任务可能会消耗太多的系统资源并引起资源缺乏。在这种情形下决定如何做取决于您自己;在某些情况下,您可以简单地抛弃请求,依靠更高级别的协议稍后重试请求,您也可以用一个指出服务器暂时很忙的响应来拒绝请求。

    7 结尾

    有句话说的不错,好记忆不如烂笔头。Android学习过程中,最好的方式就是记录,记多了看多了不知不觉就成专家了。而写博客就是帮自己梳理知识点的最好的方式,你可以尝试去多读几篇类似的文章,然后自己梳理记下来,写出自己的博客,你会受益匪浅而且极易深刻,不信你试试!!共勉吧~

    展开全文
  • 为什么要多线程开发?  因为默认的我们的任务是在主线程(UI线程)这一单线程模式下执行的,如果主线程执行过于繁重,耗时的操作时,将会阻塞线程, 一旦线程被阻塞,将无法分派任何事件,包括绘图事件。 从用户...
  • iOS多线程开发——NSThread浅析

    千次阅读 2016-05-09 01:08:42
    在IOS开发中,多线程的实现方式主要有三种,NSThread、NSOperation和GCD,我前面博客中对NSOperation和GCD有了较为详细的实现,可以参考《iOS多线程开发——NSOperation/NSOperationQueue浅析》《iOS多线程开发——...
  • 深度讲解Java多线程开发—电子表项目实现

    千次阅读 多人点赞 2020-07-23 16:09:08
    今天和大家分享一个使用Java多线程开发的电子表项目,可以实现电子表中时间的实时显示,修改以及秒表的功能。 Java电子表设计的设计顺序为从前端界面到后端类及线程的设计,之后将前后端相结合而成。以下是电子表的...
  • C/C++ 用 pthread 进行多线程开发

    千次阅读 2019-02-21 14:01:14
    作为一个程序员,不管你用的开发...本文分享如何用 C 进行多线程开发。 核心在于 pthread 这个库。 调用 pthread_create()函数就可以创建一个线程。 它的函数原型如下: #include &lt;pthread.h&gt; exte...
  • java 多线程开发注意事项

    千次阅读 2018-08-22 21:55:28
    多线程开发的三大特性 有序性 可见性 对修改后的数据可以看到拥有可见性 原子性 代码在执行的时候必须一次执行完,一次成功或者是一次失败,一次线程对一段代码有掌控,就像事务里面的原子一样 线程,本地内存你...
  • .NET基础之多线程开发基础

    千次阅读 2018-11-05 10:57:10
    多线程开发基础  Index :  (1)类型语法、内存管理和垃圾回收基础  (2)面向对象的实现和异常的处理基础  (3)字符串、集合与流  (4)委托、事件、反射与特性  (5)多线程开发基础  (6)ADO...
  • 玩转iOS开发 - 多线程开发

    千次阅读 2015-06-08 17:58:13
    前言本文主要介绍iOS多线程开发中使用的主要技术:NSOperation, GCD, NSThread, pthread。 内容按照开发中的优先推荐使用的顺序进行介绍,涉及多线程底层知识比较多的NSThread, pthread 放到了后面,建议小伙伴们先...
  • iOS多线程简介 - Swift版本 3.多线程开发 -- Run Loop
  • 多线程开发优缺点及应用场景分析

    千次阅读 2019-03-10 11:07:02
    何时使用多线程技术,何时避免用它,是我们需要掌握的重要课题。多线程技术是一把双刃剑,在使用时需要充分考虑它的优缺点。 多线程应用场景: 是否需要创建多个线程取决...多线程开发的优势: 多线程处理可以同时运...
  • IOS多线程开发其实很简单

    千次阅读 2019-02-24 08:45:11
    IOS多线程开发其实很简单https://blog.csdn.net/shenjie12345678/article/details/44152605概览 大家都知道,在开发过程中应该尽可能减少用户等待时间,让程序尽可能快的完成运算。可是无论是哪种语言开发的程序最终...
  • 我在前面的博客中《GCD实践——串行队列/并发队列与IOS多线程详解》中对iOS中的同步异步、串行并行做了较为详细的讲解。在之后的几篇GCD实践的博客中对GCD的使用也有较为详细的实现。但是我们要注意的是,那里用到的...
  • java多线程开发 如何正确关闭线程

    万次阅读 2016-04-19 23:31:33
    在java高级开发中,经常会碰到多线程,关于线程的关闭,可能会用stop() 方法,但是stop是线程不安全的,一般采用interrupt,判断线程是否中止采用isInterrupted, 如果线程中有Thread.sleep方法,当设置中断后,...
  • java web 项目 ssm框架开发 什么时候要使用多线程开发啊 大神列举下使用情景呗 为什么我一直没用多线程开发过呢~~~
  • 前段时间写了一个iOS端的数据统计SDK,数据统计有些...因为苹果提供了另外几种多线程开发的解决方案,而这些解决方案面向的不再是线程,而是面向的是任务,下面就来以iOS和Android为例简单谈谈的线程和任务的相关概念。
  • iOS多线程开发指南

    2013-08-20 13:36:01
    iOS多线程开发指南 http://www.dreamingwish.com/dream-category/toturial/ios-mulit-thread-program-guide
  • (一)Java 多线程开发 (二)Android 多线程开发 2.1)基础使用 1、继承Thread类 (1)简介 Thread类是Java中实现多线程的具体类,封装了所需线程操作。在Android开发中用于实现多线程。 注:线程对象&运行...
  • 多线程开发中需要注意的问题

    千次阅读 2017-03-21 23:04:34
    多线程开发在 Linux 平台上已经有成熟的 Pthread 库支持。其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。其中,线程操作又分线程的创建,退出,等待 3 种。互斥锁则包括 4 种操作,分别是创建,...
  • VC++多线程开发

    千次阅读 2017-12-07 16:19:58
    对于像通信程序这样既耗费时间,有需要保持对用户输入响应的应用程序来说,应用多线程编程技术是较好地选择。 由于普通的类成员函数不能作为线程入口函数,所以实例定义一个静态类成员函数作为线程函数,为
  • 多线程编程之二——MFC中的多线程开发 <br />作者:韩耀旭 原文地址:http://www.vckbase.com/document/viewdoc/?id=1706<br /><br />五、MFC对多线程编程的支持  MFC中有两类线程,分别称之...
  • 使用NSOperationQueue简化多线程开发

    千次阅读 2012-03-26 17:17:03
    多线程开发是一件需要特别精心的事情,即使是对有多年开发经验的工程师来说。 为了能让初级开发工程师也能使用多线程,同时还要简化复杂性。各种编程工具提供了各自的办法。对于iOS来说,建议在尽可能的情况下...
  • 收集的VC线程和多线程开发资料

    千次阅读 2009-08-14 14:34:00
    收集的VC线程和多线程开发资料 收藏 VC中利用多线程技术实现线程之间的通信 刘涛|天极网|2006-02-06 10:23 本实例针对多线程技术在应用中经常遇到的问题,如线程间的通信、同步等,分别进行探讨 利用API在Windows下...
  • ios多线程开发的常用四种方式 1. pthread 2. NSThread 3. NSOperation\NSOperationQueue 4. GCD 一 、pthread C语言通用的多线程API,跨平台,程序员手动管理线程生命周期,使用难度大 代码实现 //创建线程 NSLog...
  • iOS多线程开发

    万次阅读 2016-03-16 19:53:15
    概览 大家都知道,在开发过程中应该尽...改变这种状况可以从两个角度出发:对于单核处理器,可以将个步骤放到不同的线程,这样一来用户完成UI操作后其他后续任务在其他线程中,当CPU空闲时会继续执行,而此时对于
  • ios多线程开发——nsoperation详解

    千次阅读 2012-08-20 15:00:20
    多线程开发是一件需要特别精心的事情,即使是对有多年开发经验的工程师来说。 为了能让初级开发工程师也能使用多线程,同时还要简化复杂性。各种编程工具提供了各自的办法。对于iOS来说,建议在尽可能的情况下...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 42,831
精华内容 17,132
关键字:

多线程开发