多线程操作实例讲解

作者:创世魂

 

 

一、线程定时器

 

● 启动
● 停止
● 定时任务(事件)

 

“启动”帮助文档:

本方法为多线程操作,使用时请注意线程冲突问题。

通过本方法可启动一个多线程的定时任务,调用本方法后会触发“定时任务”事件。

参数1:填写要定时的周期,单位毫秒。

参数2:首次定时是否延迟启动,单位毫秒,填写0表示不延迟。

参数3、4:用于传递对象,传递的对象可在“定时任务”的事件中使用。

 

“停止”帮助文档:

顾名思义,调用本方法可停止定时任务。

停止后,“定时任务”事件将不会被触发。

 

“定时任务(事件)”帮助文档:

调用“启动()”后会触发本事件。

参数1、2的对象,是由“启动()”方法传递过来。

如果启动方法为传递过来,此处的对象则为空对象。

 

代码编写如下图:

1、定义类型为“线程定时器”的成员变量。

2、 “按钮1”被点击后,调用“启动()”方法,启动线程定时器。

3、 “按钮2”被点击后,调用“停止()”方法,停止定时器。

 

4、添加“线程定时器_定时任务”接收事件,在当前事件下,计算定时次数并显示在编辑框中。

 

运行结果如下图(GIF):

 

 

二、线程类

 

● 启动
● 线程启动(事件)

 

“启动”帮助文档:

通过本方法可以启动一条线程,会触发“线程启动”事件。

参数1、2可用于传递操作对象。

优点:加载网页源码或者网络图片时不会卡UI。

缺点:每一个线程类只能启动一次,仅第一次启动时有效。

如果想要启动多次,定义局部变量,并调用“挂接事件()”方法即可解决多次调用。

 

“线程启动(事件)”帮助文档:

调用“启动”方法会会触发本事件。

参数1、2的对象类是由“启动()”方法传递过来,如果启动方法未传递,此参数为空对象。

 

代码编写如下图:

1、定义类型为“线程类”的成员变量。

2、调用“启动”方法传递一个网址到线程事件中。

 

3、利用“对象到文本()”取出网址,并用“安卓网络操作类.HTTP读文件()”读取网站源码。

4、将读取的源码显示在编辑框中。

 

运行结果如下图(GIF):

 

 

三、线程基础类

 

● 本类说明

 

“线程类”是本类的子类,所以可通过“线程类”直接调用本类中的方法。

● 中断线程
● 是否有中断请求

 

“中断线程”帮助文档:

注意:中断线程并非终止线程,只是设置“是否有中断请求()”方法返回值为真。

 

“是否有中断请求”帮助文档:

如果调用了“中断线程()”方法,那么本方法必然返回真。

如果没有再次调用“中断线程()”,那么下次调用本方法会返回假。

 

代码编写如下图:

参考“线程类”中的获取网页源码方法,进行简单修改。

1、在“线程启动”事件下,首先调用“中断线程()”方法。

2、随后调用“是否有中断请求()”方法判断其返回值,根据返回值决定是否中断取网页源码操作。

 

运行结果如下图(GIF):

执行此获取网页源码操作,会提示线程已经中断。

● 置线程名称
● 取线程名称

 

“置线程名称”帮助文档:

顾名思义,通过本方法可以给指定线程设定名称。

 

“取线程名称”帮助文档:

通过本方法可取出“置线程名称()”设置的名称。

 

代码编写如下图:

1、“按钮1”被点击后,调用“启动()”方法启动线程,随后调用“置线程名称()”设置名称。

2、在“线程启动”事件中,调用“取线程名称()”方法可做一些判断操作,通过判断结果来执行不同的方法。

 

运行结果如下图(GIF):

 

● 等待执行许可
● 授予执行许可

 

“等待执行许可”帮助文档:

通俗来讲,调用本方法后可以让线程等待一段时间执行。

等待的时间由参数决定,单位毫秒。

如果在等待时间内执行了“授予执行许可()”方法,则线程会跳过等待立刻执行。

 

“授予执行许可”帮助文档:

调用本方法可以让线程跳过“等待执行许可()”方法中设置的时间,立刻执行。

 

代码编写如下图:

1、“按钮1”被点击启动线程后,在“线程启动”事件中调用“等待执行许可()”等待3秒钟执行信息框。

2、“按钮2”被点击后,调用“授予执行许可()”方法。

 

运行结果如下图(GIF):

左图:点击“按钮1”需要等待3秒钟,才会弹出信息框。

右图:“按钮2”被点击后,立刻弹出了信息框。

 

● 睡眠当前线程

 

帮助文档如下图:

本方法可“等待执行许可()”方法类似,都是用来延迟操作的。

不同之处为,本方法并不能保证精确的睡眠指定时间,而且还受外部线程影响。

 

代码编写如下图:

“线程基础类”属于全局类方法,因此可以直接调用“睡眠当前线程()”进行延迟操作。

 

运行结果如下图(GIF):

点击“按钮1”大约1秒后才会弹出提示信息框。

 

 

四、原子操作

 

● 本分类说明

 

在本分类中,拥有“原子逻辑型类”“原子长整数类”“原子长整数数组类”“原子整数类”“原子整数数组类”等原子类操作。

这些原子类的作用,就是用来维护在多线程环境中数值的稳定性。

例如:想要在多线程中使用“n=n+1”的操作,但是最终结果可能因为多线程的缘故而出问题。

原子操作中提供了类似的方法,可以很方便的实现“n=n+1”的操作,并且数值极为稳定不会出错。

 

在本教程中将会举例:“原子逻辑型类”“原子整数类”的基本应用。

 

● 原子逻辑型类

 

本类说明:

本类类似于自带锁性质,通过逻辑变量控制,不会被任何线程所干扰。

 

“比较更新”帮助文档:

通过本方法可比较逻辑变量,实现一些比较特殊的效果。

调用本方法后“比较值”比较过后会更新成“更新值”并返回真,再次比较的时候会返回假。

 

代码编写如下图:

通过本方法特性,进行如下代码编写。

1、定义类型为“原子逻辑型类”的变量。

2、调用“循环()”方法,循环10“比较更新()”方法比较两个逻辑值,并判断返回值是否等于真。

3、在如果语句体内,执行“编辑框1.添加内容行()”方法,给编辑框添加内容。

 

运行结果如下图(GIF):

通过此方法的特性,最后结果无论怎么点击“按钮1”编辑框中始终只是输出一条消息。

当然无论是在多线程环境还是非多线程环境都会是同样的结果。

 

● 原子整数类

 

本类说明:

本类和“原子逻辑型类”基本一致,不同之处就是本类是用来操作整数。

并且同样不会被其它线程所干扰。

 

● 当前值(读写)
● 递增取值

 

“当前值(写)”帮助文档:

通过本属性可设置原子整数类中当前整数值。

同名读属性,可读取原子整数类中存储的整数值。

 

“递增取值”帮助文档:

调用本方法就等同于在循环中使用“i=i+1”,不同之处为本方法会保持数值稳定性。

可通过返回值获取“原子整数类”递增后的值,也可通过“当前值”读属性获取。

 

代码编写(1):

1、定义“原子整数类”成员变量。

2、重置编辑框的内容和原子整数的值,目的是为了方便重复测试。

3、定义类型为“线程类[100]”的多线程数组变量,循环100次创建100个多线程对象。

注意:数组类对象要进行“新建对象()”,并且局部变量要挂接事件。

4、循环100次,启动定义的线程。

 

代码编写(2):

“线程启动”事件中,调用“递增取值()”方法进行递增操作,并将递增结果显示在编辑框中。

 

运行结果如下图:

最终结果,1-100数值正常稳定。

 

代码编写(3):

如果把“递增取值()”方法换成“i=i+1”,则执行结果未必就能保证数值正确。

 

运行结果如下图:

结果中出现了两个“2”的情况,证明“递增取值()”方法比“i=i+1”更加稳定。

注意:“i=i+1”也只是可能会不稳定,大多数情况依然能保持稳定结果,尤其是在循环次数比较低的情况。

如果“按钮1”被点击,循环执行1000次线程,或者更多次数不稳定系数会大大增加。

 

● 创建
● 加运算并取值

 

“创建”帮助文档:

顾名思义,通过本方法可设置当前“原子整数类”中的初始值。

 

“加运算并取值”帮助文档:

本方法的作用就是进行加法运算,和普通的加法运算相比,本方法在多线程中更加稳定。

参数1:填写要操作的原子整数对象,动态调用时忽略本参数。

参数2:要进行加法运算的加数。

提示:可通过返回值获取运算结果也可通过“当前值”读属性获取。

 

代码编写如下图:

1、调用“创建()”方法设置原子初始值,赋值给定义的“原子整数”变量。

2、调用“加运算并取值()”进行加10操作,最后取出当前值显示在编辑框中。

 

运行结果如下图:

最终结果就是20,证明本方法作用就是进行了加法运算。

 

● 取值并加运算

 

帮助文档如下图:

通过本方法可实现自定义加减乘除运算,具体运算过程由“整数运算符基础类”实现。

参数2:更新值,实际上就是其中一个运算值。

 

“整数运算符基础类”帮助文档:

本类只有一个虚拟方法,想要创建虚拟方法就需要定义一个新的类来实现。

注意:本类需要API24,即安卓7.0才可以运行。

 

“运算”帮助文档:

本虚拟方法只有两个参数。

参数1:此参数的数值实际上就是“原子整数类”中存储的数值。

参数2:此参数的数值是“取值并加运算()”方法填写的“参数2”

 

代码编写如下图:

1、定义“基础类”“整数运算符基础类”的类对象,类名随意。

2、添加“运算()”虚拟方法后,调用“返回()”“第一个操作数”“第二个操作数”进行数学运算。

 

3、定义类型为“原子整数类”“数学运算”(此类名要和定义的类名一致)的变量。

4、调用“当前值”写属性设置原子整数的初始值。

5、调用“取值并运算()”方法进行计算,此处实际上就是“4+5”

6、计算结果会覆盖“当前值”,最后将其取出即可。

 

运行结果如下图:

 

● 取值更新

 

帮助文档如下图:

通过本方法可将参数中的“更新值”设置为原子整数类中的“当前值”

并且返回值是更新后的值。

 

代码编写如下图:

1、定义类型为“原子整数类”的变量,设置“当前值”属性。

2、调用本方法设置新的值。

3、取出新的值显示在编辑框中。

 

运行结果如下图:

可以看到取出来的最新值就是“取值更新()”参数中的值。

 

● 递减取值

 

帮助文档如下图:

本方法和“递增取值()”的不同之处在于,本方法等同于使用“i=i-1”

 

代码编写如下图:

1、定义类型为“原子整数类”的变量,并设置“当前值”

2、循环10次调用“递减取值()”方法,并取出递减后的值显示在编辑框中。

 

运行结果如下图:

 

 

五、线程池

 

● 本类说明

 

线程池,简单来说就是可以重复使用的多线程,并且还可以控制线程数等操作。

在本篇教程中会列举一个实例来讲解线程池的基本使用方法。

 

● 列举实例讲解

 

界面准备:

准备如下界面:依次为“按钮1”“自定义表格1”

 

代码编写如下图:

1、界面准备完毕后,设置自定义表格的“列数”为3列,并定义类型为“线程池类”变量。

 

2、添加启动类“通知_被创建”虚拟方法,并“创建缓存线程池()”

3、如果在非列表中使用线程可用“创建核心线程池()”或者“创建自定义线程池 ()”

 

4、“按钮1”被点击后,利用“文本到对象()”方法,将一堆图片地址插入到自定义表格中。

 

5、添加“自定义表格_取对象项目视图”接收事件,代码分为两个部分,如下。

第一部分:黄色方框内的用于定义自定义表格项目,项目内容为一个图片框。

第二部分:剩余内容用于启动线程池,加载网络图片。

 

6、重点讲解第二部分内容:

(1)线程运行类:本类类似于“线程类”,都是通过一个启动方法来启动线程事件。

不同之处为,本类是为“线程池”提供服务。

(2)置用户对象:本方法有两个“对象类”可传递参数到“任务启动”事件中。

(3)上述例子代码中传递了“项目数据对象”“图片框1”,这个“项目数据对象”实际上就是表格插入项目传递过来的图片地址。

(4)执行任务:通过定义的“线程池变量”调用,目的就是启动定义的“线程运行类”对象。

 

7、添加“线程运行类_任务启动”接收事件,线程的具体运行过程都是由本事件执行。

8、将“用户对象2”参数强转为图片框,这个图片框组件实际上就是自定义表格中的图片框。

9、利用“对象到文本()”方法,将“用户对象1”重新转换成网络图片地址,并用“HTTP读文件()”方法转换成字节数组。

10、最后利用“图片框1.置图片数据()”方法将图片字节数组显示在图片框中。

 

运行结果如下图(GIF):

最终“按钮1”被点击后,线程池会启动将所有的网络图片非常快速的加载到自定义表格的图片框中。

 

 

六、线程锁

 

● 本分类说明

 

“线程锁”的作用有点类似于原子操作,只不过“原子操作”的作用是用来维护在多线程过程中数值运算的稳定性。

“线程锁”的作用则是用来维护数据读写操作的稳定性。

 

● 线程读写锁类

 

读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者。

读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。

本篇教程中将会简单演示读写锁的基本应用场景。

需要用到两个方法分别是“取读锁()”“取写锁()”

 

“取读锁”帮助文档:

本方法可取出“线程读锁类”对象,通过此对象针对“读数据”进行加解锁。

 

“取写锁”帮助文档:

本方法可取出“线程写锁类”对象,通过此对象针对“写数据”进行加解锁。

 

“加/解锁”帮助文档:

无论是“线程读锁类”还是“线程写锁类”对象,这两个对象都继承了“线程锁基础类”

在此基础类中,有“加锁()”“解锁()”方法可供调用,并且是成对使用。

 

代码编写(1):

1、定义类型为“线程读写锁类”“文本型”的成员变量,并给文本型变量设置初始值。

2、循环创建并启动两条线程。

 

代码编写(2):

3、在“线程启动”接收事件下,判断“标记值”分别执行写锁和读锁操作。

 

运行结果如下图:

左图:加/解锁状态下的最终结果。

右图:无锁状态下的结果。

  

 

● 线程重入锁类

 

本类说明:

同一个重入锁可以在未解锁的情况下在同一线程中多次加锁,即为重入。

下面实现一个“重入锁”的实例。

 

代码编写(1):

1、定义类型为“线程重入锁类”的成员变量。

2、“按钮1”被点击后,循环创建三个线程,设置线程名称后并循环启动。

 

代码编写(2):

3、在“线程类_线程启动”接收事件中,进行“加锁”操作后,循环添加内容到编辑框中并“解锁”

 

运行结果如下图:

左图:重入锁加锁后的运行结果。

右图:未加锁的运行结果。