小木箱生长营并发编程系列教程(排期中~):

并发编程 根底篇(中) 三大剖析法剖析Handler

并发编程 根底篇(下) android线程池那些事

并发编程 进步篇(上) Java并发关键字那些事

并发编程 进步篇(下) Java锁安全性那些事

并发编程 高档篇(上) Java内存模型那些事

并发编程 高档篇(下) Java并发BATJ面试之谈

并发编程 实战篇 android下载器完结

Tips: 重视微信公众号小木箱生长营,回复 “并发编程” 可免费取得并发编程思想导图

一、序言

Hello,我是小木箱,欢迎来到小木箱生长营并发编程系列教程,今日将共享并发编程 根底篇 android线程那些事

android线程那些事首要分为三部分,榜首部分是5W2H剖析并发,第二部分是线程安全特性,第三部分是线程安全,终究一部分是结语。

其间,5W2H剖析并发首要是针对并发提出了6个高价值的问题。

其间,线程根底首要分为五部分,榜首部分是线程操作,第二部分是线程特点,第三部分是线程通讯,第四部分是线程运转状况,终究一部分是生产者和顾客模型。

其间,线程安全首要分为五部分,榜首部分是带着问题动身,第二部分是线程安全特性,第三部分是线程安全强度,第四部分是线程安全计划,终究一部分是UncaughtException兜底。

并发编程   基础篇(上)   android线程那些事

假如彻底把握小木箱生长营并发编程系列教程,那么任何人都能经过高并发相关的技能面试。

二、5W2H剖析并发

首要咱们聊聊并发根底的榜首部分内容5W2H剖析并发。咱们依据5W2H法则依照What、Why、Where、How、How much五个维度提出了六个高价值问题

  • 并发是什么?

  • android为什么要用并发?

  • android哪些地方用到并发?

  • android怎样完结多线程?

  • android合理运用并发有什么收益?

  • android盲目运用并发有什么风险?

下面,小木箱就带带着问题动身,带咱们正式进入并发根底内容学习。

并发编程   基础篇(上)   android线程那些事

2.1 并发是什么?

传送门: Java Concurrency

首要咱们聊一聊5W2H的What,Java Concurrency—并发。

并发编程   基础篇(上)   android线程那些事

并发、并行和串行

并发是指体系在同一时间段可一同处理多个使命,而同一时间只要一个使命处于运转状况,和并发有两个挨近的概念很简略被混杂,串行和并行,串行、并发和并行是相关于进程或多线程来说的。如下图是串行、并发和并行的履行时间图。

并发编程   基础篇(上)   android线程那些事

串行比较好了解,如上图所示串行是指线程A完结之后做线程B,以此类推,直到完结线程C,每个线程排队履行。下面咱们侧重看一下并发和并行。

并发编程   基础篇(上)   android线程那些事

并发是指一个或若干个 CPU 对多个进程或线程之间进行多路复用。简略说线程A先做Task,作业一段时间,线程B再做Task;

线程B执作业一段时间线程C再做Task,线程C作业一段时间,线程A从头履行Task。

以此类推,直到作业完结,看上去像是三个线程一同一同履行,但其实彻底能够交给一个线程履行。

关于并发来说,线程A先履行一段时间,然后线程B再履行一段时间,接着线程C再履行一段时间。每个线程都轮番得到 CPU 的履行时间,并发只需求一个 CPU 即能够完结, 线程运用率最优。

并行则是指多个进程或线程同一时间被履行,是真实意义上一同履行,并行有必要要有多个 CPU 支撑。

并行是A、B和C三个线程一同履行一个或多个Task,每线程担任一项Task,A、B和C三线程在同一时间齐头并进地完结这些事情。

并行比串行和并发时间开支要小,可是由于线程A、线程B和线程C是一同履行的,需求三个 CPU 才干完结,必定程度影响机器功用。

用一句话总结便是:

串行是一个时间段内多个使命履行时,一个使命履行完才干履行另一个。

并行是指一个时间段内每个线程分配给独立的核心,线程一同运转。

而并发指的是一个时间段多个线程在单个核心运转,同一时间只能一个线程运转,体系不断切换线程,看起来像一同运转,实践上是线程不断切换。

并发编程   基础篇(上)   android线程那些事

同步和异步

除了串行、并行和并发以外,实践开发过程中,同学们经常将同步、异步混杂,下面简略比照一下同步和异步的差异。

如下图是同步和异步履行时间图,同步和异步与并发、并行、串行差异点在于同步和异步一般相对进程或多线程,而同步和异步一般是相关于线程而言的。

并发编程   基础篇(上)   android线程那些事

同步是指两个事物彼此依靠,而且一个事物有必要以依靠于另一事物的履行成果。比方在事物 A->B 事件模型中,你需求先完结事物 A 才干履行事物 B。

也便是说,同步调用在被调用者未处理完恳求之前,调用不回来,调用者会一向等候成果的回来。

异步是指两个事物彻底独立,一个事物的履行不需求等候别的一个事物的履行。也便是说,异步调用能够回来成果不需求等候成果回来,当成果回来的时分经过回调函数或许其他办法带着调用成果再做相关事情。

堵塞和非堵塞

除了同步、异步以外,实践开发过程中,同学们经常将堵塞和非堵塞混杂,下面简略比照一下堵塞和非堵塞的差异。如下图是堵塞和非堵塞履行图,

并发编程   基础篇(上)   android线程那些事

  • 所谓堵塞是宣布一个恳求不能马上回来呼应,要等一切的逻辑全处理完才干回来呼应。简略来说便是等候。

  • 所谓非堵塞相反,宣布一个恳求马上回来应答,不必等处理完一切逻辑。堵塞与非堵塞指的是单个线程内遇到同步等候时,是否在原地不做任何操作。

那么同步堵塞、同步非堵塞、异步堵塞异步非堵塞又有什么差异呢?

并发编程   基础篇(上)   android线程那些事

同步堵塞

同步堵塞是指在需求某资源时马上主张恳求,并暂停本线程之后的程序,直至取得所需的资源。参阅代码如下:

并发编程   基础篇(上)   android线程那些事

输出成果:

小木箱生长营

同步非堵塞

同步非堵塞是指在需求某资源时马上主张恳求,且能够马上得到答复,然后持续履行之后的程序。但假如得到的不是完好的资源,之后将周期性地的恳求。参阅代码如下:

并发编程   基础篇(上)   android线程那些事

输出成果:

小木箱正在学习并发编程

小木箱正在学习规划办法

异步堵塞

异步堵塞是指在需求某资源时不马上主张恳求,而组织一个今后的时间再主张恳求。当到了那时宣布恳求时,将暂停本线程之后的程序,直至取得所需的资源。参阅代码如下:

并发编程   基础篇(上)   android线程那些事

输出成果:

小木箱生长营说: 异步使命开端 …

小木箱生长营说: 异步使命完毕 …

小木箱生长营说: 一切异步使命履行完毕,持续履行后续使命

异步非堵塞

异步非堵塞是指在需求某资源时不马上主张恳求,而组织一个今后的时间再主张恳求。当到了那时宣布恳求时,能够马上得到答复,然后持续履行之后的程序。

但假如得到的不是完好的资源,之后将周期性地的恳求。参阅代码如下:

并发编程   基础篇(上)   android线程那些事

输出成果:

This is an asynchronous non-blocking code.

至此,同步、异步、堵塞、非堵塞以及他们的组合运用,小木箱现已解说完毕了,下面小木箱侧重的聊一下android为什么要运用并发?

2.2 android为什么要用并发?

由于CPU、内存、I/O 设备的速度是有极大差异的,为了合理运用 CPU 的高功用,平衡这三者的速度差异,充分运用体系资源,让多个线程在一同运转的过程中竞赛资源,充分运用android操作体系处理能力,因而咱们需求运用并发。

总结一下便是: 进步程序功用、改善用户体会和节约设备资源

2.3 android哪些地方用到并发?

android并发场景应用十分广泛。

假如你需求编写发动器进行发动使命办理,那么你需求了解并发。

假如你需求对大文件进行多线程下载,那么你需求了解并发。

假如你用 AsyncTask进行调度使命,那么你需求了解并发。

假如你看Handler底层源码ThreadLocal完结,那么你需求了解并发。

假如你想编写一个合规的线程池,那么你需求了解并发。

……

所以说并发对android开发来说无影随形,已然并发对android程序员来说这么重要,那么咱们该怎样高功率运用并发呢?

2.4 android怎样完结多线程?

android完结多线程的办法大约分为四种,榜首种办法是HandlerThread,第二种办法是AsyncTask,第三种办法是IntentService,第四种办法是ExecutorService

2.4.1 HandlerThread

HandlerThread界说

首要,咱们聊聊榜首种办法HandlerThread,android的HandlerThread是一种特殊的Thread,HandlerThread供给了一个Looper,能够用来处理音讯和处理程序。

HandlerThread优势在于能够将耗时的使命分发到后台线程,然后防止UI线程的堵塞和进步应用程序的功用和流畅性。

HandlerThread底层原理

android的HandlerThread底层原理:

  1. 创立一个承继自Thread类的HandlerThread,并重写run办法;
  2. 在run办法中创立一个Looper目标,并调用Looper.prepare办法;
  3. 在Looper.prepare办法中创立一个MessageQueue,并将MessageQueue赋值给Looper目标;
  4. 调用Looper.loop办法,HandlerThread会一向从MessageQueue中取出Message,并交给Handler处理;
  5. 假如HandlerThread调用了quit办法,那么Looper.loop办法就会中止,然后完毕HandlerThread的运转。

并发编程   基础篇(上)   android线程那些事

由于,HandlerThread能够运用Handler来发送和处理音讯而且HandlerThread能够创立多个Handler,每个Handler能够拥有自己的线程。所以,HandlerThread能够完结多线程。

由于,HandlerThread还供给了一种机制来办理线程,所以,线程能够在合适的时分被暂停或许康复。

HandlerThread完结办法

下面,小木箱运用HandlerThread带咱们完结一下android多线程:

并发编程   基础篇(上)   android线程那些事

2.4.2 AsyncTask

然后,咱们聊聊第二种办法AsyncTask,android的AsyncTask通常用于履行一些时间短的耗时操作,比方从网络获取数据,在UI线程中履行简略的核算,或许更新UI等。

AsyncTask界说

android的AsyncTask是android供给的用于完结多线程的类,AsyncTask能够完结多线程协作,异步履行后台使命,而且能够经过主线程更新UI。

AsyncTask底层原理

android的AsyncTask完结多线程底层原理是:AsyncTask创立一个新的作业线程,在作业线程中调用doInBackground办法履行后台使命,一同在主线程中调用onProgressUpdate办法更新UI界面。

当doInBackground履行完毕后,会回调onPostExecute办法,在onPostExecute办法中能够更新UI界面。

并发编程   基础篇(上)   android线程那些事

AsyncTask运用办法

下面,小木箱运用android的AsyncTask带咱们完结一下android多线程:

并发编程   基础篇(上)   android线程那些事

2.4.3 IntentService

IntentService界说

接着,咱们聊聊第三种办法IntentServiceIntentService是android供给的一种用于履行异步使命的服务,IntentService是一种特殊的Service,能够在独自的作业线程中处理耗时使命,并在完结后主动中止。

并发编程   基础篇(上)   android线程那些事

IntentService能够处理多个异步使命,每个使命都会在一个独自的线程中处理,因而不会堵塞UI线程,而且能够在使命完结后主动中止。

IntentService底层原理

IntentService底层原理是IntentService运用HandlerThread类来处理使命,HandlerThread内部有一个Looper,Looper会循环从音讯行列中取出音讯,每取出一条音讯就会履行一次handleMessage办法。

在IntentService中,handleMessage办法会调用onHandleIntent办法,onHandleIntent办法便是咱们要完结的使命,当使命履行完毕后,IntentService会主动中止。

IntentService运用办法

下面,小木箱带咱们看一下IntentService多线程代码完结:

  • 在androidManifest.xml文件中声明一个IntentService:

并发编程   基础篇(上)   android线程那些事

  • 创立IntentService

并发编程   基础篇(上)   android线程那些事

  • 调用IntentService

并发编程   基础篇(上)   android线程那些事

2.4.4 ExecutorService

ExecutorService界说

终究,咱们聊聊第四种办法ExecutorService,ExecutorService是一个接口,ExecutorService供给了一种机制,能够将使命提交给Executor,然后由Executor在后台履行使命,然后供给并发性。

并发编程   基础篇(上)   android线程那些事

ExecutorService还供给了一种机制,能够办理运转中的使命和完结的使命,以及查看使命的履行状况。

ExecutorService底层原理

ExecutorService底层原理是运用了一个线程池来办理多个线程,而且能够操控线程的数量,供给了一系列的API来提交使命,而且能够操控使命的履行,比方能够提交一个使命,能够提交一个使命序列,能够提交一个能够操控使命履行时间的使命,也能够提交一个守时使命,完结了对使命的办理和操控。

并发编程   基础篇(上)   android线程那些事

ExecutorService的作业原理是,当调用其间的submit办法时,会将使命提交到线程池中,线程池会担任将使命分配给线程,然后线程池会操控线程的数量,假如线程数量超出了约束,则会把使命放到行列中,等候闲暇的线程来履行使命, 假如没有闲暇的线程,则会新建一个线程来履行使命,当线程完结使命时,会从行列中取出下一个使命来履行,直到一切的使命都完结,ExecutorService才会完毕。

并发编程   基础篇(上)   android线程那些事

ExecutorService运用办法

下面,小木箱带咱们看一下ExecutorService多线程代码完结:

并发编程   基础篇(上)   android线程那些事

终究,小木箱对HandlerThread、AsyncTask、IntentService和ExecutorService运用场景和优缺陷做一下简略的概括总结:

类型 运用场景 优点 缺陷
HandlerThread 需求在后台运转一个持续的线程,能够在线程中处理音讯行列中的音讯 能够完结音讯的传递和处理,能够界说不同的音讯处理程序 简略呈现内存走漏,资源耗费大
AsyncTask 异步处理耗时操作 操作简略,完结方便,能够很方便的在主线程和子线程之间传递音讯 简略呈现内存走漏,资源耗费大,不能处理杂乱的使命
IntentService 后台处理长时间使命,处理完毕后主动中止 能够处理杂乱的使命,能够完结音讯的传递和处理 资源耗费大,不能处理频频的使命
ExecutorService 异步处理耗时操作 操作简略,能够完结音讯的传递和处理,能够处理杂乱的使命 资源耗费大,不能处理频频的使命

2.5 android合理运用并发有什么收益?

那么,android合理运用并发有什么收益?

当咱们在运用多线程处理文件下载过程中,不推翻原有线程池运用办法的根底之上,从下降线程池参数修正的成本以及多维度监控这两个方面能够下降毛病产生的概率。

总结一下便是: 进步应用程序功用、改善用户体会、进步应用程序的可维护性和改善应用程序的可扩展性

2.6 android盲目运用并发有什么风险?

由于线程池的参数并不好装备。一方面线程池的运转机制不是很好了解,装备合理需求强依靠开发人员的个人经历和常识;

另一方面,线程池履行的状况和使命类型相关性较大,IO密布型和CPU密布型的使命运转起来的状况差异十分大,这导致业界并没有一些老练的经历战略协助开发人员参阅。

假如盲目运用并发会导致如下三个问题:

  1. 频频恳求/毁掉资源和调度资源,将带来额定的耗费,或许会十分巨大。

  2. 对资源无限恳求短少按捺手段,易引发体系资源耗尽的风险。

  3. 体系无法合理办理内部的资源散布,会下降体系的稳定性。

总结一下便是: 内存走漏、线程安全和数据纷歧致。

三、线程根底

3.1 线程操作

3.1.1 线程运用办法

创立线程有四种办法,榜首种是直接 new Thread 重写Thread的run办法。

第二种是完结Runnable接口,将Runnable接口传给Thread。无论是承继Thread仍是Runnable接口都无法获取使命履行成果。

假如需求获取使命履行成果,就需求运用第三种办法运用Callable和Future接口

由于前史规划的原因,Thread只承受Runnable而不承受Callable,而FutureTask是Runnable和Callable的包装,FutureTask自身是承继Runnable的,所以FutureTask能够直接传给Thread,FutureTask调用get办法就能够获取到线程履行成果。

并发编程   基础篇(上)   android线程那些事

假如FutureTask使命没有找履行完,那么FutureTask无参get会一向堵塞,FutureTask能够运用超时get,超越必守时间就回来null。

第四种办法是线程池办法,本文简略入门一下,后文会侧重解说。

承继Thread类

并发编程   基础篇(上)   android线程那些事

输出成果:

小木箱说,当时运转的线程名为: CrazyCodingBoyThreadTest1

小木箱说,当时运转的线程名为: CrazyCodingBoyThreadTest2

完结Runnable接口

并发编程   基础篇(上)   android线程那些事

输出成果:

小木箱说,当时运转的线程名为: CrazyCodingBoyRunnable1

小木箱说,当时运转的线程名为: CrazyCodingBoyRunnable2

运用Callable和Future接口

并发编程   基础篇(上)   android线程那些事

输出成果:

小木箱说: 主线程在履行使命

小木箱说: Callable子线程开端核算

小木箱说: task运转成果4950

小木箱说: 一切使命履行完毕

运用Executors类

并发编程   基础篇(上)   android线程那些事

输出成果:

index:2

index:0

index:1

运用线程池

Executor办理多个异步使命的履行,是无需显式的办理线程的生命周期的。

并发编程   基础篇(上)   android线程那些事

输出成果:

pool-1-thread-4 Start. Command = 3

pool-1-thread-2 Start. Command = 1

pool-1-thread-3 Start. Command = 2

pool-1-thread-5 Start. Command = 4

pool-1-thread-1 Start. Command = 0

3.1.2 发动线程

发动线程的办法有两种,榜首种是start,第二种是run,其间start才是发动线程的办法,run是一个普通办法。

并发编程   基础篇(上)   android线程那些事

3.1.2.1 start

start的线程处于安排妥当状况,当得到CPU的时间片后就会履行其间的run办法,详细能够看一下图示例代码,由于当履行到此处,创立了一个新的线程t并处于安排妥当状况,代码持续履行,打印出”ping”。此时,履行完毕。线程t得到CPU的时间片,开端履行,调用pong办法打印出”pong”。

并发编程   基础篇(上)   android线程那些事

3.1.2.2 run

经过run办法发动线程其实便是调用一个类中的办法。无需等候run办法中的代码履行完毕,就能够接着履行下面的代码。并没有创立一个线程,程序中仍旧只要一个主线程,有必要比及run办法里边的代码履行完毕,才会持续履行下面的代码,这样就没有抵达写线程的意图。详细能够参阅如下示例代码,由于t.run实践上便是等候履行new Thread里边的run办法调用pong完毕后,再持续打印”ping”。

并发编程   基础篇(上)   android线程那些事

考虑1: 一个线程两次调用start办法会呈现什么状况?为什么?

考虑2: 已然 start 办法会调用 run 办法,为什么咱们挑选调用 start 办法,而不是直接调用 run 办法呢?

3.1.3 线程中止

说完发动线程,咱们说一下线程中止。

什么是线程中止?当需求做到一半产品说要下线,就相当于线程中止。

什么是线程中止不了?当需求做到一半产品说要下线,可是你觉的产品SB,要持续做完,就相当于线程中止不了。

正常状况下线程履行完结主动完毕。

假如运转时反常,会调用一个线程的interrupt办法来中止该线程。

假如该线程处于堵塞、限期等候或许无限期等候状况,那么就会抛出 InterruptedException,然后提早完毕该线程,中止会提早完毕。

下面小木箱说一下线程人为中止的两种办法: stop和interrupt。

3.1.3.1 风险中止(不引荐)❌
  • stop

并发编程   基础篇(上)   android线程那些事

由于stop办法线程中止很风险的,假如stop办法强行线程中止,那么会使一些整理作业得不到完结,导致资源泄露。

假如线程调用stop办法后导致线程持有的锁忽然开释,那么数据会呈现纷歧致性,目标的内部状况因而被破坏。

stop办法不会保证线程当即停止,运用stop办法或许会导致线程死锁问题。

3.1.3.2 安全中止(引荐)✔️
interrupt线程中止原理

并发编程   基础篇(上)   android线程那些事

interrupt办法是一个标识位,interrupt只是对线程打了一个“中止”的符号,并不是真实的中止线程。当线程进入到堵塞状况时,就会查看这个符号,假如被设置了,就会抛出InterruptedException,然后提早完毕被堵塞状况。 假如线程处于正常活动状况时,假如查看到这个interrupt符号被设置了,那么线程将不会抛出InterruptedException,而是持续正常运转,除非线程在代码中去查看interrupt符号,然后自行决议怎样处理。

interrupt线程中止完结

下面,小木箱带咱们完结一下线程中止的逻辑

并发编程   基础篇(上)   android线程那些事

线程中止面试题

关于线程或线程池的中止有两个问题小木箱需求让咱们考虑一下。

问题一: interrupt、interrupted和isInterrupted有什么差异呢?

并发编程   基础篇(上)   android线程那些事

咱们一般运用interrupted办法能够判断线程是否被中止,能够在循环体中运用interrupted办法判断条件,运用interrupt办法来提早中止线程。

问题二: 线程池是怎样中止的?

Executor的中止操作有两种: 榜首种是经过shutdown办法完结。第二种是经过shutdownNow办法完结。

shutdown办法会等悉数wait线程都履行完毕之后再关闭。

shutdownNow,相当于调用每个thread的interrupt办法。

3.1.4 线程切换

假如当时线程现已完结,那么咱们能够运用yield切换到其他线程去履行

3.2 线程特点

线程操作小木箱说完了,接下来小木箱说一下线程特点,线程特点有三个,榜首个是线程Id,第二个是线程姓名,第三个是看护线程,第四个是线程优先级

首要咱们看一下测验代码,剖析一下线程特点:

并发编程   基础篇(上)   android线程那些事

输出成果:

true

main Thread’s name is main

sub Thread’s name is Thread-0

sub Thread is 22

main Thread id is 1

经过以上测验代码,咱们能够得出结论:

3.2.1 线程ID

并发编程   基础篇(上)   android线程那些事

线程ID能够用来在线程之间传递音讯,线程ID能够用来查看线程的状况,线程ID能够查看线程是否完结某些使命

3.2.2 线程姓名

线程默许姓名是0,Java中的线程姓名是由Thread类的getName办法获取的,该办法回来一个字符串,表明线程的姓名。

在创立线程时,能够运用Thread的构造函数来指定线程的姓名,假如不指定,则体系会主动生成一个姓名,格式为Thread-x,其间x是一个正整数。

3.2.3 看护线程

并发编程   基础篇(上)   android线程那些事

看护线程是程序运转在后台时供给Service的线程,当一切非看护线程完毕时,程序停止,一同杀死一切的看护线程,看护线程不会占用太多的体系资源,通常会在后台运转。写测验类的时分main办法属于非看护线程,运用setDaemon办法能够将一个线程设置为看护线程 与非看护线程相比,看护线程拥有更低的优先级,而且在用户线程完毕时主动完毕。看护线程不能独立运转,而是需求依靠用户线程来履行使命,因而看护线程不能履行实践的使命,而只能为非看护线程供给服务。

3.2.4 线程优先级

并发编程   基础篇(上)   android线程那些事

线程优先级是指线程在多线程环境下的调度优先级,线程优先级决议了体系在多个线程之间进行调度时,哪个线程先履行,哪个线程后履行。线程优先级越高,越简略被调度,即被履行的概率越大,线程的优先级默许是5。

3.3 线程通讯

完结线程协作的办法首要有四种,榜首种是wait/notify/notifyAll办法。第二种是join办法。第三种是await/singal/singalAll办法。终究一种是CountDownLatch。

3.3.1 wait/notify/notifyAll

并发编程   基础篇(上)   android线程那些事

wait/ nofity/notifyAll办法是Object三个办法,详细能够参阅API介绍Object有哪些共用办法?文章介绍,依据承继特性,一切Object子类都能够运用这wait/ nofity/notifyAll办法。

wait/ nofity/notifyAll办法只能在synchronized的同步代码块中运用,不然会抛出反常。

wait办法表明在其他线程调用此目标的 notify办法前,导致当时线程等候。

notify办法表明唤醒在此目标Monitor上等候的单个线程。

notifyAll办法表明唤醒在此目标Monitor上等候的一切线程。

并发编程   基础篇(上)   android线程那些事

Monitor是一种操控多线程同步互斥的机制,每一个Object实例都有一个Monitor与之相相关,每一个Monitor都有一个等候行列,

当调用wait办法时,当时线程就会进入到Monitor的等候行列中,等候被唤醒,当调用notify/notifyAll办法时,就会从Monitor的等候行列中唤醒一个或多个线程,使它们能够持续履行。

并发编程   基础篇(上)   android线程那些事

notify/notifyAll办法用于唤醒正在等候线程Monitor的线程,而Monitor则是一种操控多线程同步的机制,Monitor答应一个线程在其他线程履行操作之前或之后取得操控权,然后等候线程比及从头取得对Monitor操控权后才干持续履行。

那么线程怎样成为该线程目标Monitor的操控者呢?一共有三种办法

  • 运用synchronized关键字,当一个线程取得了某个目标的锁,该线程就成为了该目标的Monitor的操控者,直到它开释了该目标的锁。
  • 运用Object.wait办法,当一个线程调用了某个目标的wait办法,该线程就成为了该目标的Monitor的操控者,直到它被唤醒或超时。
  • 运用Lock接口,当一个线程取得了某个Lock实例的锁,该线程就成为了该Lock实例的Monitor的操控者,直到它开释了该Lock实例的锁。

注意: Monitor目标是同享的。Monitor目标能够保证在同一时间只要一个线程能够拜访该资源,然后防止了多线程拜访该资源时或许呈现的竞赛条件

下面用wait 、 notify 和 notifyAll办法简略的完结一下线程协作:

并发编程   基础篇(上)   android线程那些事

输出成果:

Thread[#22,waitThreadA,5,main] wait !

Thread[#24,waitThreadC,5,main] wait !

Thread[#23,waitThreadB,5,main] wait !

Thread[#1,main,5,main] notify !

Thread[#1,main,5,main] notifyAll !

Thread[#24,waitThreadC,5,main] wait !

经过上述代码咱们能够看到, 当线程A调用目标的wait办法时,线程A就会抛弃目标锁,进入等候此目标的等候确定池,只要针对此目标调用notify办法后本线程才进入目标确定池准备获取目标锁进入运转状况。 当线程A调用目标的notify办法时,线程A就会唤醒等候此目标的线程B,线程B会进入目标确定池准备获取目标锁进入运转状况。 当线程A调用目标的notifyAll办法时,线程A就会唤醒一切等候此目标的线程,这些线程都会进入目标确定池准备获取目标锁进入运转状况。

当然wait和notify也是生产者-顾客的完结模型,详细事项细节能够参阅 #5.1

3.3.2 join

然后说说第二种join办法,主线程需求取得子线程的履行成果,join办法的首要效果是等候调用该办法的线程停止。

并发编程   基础篇(上)   android线程那些事

当一个线程调用另一个线程的join办法时,调用线程将被堵塞,直到被调用的join办法所属的线程停止。

调用线程才持续履行,使一切线程都等候被join的线程停止,这样,才干保证某个线程在另一个线程之前停止。

假设有这样一个场景: 在主线程中发动了一个子线程做耗时作业,主线程会先于子线程完毕, 怎样主线程中取得子线程的成果?

咱们或许会想到用sleep能够让主线程休眠,等子线程履行完了,再持续主线程的履行,可是休眠多久这是彻底不知道的。而且sleep不会开释锁,或许会抛出InterruptedException,Future、CutDownLaunch和join都能够很方便地完结这个功用。

下面,小木箱用join办法等候线程停止去完结这个功用:

并发编程   基础篇(上)   android线程那些事

输出成果:

小木箱说: 子线程开端运转

小木箱说: 子线程运转完毕

小木箱说: 主线程持续运转

join三个线程协作,小木箱用代码完结一下:

并发编程   基础篇(上)   android线程那些事

输出成果:

小木箱生长营的产品经理规划新需求

小木箱开发新需求功用

小木箱生长营的测验测验新功用

3.3.3 await/singal/singalAll

接着说说第三种是await/singal/singalAll ,在Java中,除了Object的waitnotify/notify办法能够完结等候告诉机制。

java.util.concurrent类中供给的ConditionLock合作相同能够完结等候告诉机制,Condition能够更加精确地操控多线程之间的和谐与通讯。

并发编程   基础篇(上)   android线程那些事

Condition目标相关一个锁目标,只要在取得与之相关的锁时,才干够调用Condition实例的await办法使线程等候,或许调用signal/signalAll办法宣布告诉唤醒等候的线程。

当一个线程调用Condition实例的await办法时,能够指定等候的条件,Condition就会开释与之相关的锁,一同进入等候状况,直到其它线程调用Condition实例的signal/signalAll办法时,该线程才会从等候状况中唤醒,并从头取得与之相关的锁。

下面用Condition完结三个线程顺次打印ABC逻辑:

并发编程   基础篇(上)   android线程那些事

输出成果:

ABC

ABC

ABC

3.3.4 CountDownLatch

并发编程   基础篇(上)   android线程那些事

终究说说第三种CountDownLatch,CountDownLatch底层原理是运用可重入锁ReentrantLock和条件变量Condition,一同还有一个计数器count。

当count的值大于0时,表明还有使命没有完结,await办法会被堵塞;当count的值等于0时,表明一切使命现已完结,await办法会回来。

countDown办法会将count减1,当count减至0时,会唤醒await办法回来。

CountDownLatch适用场景,是用来进行多个线程的同步办理,线程调用了countDownLatch.await 之后,需求等候countDownLatch的信号countDownLatch.countDown ,在收到信号前,CountDownLatch不会往下履行。 下面,小木箱用代码完结三个线程顺次打印ABC

并发编程   基础篇(上)   android线程那些事

输出成果:

小木箱生长营A

小木箱生长营B

小木箱生长营C

3.4 线程运转状况

线程运转状况图

线程通讯小木箱说完了,接下来咱们聊一下线程运转状况,线程运转状况能够参阅以下线程运转状况图以及相关参数界说

并发编程   基础篇(上)   android线程那些事

并发编程   基础篇(上)   android线程那些事

线程运转状况

  • Time waiting(睡觉)

    • Thread.sleep
  • Waiting(等候)

    • 界说

      • 等候其他thread显式的唤醒,不然不会被分配CPU时间进入办法
    • 办法

      • object.wait
      • Thread.join
      • LockSupport.park
  • Blocked(堵塞)

    • 等候获取一个排它锁,假如其他thread开释了lock就会完毕此状况

线程挂起/康复

挂起线程是指把正在运转的线程暂停,线程暂停后,线程处于堵塞状况,不会耗费CPU资源,可是线程的状况仍然是RUNNABLE,只是没有被调度到CPU上履行。

康复线程是指把挂起的线程从头调度到CPU上履行,线程康复后,线程处于安排妥当状况,能够被调度到CPU上履行,耗费CPU资源。

线程挂起/康复办法有: join与sleep,wait与notify两组办法。

首要咱们来说一下join与sleep,join线程是指用线程目标调用,假如在一个线程A中调用另一个线程B的join办法,那么线程A将会等候线程B履行完毕后再履行。

并发编程   基础篇(上)   android线程那些事

假如在A线程的代码中调用了join,那么线程A会被挂起直至线程b运转完为止才会持续运转。

并发编程   基础篇(上)   android线程那些事

咱们有序调用notify和wait,先履行wait,再履行notify,就不会像suspend和resume相同产生死锁:

并发编程   基础篇(上)   android线程那些事

并发编程   基础篇(上)   android线程那些事

3.5 生产者顾客模型

线程运转状况小木箱说完了,接下来咱们聊一下生产者顾客模型,生产者顾客办法是经过一个线程容器来处理生产者和顾客的强耦合问题。

并发编程   基础篇(上)   android线程那些事

常见的办法有wait / notify办法、 await / signal办法 、 BlockingQueue堵塞行列办法和Semaphore办法

3.5.1 wait/notify

并发编程   基础篇(上)   android线程那些事

wait/ notify完结底层原了解析参照3.1

下面,小木箱用wait/ notify完结一下生产者顾客模型代码:

并发编程   基础篇(上)   android线程那些事

输出成果:

小燕子 –> 女

小木箱 –> 男

小燕子 –> 女

小木箱 –> 男

3.5.2 ReentrantLock

ReentrantLock是锁的另一种表现办法,由于JVM天生就支撑synchronized,ReentrantLock不是一切JDK版别都支撑,而且synchronized不必担心没有开释锁导致死锁问题,JVM会保证锁的开释,因而除非下列状况主张运用ReentrantLock,不然咱们一律运用synchronized完结线程同步

并发编程   基础篇(上)   android线程那些事

  • ① 假如你想更好的处理死锁,那么ReentrantLock供给了可中止的锁恳求
  • ② 假如你想完结更杂乱的线程同步,更好操控notify哪个线程,那么ReentrantLock供给了wait/notify/signal更多的办法,并结合Condition对ReentrantLock高档应用,支撑多个条件变量
  • ③ 假如你想完结更精确的线程操控,例如每个到来的线程都将排队等候,那么ReentrantLock具有公正锁功用能够协助到你
  • ④假如你想更好的完结多层线程同步,那么主张你运用ReentrantLock可重入锁能力

下面,小木箱用ReentrantLock完结一下生产者顾客模型代码:

并发编程   基础篇(上)   android线程那些事

ReentrantLock(完结lock接口)相关于synchronized多了三个高档功用:

高档功用1: 等候可中止

ReentrantLock榜首个高档功用是ReentrantLock等候可中止,ReentrantLock类供给了一个lockInterruptibly办法,lockInterruptibly办法能够让一个线程在等候锁的过程中呼应中止。

并发编程   基础篇(上)   android线程那些事

ReentrantLock等候可中止完结代码如下:

并发编程   基础篇(上)   android线程那些事

高档功用2: 公正锁

并发编程   基础篇(上)   android线程那些事

ReentrantLock第二个高档功用是ReentrantLock具有公正锁,公正锁是指多个线程依照恳求锁的次序来获取锁,相似排队打饭,先来后到的准则,只要等前面的线程开释了锁,后面的线程才干获取到锁。 非公正锁是指多个线程获取锁的次序没有任何规矩,任何一个线程都有或许取得锁,和先来后到没有任何关系,这样或许导致某些线程一向拿不到锁,成果也便是不公正的了。

synchronized只能对错公正锁,而ReentrantLock既支撑公正锁也支撑非公正锁。

ReentrantLock非公正锁

并发编程   基础篇(上)   android线程那些事

ReentrantLock公正锁

并发编程   基础篇(上)   android线程那些事

高档功用3: ReentrantLock + Condition

并发编程   基础篇(上)   android线程那些事

ReentrantLock第三个高档功用是ReentrantLock能够绑定多个Condition经过屡次newCondition能够取得多个Condition目标,简略的完结杂乱的线程同步

并发编程   基础篇(上)   android线程那些事

3.5.4 BlockingQueue

BlockingQueue完结首要用于生产者-顾客行列,但BlockingQueue别的还支撑 Collection 接口。

并发编程   基础篇(上)   android线程那些事

BlockingQueue是线程安全的,一切排队办法都能够运用内部锁或其他办法的并发操控来主动抵达排队办法的意图。

BlockingQueue以四种办法呈现,关于不能当即满意但或许在将来某一时间能够满意的操作,BlockingQueue的四种办法处理办法不同:榜首种是抛出一个反常,第二种是回来一个特殊值(null 或 false,详细取决于操作),第三种是在操作能够成功前,无限期地堵塞当时线程,第四种是在抛弃前只给定最大时间约束内堵塞。

下面,小木箱用BlockingQueue完结一下生产者顾客模型代码:

并发编程   基础篇(上)   android线程那些事

3.5.5 Semaphore

Semaphore底层原理是基于信号量底层原理,Semaphore是一种用于操控进程或线程拜访同享资源的体系调用。

并发编程   基础篇(上)   android线程那些事

Semaphore经过计数器来统计能够拜访同享资源的进程或线程的数量,当计数器的值大于0时,表明有可用的资源,答应进程或线程拜访同享资源;

当计数器的值等于0时,表明没有可用的资源,不答应进程或线程拜访同享资源。

Semaphore供给了P(Proberen)和V(Verhogen)两种操作,P操作使计数器减1,V操作使计数器加1。

下面,小木箱用Semaphore完结一下生产者顾客模型代码:

并发编程   基础篇(上)   android线程那些事

3.5.6 PipedInputStream / PipedOutputStream

PipedInputStream / PipedOutputStream两个类坐落java.io包中,PipedInputStream / PipedOutputStream是处理同步问题的最简略的办法,一个线程将数据写入管道,另一个线程从管道读取数据,PipedInputStream / PipedOutputStream便构成了一种生产者/顾客的缓冲区编程办法。

并发编程   基础篇(上)   android线程那些事

PipedInputStream/PipedOutputStream只能用于多线程办法,PipedInputStream / PipedOutputStream用于单线程下或许会引发死锁。

在生产者和顾客之间建立一个管道,从成果上看出也能够完结同步,但一般不运用,由于缓冲区不易操控、数据不易封装和传输。

下面,小木箱用PipedInputStream/PipedOutputStream完结一个生产者和顾客模型:

并发编程   基础篇(上)   android线程那些事

四、线程安全

说完线程根底,咱们聊一聊线程安全,线程安全首要有六个问题需求咱们一同考虑

并发编程   基础篇(上)   android线程那些事

4.1 带着问题动身

4.1.1 什么是线程安全?

榜首个问题是什么是线程安全?

《Java Concurrency In Practice》的作者Brian Goetz说过,当多个线程拜访一个目标时,假如不必考虑这些线程在运转时环境下的调度和交替履行,也不需求进行额定的同步,或许在调用方进行任何其他的和谐操作,调用这个目标的行为都能够取得正确的成果,那这个目标是线程安全的

浅显一点来说: 线程安满是指多线程拜访同一个资源时,不会由于线程交叉履行而导致资源混乱,然后保证程序的正确性。

4.1.2 你知道有哪些线程不安全的状况?

第二个问题是你知道有哪些线程不安全的状况?

小木箱从android、容器和线程池三个方面举例说明线程不安全的状况。

首要,在android多线程编程中,假如多个线程一同拜访同一个Activity里的资源,也或许导致线程不安全的状况,由于Activity是一个单例,它的资源或许被多个线程一同拜访。

并发编程   基础篇(上)   android线程那些事

上面的代码中,MyActivity类中界说了一个counter变量,而且在onCreate办法中发动了两个线程,两个线程都会更新counter变量,可是由于没有进行同步操作,这两个线程或许会一同拜访counter变量,然后导致线程不安全的问题,然后或许引发Java反常。

为了处理这个问题,能够在更新counter变量时运用同步操作,例如运用synchronized关键字:

并发编程   基础篇(上)   android线程那些事

然后,在android UI操作并不是线程安全的,而且这些操作有必要在UI线程履行,子线程是无法更新UI的,详细完结思路如下:

并发编程   基础篇(上)   android线程那些事

但这并非肯定的,子线程其实也是能够更新UI的

Toast本质是经过window显现和绘制的,而子线程不能更新UI在于ViewRootImpl的checkThread办法无法在Activity维护View树的行为。

然后,HashMap的putVal办法增加元素不是线程安全的,因而可经过Collections类的静态办法synchronizedMap取得线程安全的HashMap

并发编程   基础篇(上)   android线程那些事

终究,在ThreadPoolExecutor中,addWorker为什么需求持有mainLock,本质原因是workers是HashSet类型的,不能保证线程安全。

并发编程   基础篇(上)   android线程那些事

4.1.3 怎样防止线程安全问题?

第三个问题怎样防止线程安全问题?当存在多个线程协作同享数据时,需求保证同一时间有且只要一个线程在操作同享数据,其他线程有必要比及该线程处理完数据后再进行,保证线程安全的办法有三种。

榜首种是运用线程安全的类,如AtomicInteger类;

第二种是加锁排队履行,如synchronized和ReentrantLock等运用;

第三种是运用线程本地变量,如ThreadLocal来处理。

后文会详细解说完结过程和原理。

4.1.4 多线程会带来哪些线程安全问题?

多线程会带来哪些线程安全问题?多线程会带来四个线程安全问题。榜首个问题是原子性问题,第二个问题是竞赛条件,第三个问题是死锁,第四个问题是丢掉更新。

首要咱们来说说榜首个问题原子性问题,某些操作不能被中止,比方赋值操作,假如多线程一同对同一变量进行赋值操作,会导致变量的值不正确。比方多线程一同拜访 i++ 的场景:

并发编程   基础篇(上)   android线程那些事

如下图反常代码所示,输出成果或许小于20,由于多线程一同对count变量进行赋值操作,或许会呈现线程安全问题。为了处理这个问题,能够运用同步代码块,保证变量的操作是原子性的

并发编程   基础篇(上)   android线程那些事

输出成果:

18

那么正确的编码是怎样的呢,咱们应该运用同步代码块,能够保证count变量的操作是原子性的

并发编程   基础篇(上)   android线程那些事

输出成果:

20

第二个问题是竞赛条件,当多个线程一同拜访某个变量时,某个线程修正了变量的值,可是其他线程没有读取到修正后的值,导致程序呈现过错,如下面的代码所示:

并发编程   基础篇(上)   android线程那些事

输出成果:

Thread-1 : 2

Thread-3 : 4

Thread-0 : 1

Thread-4 : 5

Thread-2 : 3

上述代码中,5个线程一同拜访count变量,而且在run办法中将count变量加1,可是由于多线程的存在,有或许某个线程修正了count变量的值,而其他线程还没有读取到修正后的值,这就或许导致程序呈现过错。

为了处理这个问题,能够运用synchronized关键字来对count变量进行同步操作:

并发编程   基础篇(上)   android线程那些事

输出成果:

Thread-0:1

Thread-4:2

Thread-3:3

Thread-2:4

Thread-1:5

第三个问题是死锁,多个线程彼此等候对方开释某个资源,导致程序无法持续履行。

假设有两个小木箱线程P1和小木箱线程P2,它们别离需求资源A和资源B。

可是它们一同只能取得一个资源,当小木箱线程P1取得资源A时,P2取得资源B,然后小木箱线程P1等候资源B,小木箱P2等候资源A。

可是由于资源A和资源B只要一个,所以小木箱线程P1和小木箱线程P2都无法取得自己想要的资源,这便是死锁。

死锁的原因是由于小木箱线程P1和小木箱线程P2一同恳求资源A和资源B,而资源A和资源B只要一个。

所以小木箱线程P1和小木箱线程P2都无法取得自己想要的资源,然后导致死锁的产生。

并发编程   基础篇(上)   android线程那些事

输出成果:

Thread[#23,小木箱线程P2,5,main]get ResB

Thread[#22,小木箱线程P1,5,main]get ResA

Thread[#23,小木箱线程P2,5,main]waiting get ResA

Thread[#22,小木箱线程P1,5,main]waiting get ResB

终究一个问题是丢掉更新。

丢掉更新是指当多个线程一同拜访某个变量时,某个线程修正了变量的值。

可是其他线程没有读取到修正后的值,导致程序呈现过错。

首要原因是多个线程一同拜访某个变量,而该变量没有被同步,导致其他线程读取到的值不是最新的值。

并发编程   基础篇(上)   android线程那些事

输出成果:

count=10000

处理计划有三种,榜首种是运用synchronized关键字,运用synchronized关键字能够处理多线程拜访变量时呈现的线程安全问题。第二种是运用Atomic类,Atomic类供给了一种原子操作,能够处理多线程拜访变量时呈现的线程安全问题。第三种是运用volatile关键字,volatile关键字能够保证多线程拜访变量时,能够读取到最新的值。

4.1.5 线程安全问题体现是怎样的?

线程编程会带来功用问题呢?首要有两个方面,榜首个方面是线程调度,第二个方面是线程协作。

首要,咱们说说榜首个方面,线程调度详细体现是缓存失效带来功用问题。下面代码存在线程不安全的问题,由于多个线程一同对SUM变量进行操作,或许会呈现脏读、脏写等问题。

并发编程   基础篇(上)   android线程那些事

输出成果:

199269

能够运用synchronized关键字加锁来处理。

并发编程   基础篇(上)   android线程那些事

输出成果:

200000

由于程序有很大约率会再次拜访刚才拜访过的数据,所以为了加速整个程序的运转,会运用缓存,这样咱们在运用相同数据时就能够很快地获取数据。

可一旦进行了线程调度,切换到其他线程,CPU就会去履行不同的代码,由于CPU读写缓存速度低于内存读写缓存速度,原有缓存很或许失效,需求从头读写缓存新数据,形成必定的开支。

因而,线程调度器为了防止频频地产生上下文切换,有四种处理办法,别离是削减线程的数量、 调整线程优先级、 整线程优先级、调整时间片巨细和运用预取战略。

削减线程的数量方面,咱们能够削减线程的数量,能够削减上下文切换的产生次数,这样能够进步功率。

调整线程优先级方面,咱们在线程调度器中,能够经过调整线程的优先级来操控上下文切换次数,使核算机体系能够更高效地运转。

调整时间片巨细方面,咱们在线程调度器能够经过调整时间片巨细来操控上下文切换的产生次数,以进步体系功率。

运用预取战略方面,咱们在线程调度器能够运用预取战略来削减上下文切换次数,以进步体系功率。

首要,咱们说说第二个方面线程协作导致线程不安全的状况。

由于主线程在设置running变量为false之前,另一个线程或许现已读取了running变量的值并将它设置为true。这样,循环就会一向运转,导致线程不安全的状况。

并发编程   基础篇(上)   android线程那些事

为了处理这个问题,能够运用synchronized关键字来保证变量running的原子操作:

并发编程   基础篇(上)   android线程那些事

由于线程之间假如有同享数据,为了防止数据错乱,为了保证线程安全,或许制止编译器和 CPU 对其进行重排序等优化,也或许出于同步的意图,重复把线程作业内存的数据 flush 到主存中,然后再从主内存 refresh 到其他线程的作业内存中。

这些问题在单线程中并不存在,但在多线程中为了保证数据的正确性,就不得不采纳上述办法,由于线程安全的优先级要比功用优先级更高,间接下降了功用。

4.1.6 怎样获取子线程的成果?

第五个问题为怎样获取子线程的成果?获取子线程的成果有两种办法,榜首种办法是FutureCallable,第二种办法是Callable接口。

由于Runnable没有详细回来值,也不能抛出checked Exception。

并发编程   基础篇(上)   android线程那些事

假如咱们想要拿到线程履行成果,那么主张运用Future和Callable办法。

Future是一个存储器,Future存储了call0这个使命的成果,而这个使命的履行时间是无法提早确定的。

由于这彻底取决于call办法履行的状况,经过Future.get来获Callable取接口回来的履行成果。

并发编程   基础篇(上)   android线程那些事

输出成果:

并发编程   基础篇(上)   android线程那些事

假如咱们想撤销使命的履行,咱们能够调用cancel办法。

  1. 假如这个使命还没有开端履行,那么这种状况最简略,使命会被正常的撤销,未来也不会被履行,办法回来true。
  2. 假如使命已完结,或许已撤销,那么cancel办法会履行失利,办法回来false。
  3. 假如这个使命现已开端履行了,那么这个撤销办法将不会直接撤销该使命,而是会依据咱们填的参数。 mayInterruptIfRunning。

运用Future的注意点有两个。

榜首个是当for循环批future量获取的成果时,简略产生一部分线程很慢的状况,get办法调用timeout时应运用约束。

第二个是生命周期只能前进,不能后退。就和线程池的生命周期相同,一旦彻底完结了使命,Future就永久停在了“已完结”的 状况,不能重头再来。


Callable比较简略了,相似于Runnable,被其它线程履行的使命,完结call办法,有回来值

并发编程   基础篇(上)   android线程那些事

输出成果:

小木箱说: 我是call的回来值

4.1.7 什么是多线程的上下文切换?

第六个问题什么是多线程的上下文切换?在实践开发中,线程数往往是大于 CPU 核心数的,比方 CPU 核心数或许是 8 核、16 核等等,但线程数或许抵达成百上千个。

这种状况下,操作体系就会依照必定的调度算法,给每个线程分配时间片,让每个线程都有时机得到运转。

而在进行调度时就会引起上下文切换,上下文切换会挂起当时正在履行的线程并保存当时的状况,然后寻找下一处行将康复履行的代码,唤醒下一个线程,以此类推,重复履行。

但上下文切换带来的开支是比较大的,假设咱们的使命内容十分短,比方只进行简略的核算,那么就有或许产生咱们上下文切换带来的功用开支比履行线程自身内容带来的开支还要大的状况。

那么什么状况会导致密布的上下文切换呢?假如程序频频地竞赛锁,或许由于 IO 读写等原因导致频频堵塞,那么程序就或许需求更多的上下文切换,上下文切换导致了更大的开支,咱们应该尽量防止这种状况的产生。

4.2 线程安全特性

带着问题动身小木箱说完了,接下来咱们聊一下线程安全特性,无论是缓存失效、仍是上下文切换带来的时序性问题和线程调度引发的数据准确性问题。

在深入了解Java虚拟机那本书统一概括总结为线程安全的三大特性: 原子性、有序性和可见性。

并发编程   基础篇(上)   android线程那些事

4.2.1 可见性

当线程 A在CPU1上履行,线程 B在CPU2上履行,同享变量param=0,线程 A给同享变量param赋值时,会把param的初始值加载到CPU1的高速cache中,然后赋值2,线程 B给同享变量param赋值时,会把param的初始值加载到CPU2的高速cache中。此时param的值在CPU2中是0而不是2。线程 A在CPU1中修正了param可是CPU2中的线程 B却没有拿到。

保证线程可见性的办法有三种,榜首种是供给volatile关键字保证可见性。

当一个变量被volatile润饰时,保证修正的值会被马上更新到主内存中,当其他线程读取时,会去主内存中读取新值。

第二种是synchronized。第三种是Lock。

lock和synchronized由于同一时间只要一个线程履行,锁开释之前会把变量的修正刷新到主内存中.

并发编程   基础篇(上)   android线程那些事

运用volatile关键字能够保证变量可见性。当一个线程对volatile变量进行写操作时,会导致其他线程对该变量的读操作当即可见

并发编程   基础篇(上)   android线程那些事

运用synchronized关键字能够保证变量可见性。当一个线程拜访一个目标的synchronized办法或许synchronized块时,其他线程对该目标的其他synchronized办法或许synchronized块的拜访将被堵塞,直到拜访线程离开synchronized办法或许synchronized块时,其他线程才干够拜访该目标的其他synchronized办法或许synchronized块。

并发编程   基础篇(上)   android线程那些事

这样一来,synchronized关键字能够保证在同一时间,只要一个线程能够履行某个办法或许某个代码块,然后也就保证了操作的可见性,即一个线程修正了某个同享变量的值,这新值对其他线程来说是可见的。

ReentrantLock经过运用内部的可重入锁来保证可见性。当线程取得锁时,它会同步内存,保证一切线程都能看到最新的状况。当线程开释锁时,ReentrantLock会将最新的状况写回主内存,保证其他线程能够看到最新的状况。

并发编程   基础篇(上)   android线程那些事

那么保证线程可见性的本质是什么呢? Happens-Before准则 ,什么是Happens-Before准则呢? JMM向程序员供给了足够强内存可见性保证,不影响程序履行成果状况,可见性保证并必定存在,比方下面的程序,A Happens-Before B 并不保证,由于其不影响程序履行成果;

并发编程   基础篇(上)   android线程那些事

JMM为了满意编译器和处理器的约束尽或许少

并发编程   基础篇(上)   android线程那些事

Happens-Before遵从的规矩是:只要不改动程序的履行成果,编译器和处理器想怎样优化就怎样优化。

Happens-Before核心思想是: 前一个操作的成果对后续操作时可见的

Happens-Before意图是: 为了在不改动程序履行成果的前提下,尽或许地进步程序履行的并行度。

4.2.2 有序性

JMM (Java Memory Model) 有序性问题是指在多线程编程中,由于编译器和处理器优化,导致程序履行次序与代码次序纷歧致的问题。JMM 为程序员供给了一种牢靠的机制来保证程序的正确性,然后防止呈现不行预料的成果。

并发编程   基础篇(上)   android线程那些事

由于JMM界说了一种内存模型,该模型界说了线程之间的内存拜访次序,可是由于处理器的指令重排序,导致线程之间的内存拜访次序或许会产生改动。

并发编程   基础篇(上)   android线程那些事

这就导致了线程之间存在有序性问题,然后呈现了程序的纷歧致性。

并发编程   基础篇(上)   android线程那些事

JMM有序性问题能够经过运用运用synchronized关键字、运用Lock锁和运用Atomic类来处理: 运用synchronized关键字能够保证每次对变量的读写操作都是从主内存中读取最新的值,然后保证线程间变量的一致性。

并发编程   基础篇(上)   android线程那些事

当一个线程拜访一个目标的synchronized代码块时,其他线程便不能拜访该目标的其他synchronized代码块。

Lock锁能够保证每次对变量的读写操作都是从主内存中读取最新的值,然后保证线程间变量的一致性。当多个线程拜访同一个ReentrantLock目标时,多个线程会依照获取锁的次序顺次获取锁,而不会产生竞赛,然后保证线程安全。

并发编程   基础篇(上)   android线程那些事

ReentrantLock运用一个可重入的锁来保证线程依照次序拜访同享资源。ReentrantLock运用FIFO(先进先出)的锁行列来办理等候的线程,然后保证线程依照它们宣布恳求的次序来拜访同享资源。

Atomic类供给了一种能够完结原子操作的办法,然后保证线程间变量的一致性。Atomic还能够经过运用内存屏障来保证操作的有序性,内存屏障能够保证操作在特守时间点完结,然后保证操作的有序性。

并发编程   基础篇(上)   android线程那些事

在实践开发过程中,Android中存在多个进程,多个进程间经过IPC进行数据交互,假如没有同步机制,会呈现JMM有序性问题。

4.2.3 原子性

JVM原子性是指Java虚拟机中的指令是原子的,也便是说,它们不能被其他线程中止或改动。这意味着,当一个线程正在履行某个指令时,其他线程就不能改动它。这保证了程序的正确性,防止了线程之间的竞赛条件。

并发编程   基础篇(上)   android线程那些事

JVM经过运用synchronized关键字、运用Atomic类、运用Lock锁和运用CAS算法来保证原子性。

运用Synchronized关键字能够保证在同一时间只要一个线程能够履行某个办法或某个代码块,Synchronized能够保证操作的原子性,即操作过程不行被中止,Synchronized运用的是互斥锁机制,能够保证同一时间只要一个线程能够拜访某个资源,然后保证了操作的原子性。

并发编程   基础篇(上)   android线程那些事

运用Atomic类的办法能够保证操作的原子性,即每次操作都是不行中止的,也便是说在一个线程进行操作的过程中,其他线程不能中止或插入,只要当时线程完结操作后,其他线程才干进行操作。

并发编程   基础篇(上)   android线程那些事

运用ReentrantLock能够保证原子性,由于ReentrantLock运用了一个可重入的锁,ReentrantLock能够保证操作的原子性,即一个操作有必要在另一个操作完结之前完结。ReentrantLock还能够经过运用比较和交换(CAS)技能来保证原子性,然后保证操作的一致性和正确性。

并发编程   基础篇(上)   android线程那些事

运用CAS算法能够经过比较并交换操作来保证原子性。当多个线程一同测验修正某个变量时,CAS算法会查看变量当时值是否与预期值持平,假如持平,就修正变量的值,不然就不做任何修正。这种办法能够保证多个线程一同修正变量时,只要一个线程的修正能够成功,然后保证了原子性。

并发编程   基础篇(上)   android线程那些事

简略总结一下:

假如多线程拜访同一块资源时分,你想要保证资源的可见性,那么小木箱主张你运用volatile、synchronized、ReentrantLock和Atomic

假如多线程拜访同一块资源时分,你想要保证资源的有序性,那么小木箱主张你运用synchronized和ReentrantLock

假如多线程拜访同一块资源时分,你想要保证资源的原子性,那么小木箱主张你运用synchronized、Atomic、ReentrantLock和CAS算法

4.3 线程安全强度

线程安全特性小木箱说完了,接下来咱们聊一下线程安全强度,线程安全强度有三大特征: 榜首,线程不行变。第二,肯定线程安全。第三,相对线程安全。第四,线程阻隔。

线程不行变

线程不行变是指一个线程实例的状况在它被创立之后不能被改动的概念。也便是说,一旦一个线程被创立,它的特点例如它的优先级,姓名等都不能被改动。

不行变的object必定是线程安全的,由于线程的状况是由操作体系内核操控的,操作体系内核不答应线程的状况产生变化。

运用final根底数据类型、string、枚举类型、Number部分子类和调集类型Collections.unmodifiableXXX()获取不行变调集都能够保证线程不行变

肯定线程安全

肯定线程安满是指在多线程环境下,任何时间都不会呈现数据纷歧致的状况,也便是说不论多少个线程一同对同一个数据进行操作,终究成果都是一致的,一个线程对数据的改动,其他线程都能看到,也便是说肯定线程安满是指多个线程一同对数据进行操作时,数据的一致性是肯定保证的。

肯定线程安全的完结,能够下降体系呈现线程安全问题的或许性,进步体系的稳定性和牢靠性。

不论运转环境怎样,调用者都不需求任何额定的sync操作,比方:ATM取钱,怎样去的和取完之后怎样拿走不影响取钱这个事务安全

相对线程安全

相对线程安满是指在多线程环境下,有必定的约束条件下,不会呈现数据纷歧致的状况,也便是说在满意必定的条件下,多个线程一同对数据进行操作时,数据的一致性是相对保证的。

保证对这个object独自的操作是thread安全的;可是对一些特定次序的接连调用,需求额定的手段来保证,java中的大部分thread安全类都属于这种类型,比方:Vector、HashTable、Collections、synchronizedCollection()办法包装的调集

线程阻隔

线程阻隔指目标自身不是thread安全的,可是能够在调用端采用正确的同步手段来保证目标在并发环境中安全的运用。

为了进步体系的并发功用,削减线程之间的竞赛。 为了进步体系的安全性,防止一个线程对其他线程形成损害。为了进步体系的稳定性,防止一个线程中止其他线程的履行。为了进步体系的可维护性,防止一个线程影响其他线程的运转。JVM对各个线程进行独立阻隔

线程敌对

无法在多线程环境中并发运用的代码,java中几乎没有,由于会导致一种不行操控的状况,然后使体系处于不稳定的状况。

线程敌对的意图是为了维护多个线程之间的同享资源,防止不同线程之间的数据竞赛,然后防止程序呈现数据纷歧致的问题。

4.4 线程安全计划

线程安全强度小木箱说完了,接下来咱们聊一下线程安全计划,小木箱将上述的安全战略概括总结为四大类,互斥堵塞同步、非堵塞同步、无同步计划和操控并发流程

4.4.1 互斥堵塞同步

堵塞线程履行抵达同步的意图

synchronized

依照锁的运用方位,synchronized锁的类型有四种: 榜首种是目标锁,第二种是办法锁,第三种是类锁,第四种是静态办法锁

榜首种是目标锁,synchronized(this),同步一个代码块,只效果于一个目标,假如调用两个目标的同步代码块办法,不会同步,能够用来维护一个目标的实例办法,使得多个线程能够一同拜访,可是同一时间只能有一个线程能够履行该办法,以防止多线程环境中数据呈现过错。

第二种是类锁,synchronized(Student.class),同步一个类,对这个类的一切目标同步,能够用来维护一个类的静态办法,使得多个线程能够一同拜访,可是同一时间只能有一个线程能够履行该办法,以防止多线程环境中数据呈现过错。

第三种是办法锁,synchronized method,同步一个办法,效果同上,只效果于一个目标,当多个线程拜访同一个目标的实例办法时,它们会被同步,以保证线程安全。

第四种是静态办法锁,synchronized static method,同步静态办法,效果同步一个类,当多个线程拜访同一个类的静态办法时,它们会被同步,以保证线程安全。

并发编程   基础篇(上)   android线程那些事
新版别jvm对synchronized进行了许多优化,例如自旋锁等,synchronized的功用稍微比ReentrantLock差一点

独占锁Reentrantlock、同享锁CountDownLatch、CyclicBarrier、Phaser、Semaphore等
4.4.2 非堵塞同步

非堵塞同步首要处理线程等候、切换带来的功用问题。基于冲突检测的达观并发战略:先操作,假如没有其他线程竞赛就直接成功;不然采纳补偿措施不断重试,直到成功;底层需求硬件指令集支撑。

CAS(compare and swap) 和原子类AtomicInteger的办法compareAndsSet()、getAndIncrement()都对错堵塞同步表现办法

4.4.3 无同步计划

所谓的无同步计划便是操控并发流程

操控并发流程界说

操控并发流程是指保证线程安全,不必定要同步,假如一个method不触及同享数据,就无须同步。

操控并发流程特征
  • 特征一: 栈关闭

多个thread拜访同一个method的局部变量时,不会呈现thread安全问题,由于局部变量存储在虚拟机栈中,属于thread私有的

  • 特征二: 线程本地存储(Thread Local Storage)

假如一段代码中的数据有必要与其他线程同享,那就保证同享数据代码在同一个thread里边履行,无须同步也能保证thread不会呈现数据争用的问题

能够运用ThreadLocal类完结thread本地存储功用

  • 特征三: 可重入代码

能够在代码履行的任何时间中止,转而履行别的一段代码,本来的程序不会呈现任何过错

怎样操控并发流程

Java操控并发能够运用Cyclicbarrier和RxJava来操控并发流程。下面侧重来解说Cyclicbarrier,Cyclicbarrier是一种同步东西类,Cyclicbarrier答应一组线程彼此等候,直到抵达某个公共屏障点(common barrier point)。

并发编程   基础篇(上)   android线程那些事

当一切线程都抵达屏障点时,屏障点才会翻开,一切线程才干持续履行。 Cyclicbarrier能够用来完结多线程之间的协作,比方说,玩家在游戏中抵达某个关卡时,一切玩家都要抵达某个方位,然后才干持续游戏,这时分就能够运用Cyclicbarrier了。 Cyclicbarrier底层运用AQS(AbstractQueuedSynchronizer)完结,AQS是一种基于FIFO行列完结的锁机制,AQS经过一个int变量来表明同步状况,当状况为0时,表明无锁状况,当状况大于0时,表明有锁状况。

Cyclicbarrier在内部维护了一个计数器,每当一个线程抵达屏障点时,计数器的值就会减1,当计数器的值减为0时,表明一切线程都现已抵达屏障点,此时就会翻开屏障,一切线程持续履行。

并发编程   基础篇(上)   android线程那些事

Cyclicbarrier优点有两个,榜首个是Cyclicbarrier能够完结让一组线程等候至某个状况之后再悉数一同履行。 第二个是Cyclicbarrier能够复用,当线程抵达屏障点后,计数器会重置为初始值,这样就能够屡次运用Cyclicbarrier了。

Cyclicbarrier缺陷有两个,榜首个是当某个线程超时或许被中止时,整个体系都会受到影响,由于其他线程都会被堵塞。第二个是假如线程太多,或许会导致计数器溢出。

简略总结一下便是:

假如你想保证线程不行变,那么小木箱主张你运用String、Integer 、volatile和 ConcurrentHashMap

假如你想保证线程相对安全,那么小木箱主张你运用mutex、semaphore、lock、局部变量、可重入函数、非堵塞算法和Vector等

假如你想保证线程处于阻隔状况,那么小木箱主张你在Linux运用Namespace机制、运用锁和ThreadLocal

假如你想保证肯定的线程敌对,那么小木箱主张你运用原子操作、线程池拜访权限管控、同步锁、volatile关键字和信号量

假如你想保证肯定的线程安全,那么能够运用原子操作、同步锁、volatile关键字和ConcurrentHashMap

4.5 UncaughtException兜底

终究,咱们测验回答一个问题: 线程的未捕获反常UncaughtException应该怎样处理?

并发编程   基础篇(上)   android线程那些事

当线程抛UncaughtException,咱们能够运用UncaughtExceptionHandler处理,由于主线程能够轻松发现反常,子线程却不行。

在子线程抛出了反常会被主线程覆盖,子线程反常无法用传统办法捕获子线程抛出反常。

主线程try catch没用,只能捕获当时线程的反常,不能直接捕获一切反常,因而UncaughtExceptionHandler进步了代码健壮性。

并发编程   基础篇(上)   android线程那些事

输出成果: 小木箱生长营捕获了Thread-0线程的反常

这样,咱们能够全局为不健康的线程进行兜底管控。

五、结语

并发编程 根底篇(上) android线程那些事课程就此完毕了,关于每一个Android开发者来说,线程常识重要性不言而喻,国内为什么老八股喜欢考线程常识,由于假如你不具备这方面扎实的线程安全和线程根底常识,那么应对高功用下载组件完结仍是处理发动和卡顿优化等作业都十分扎手。

下一节,小木箱将带咱们学习并发编程 根底篇(中) 三大剖析法剖析Handler。

我是小木箱,假如咱们对我的文章感兴趣,那么欢迎重视小木箱的公众号小木箱生长营。小木箱生长营,一个专心移动端共享的互联网生长社区。