Android多线程编程(二)
不管是安卓还是其他的不放呢,同步与异步都是必不可少的知识,我们在学习工作的时候多多少少都会接触到这一部分,今天我就来详细的记录学习一下安卓学习的这一部分,首先我们来了解一下同步与异步的总体知识:
同步与异步
在我们编写的程序项目中,同步与异步都是并存的,不存在优劣之分,他们在不同的地方执行着自己独有的功能,我们根据不同的情况也会采取不同的通讯方式。比如说,我们这个程序,目前有很多任务,比如用户点击刷新,就会执行触碰判断请求,网络请求,界面刷新请求等很多种,那么我们采用同步和异步会如何执行?
同步
同步通讯的思想就是,用户操作之后,就等待,等待这部分程序依次全都跑完,然后再告诉你我们这里结束了,也就是这个时候返回数据给用户,用户会因此等待一阵子。
对应线程就是,这几个请求我们已经放在几个线程中了,不过呢,需要一个一个执行,其他线程等一等吧。
异步
异步通讯的思想就是,用户在进行操作后,我们将用户的操作请求放在请求队列中,并反馈给用户说这个操作已经在执行了,你想干什么干什么吧,我们会在后面执行你这个操作,你不用等了。
对应线程就是,这几个请求我们已经在多个线程中进行同时处理了,这几个线程给我一起冲。
这样看起来,异步是不是比同步好很多,但是存在就是有原因的,有一些问题是异步无法解决的,类似于:
张三李四王五三个人同时在不同的火车站售票口买票,买的还都是同一趟列车的票,如果我们这个时候使用异步通讯,三个售票口分别在后台进行处理,那么结果就是三个人买的票都是同一张,那么问题就大了,这个位置是谁的?所以对于这种多个线程需要同时访问共享资源的时候,我们就需要用到线程的同步知识了,一个一个售票口进行访问火车票资源,这样才可以避免卖了同一张票。
那么接下来,我们就开始正式学习安卓线程的同步和异步知识:
安卓线程异步通讯
在安卓中,只会存在两种线程,UI线程(主线程)以及工作线程(子线程),UI线程中主要负责处理用户的点击事件,绘图事件等UI界面相关的工作,而工作线程就负责处理耗时逻辑,等任务完成后通知UI线程更新UI界面。那么这就涉及到了线程之间的异步通讯了,我们接下来就来了解一下:
线程中进行异步消息处理的,有AsyncTask,Handler四兄弟,Activity的runOnUIThread方法以及View的post以及postDelay方法,但是现在不妙的是,AsyncTask已经被弃用了,泪目,那就只能不学这部分了,我们依次来弄一下这几个通讯方式:
Handler异步通讯机制
事先说一下哈,这里知识会进行简要介绍,不会有什么源码解析什么的,源码解析应该会在后面学学吧,现在先掌握用法。
四兄弟介绍
异步消息处理主要是由四部分组成,Message,Handler,MessageQueue和Looper,对于Message以及Handler应该大多都有接触过,接下来我们依次学习一下:
Message
Message,顾名思义,信息,他是在线程中传递的信息,Message内部可以携带少量的信息,用于在不同的线程之间进行数据交换,Message不仅可以使用what字段携带信息,也可以用arg1和arg2来携带整形数据,还有obj字段来携带一个Object对象,在后面我们会写一写代码来进行实际应用;
Handler
Handler,顾名思义,处理者,那他处理什么呢,处理发送消息(sendMessage())方法,处理接收的消息(handlerMessage())方法,也就是这么个流程:使用Handler的sendMessage()方法发送出去了一条消息,传递传递,处理处理,最终传递到了需要这条消息的位置,这个位置的Handler的handlerMessage()方法对消息及逆行了接收处理;
MessageQueue
MessageQueue,顾名思义,消息队列,这就很好理解了,Handler发了几条信息,这部分消息还没被处理的时候,就会被放置在消息队列MessageQueue中,直到被处理,需要注意的是,每个线程只会有一个MessageQueue对象;
Looper
Looper,顾名思义,经过我的有道翻译之后,他被翻译成了电影,因此无法顾名思义,可惜。不过这个Looper,就是相当于管家,用来管理MessageQueue消息队列,在我们调用了Looper的loop()方法后,就会进入到一个无限的循环之中,每当MessageQueue中存在一条消息,我们就会把他取出,并且传递到之前说过的Handler的handlerMessage()方法中,进行消息处理。每个线程中只有一个MessageQueue消息队列,那么相应的也就只有一个管家,Looper。
异步通讯处理流程
1.子线程在进行UI操作的时候,会创建Message对象存储消息,并且通过Handler的sendMessage()方法来发送这条消息;
2.这条消息会进入到MessageQueue消息队列中,Looper会从MessageQueue中取出这条待处理消息,并且准备传递给主线程的handlerMessage()方法进行处理;
3.主线程创建Handler对象,并且重写实现handlerMessage()方法;
4.消息最终在主线程中的Handler的handlerMessage()中进行处理。
这个UI操作消息通过一系列的传递处理后,从子线程传递到了主线程进行处理,这样就做到了可以安全更新UI的操作,我们来画一张流程图来更清晰的看看这个处理流程:
就这样,接下来我们尝试代码简单实现一下:
Handler异步消息机制代码实现
之前说了那么多,不过我们在实际应用的时候基本上是不会全都写的,这部分看起来很多,但是应用的时候代码却很简洁,基本上就是用到了说的Message还有Handler:
准备接收消息以及对UI进行更新处理的Handler:
private Handler mHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
String timeClock = (msg.obj).toString();
time.setText(timeClock);
}
};
发送消息的Handler:
private void timeClock() {
new Thread(new Runnable() {
@Override
public void run() {
while (count >= 0) {
Message msg = new Message();
msg.obj = count;
mHandler.sendMessage(msg);
count--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
这就是一个简单的倒计时,看一下效果:
好了,下一个:
Activity.runOnUiThread(Runnable)
这盒相对来说就简单多了,从函数名我们就可以看出,是在UI线程上进行运行,直接看代码吧,和上一个效果一样:
private void timeClock() {
new Thread(new Runnable() {
@Override
public void run() {
while (count >= 0) {
runOnUiThread(new Runnable() {
@Override
public void run() {
String timeclock = String.valueOf(count);
time.setText(timeclock);
count--;
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
是不是和之前的很像,没错,就是按照上面的改了一小部分,把handler传递改成了runOnUiThread()这个方法,这样就可以在这个工作线程中进行UI线程的工作;
View.post(Runnable)
不想看了,下次再看,芜湖,自由的气息!
安卓线程同步通讯
在安卓中,其实同步编程使用的不算多,远没有异步编程使用广泛,但是我们仍突然要熟悉,说不定什么时候就用到了,我们就直接开始吧。
使用synchronized关键字
每一个Java对象都有一个内置锁,我们在使用synchronized关键字修饰方法或者代码块的时候,内治所会保护整个方法或者代码快,想执行这个方法活代码块时,必须要获得内置锁,在运行时会加上内置锁,运行结束时,内置锁就会打开了。类似加锁操作如下:
private synchronized void startProgressAnimation() {
if (singleTapThread == null) {
singleTapThread = new SingleTapThread();
getHandler().postDelayed(singleTapThread, 100);
}
}
类似这种,在void之前加上synchronized关键字,这样的话,就算有多个线程访问这个方法,这个方法也只会依次执行。不过需要注意的是,synchronized关键字只能锁住同一个对象的线程,也就是说,锁住的是这个方法所属的对象自身。这种是修饰方法,也可以修饰代码块:
synchronized (this){
getHandler().postDelayed(singleTapThread, 100);
}
这种就不多说了,和上面的一样。
volatile关键字
刚才说的synchronized有一个弊端,那就是不能只对某个变量加锁,这样很容易就会造成资源浪费,属于一种重量锁,而我们接下来要说的volatile则是一种轻量锁,锁住的是变量。
我们用volatile关键字修饰变量,可以保证不同的线程对共享数据的可见性,因为我们在多个线程同时对某个共享数据进行处理时,会有这种流程:
我也不知道为什么一直都是歪的,挺离谱的。不过也就是这个意思,线程从主存中取出数据进行修改,这个时候另一个线程可能同时对其进行修改,这个时候可能就会出现数据错乱的现象,这个问题我们采用synchronized关键字;
但是看图,我们所有的线程都有一个工作缓存区,这个缓存区在各个线程的处理下各不相同,那么这个时候我们就要用到volatile关键字了,用volatile关键字修饰的变量,在修改时,可以保证工作缓存区内的数据也会同样发生修改。
volatile int value=0;
大概的代码就是如从,具体的还是要看应用。
基本就到这吧,其他的我用的也不多,等有需要的时候再记笔记。