• 多线程——从 java 基础到 Android 开发


    java 基础

    <1> 多线程三种实现方式

    1. 继承 Thread 类
    2. 实现 Runnable 接口的方式
    3. 使用线程池,ExcutorService 、Callable、Future 实现多线程

    前两种比较熟悉,这里说一下第三种方式

    ExcutorService、Callable、Future对象实际上都是属于Excutor 框架中的功能类,执行 Callable 任务后,可以获取一个Future对象,在该对象上调用 get()可获得Callable任务返回的Object ,再结合线程池接口 ExcutorService 即可实现由返回结果的多线程。

    Runnable()与Callable()区别

    如果是一个多线程协作程序,比如菲波拉切数列,1,1,2,3,5,8…使用多线程来计算。 
    但后者需要前者的结果,就需要用callable接口了。 
    callable用法和runnable一样,只不过调用的是call方法,该方法有一个泛型返回值类型,你可以任意指定。

    runnable接口实现的没有返回值的并发编程

    <2> 多线程通过线程池创建多个线程

    Excutor 提供五种线程池

    (优势:减少多个线程创建和销毁时的耗时,线程在面向对象编程中也相当于一个对象的创建和销毁,提供运行效率)

    • newCachedThreadPool--------可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 
    • newFixedThreadPool ---------一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 
    • newScheduledThreadPool --------一个定长线程池,支持定时及周期性任务执行。 
    • newSingleThreadExecutor ---------一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(先进先出(FIFO), 后进先出(LIFO), 优先级)执行
    • ThreadPoolExecutor----------------一个基本的线程池,以上四种的内部实现都是基于该最基础的线程池实现的。

    在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险

    java 使用方式详细了解及说明参考:https://www.cnblogs.com/Steven0805/p/6393443.html

    Android中使用方式详细了解及说明:https://www.jianshu.com/p/7b2da1d94b42

    <3> Androd中多线程的创建

    创建最基本的线程池 ThreadPoolexcutor 对象,其中构造参数里面包含7个参数,

    • corePoolsize :线程池维护线程的核心线程数
    • maxmumPoolSize: 线程池维护线程的最大数量
    • keepAliveTime : 线程池维护线程所允许的空闲时间
    • unit:线程池维护线程所允许的空闲时间单位
    • workQueue : 线程池所使用的缓冲队列
    • threadFactory :新建线程工厂
    • handler :线程对拒绝任务的处理策略

    <4> java基础中多线程通信

    ①同步

    这里讲的同步是指多个线程通过synchronized关键字这种方式来实现线程间的通信,是加锁的方式

    这种方式,本质上就是“共享内存”式的通信。多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行

    ②while轮询的方式

    在这种方式下,线程A不断地改变条件,线程ThreadB不停地通过while语句检测这个条件(list.size()==5)是否成立 ,从而实现了线程间的通信。但是这种方式会浪费CPU资源。之所以说它浪费资源,是因为JVM调度器将CPU交给线程B执行时,它没做啥“有用”的工作,只是在不断地测试 某个条件是否成立。就类似于现实生活中,某个人一直看着手机屏幕是否有电话来了,而不是: 在干别的事情,当有电话来时,响铃通知TA电话来了。关于线程的轮询的影响,可参考:JAVA多线程之当一个线程在执行死循环时会影响另外一个线程吗?

    这种方式还存在另外一个问题:

    轮询的条件的可见性问题,关于内存可见性问题,可参考:JAVA多线程之volatile 与 synchronized 的比较中的第一点“一,volatile关键字的可见性

    线程都是先把变量读取到本地线程栈空间,然后再去再去修改的本地变量。因此,如果线程B每次都在取本地的 条件变量,那么尽管另外一个线程已经改变了轮询的条件,它也察觉不到,这样也会造成死循环。

    ③wait/notify机制

    这里用到了Object类的 wait() 和 notify() 方法。

    当条件未满足时(list.size() !=5),线程A调用wait() 放弃CPU,并进入阻塞状态。---不像②while轮询那样占用CPU

    当条件满足时,线程B调用 notify()通知 线程A,所谓通知线程A,就是唤醒线程A,并让它进入可运行状态。

    这种方式的一个好处就是CPU的利用率提高了。

    但是也有一些缺点:比如,线程B先执行,一下子添加了5个元素并调用了notify()发送了通知,而此时线程A还执行;当线程A执行并调用wait()时,那它永远就不可能被唤醒了。因为,线程B已经发了通知了,以后不再发通知了。这说明:通知过早,会打乱程序的执行逻辑。

    ④管道通信就是使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信

    具体就不介绍了。分布式系统中说的两种通信机制:共享内存机制和消息通信机制。感觉前面的①中的synchronized关键字和②中的while轮询 “属于” 共享内存机制,由于是轮询的条件使用了volatile关键字修饰时,这就表示它们通过判断这个“共享的条件变量“是否改变了,来实现进程间的交流。

    而管道通信,更像消息传递机制,也就是说:通过管道,将一个线程中的消息发送给另一个

    以上参考:https://www.cnblogs.com/hapjin/p/5492619.html

    <5> Android 中多线程的通信

    • Handler 通信机制(事件驱动程序设计模型)

    在 Android 应用开发中他与其他线程协作,接收其他消息来更新主线程的内容。

    日常开发中Handler 通常用于异步请求,如网络请求等,同时Handler也用于定时操作,发送定时消息。

    有Message,Looper,MessageQueue,Handler 四个对象

    Message--------线程之间传递的消息

    MessageQueue----------存放Message和Runnable 的消息集合

    Looper-------不停的轮询消息队列,只要有消息就取出,没有消息就休眠。减少CPU资源的浪费

    Handler-------用来处理消息

    使用方法:

    首先在主线程中创建handler对象,然后在子线程中调用handler的sendMessage()方法,接着这个消息就好放在主线程的Messagequeue中,通过Looper对象取出MessageQueue中的消息,最后分发回Handler的handMessage()方法中

    1. Looper 用来统一管理messageQueue的,一个线程中只有一个looper对象来进行管理,主要的执行方法在looper.loop()方法中。
    2. UI主线程会给自己创建一个looper对象。子线程的looper需要手动的创建。
    3. 当handler在UI主线程上进行实例化的时候,自动持有主线程的looper。
    • AsyncTask 多线程异步处理机制

     Android 中专门用来处理异步请求的抽象类,主要是为耗时操作开辟新线程

    这个类是对Thread类的封装并加入了一些新的方法

    1. 三种泛型类型参数

    这些参数在不使用的时候可以直接指定为void,AsyncTask<Params,Progress,Results>可以指定为AsyncTask<void,void,void>

    2.四个回调函数

    onPreExcute() 准备运行

    doInBackground(Params) 正在后台运行,这里在后台线程执行耗时操作,将执行结果传递到onPostExcute(results)处理,也可以使用pulishProgress()发布进度值-------必须被复写

    onProgressUpdate(Progress)进度更新,由Ui线程在pulishProgress()之后调用,一般用于动态更新进度条

    onPostExcute(Results)完成后台任务

    • Acyivity.runOnUIthread(Runnable)
    • View.Post(Runnable)
    • View.PostDeayed(Runnble,long)

    Java 基础多线程相关 

    1.并行和并发的区别

    并发:是指两个任务请求运行,处理器接受一个任务,两个任务轮流进行

    并行:是指两个任务同时运行,甲任务进行,乙任务也会进行(需要多核处理器)

    JVM在启动时候启动了主线程和和垃圾回收器,所以是多线程

    2.多线程中的锁类型(目前了解有6种)

    2.1 Synchronized

    2.2 Lock

    2.3 ReentrantLock

    2.4 ReentrantReadWriteLock

    2.5 Condition

    2.6 ManyCondition

     #1 synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中

    #2 ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候。

    参考:https://blog.csdn.net/nalanmingdian/article/details/77800355

  • 相关阅读:
    C# WPF透明黑色样式窗口
    ExtJS速学
    编译away3d例程序记
    用VC加载Lua.lib,C++调用lua脚本函数
    mysql数据库备份
    Google TTS(文字转语音)api 2
    C Sharp Coding Standards
    微信公众平台功能大杂烩 ip/域名查询 车牌号归属地查询 手机归属地查询 公交查询 英汉互译
    google jsapi学习记录
    Google TTS(文字转语音)api
  • 原文地址:https://www.cnblogs.com/fanfusuzi/p/11144465.html
Copyright © 2020-2023  润新知