android 使用线程 post请求,Android 多线程

Android中实现多线程,常见的方法有:

继承Thread类

实现Runnable接口

ThreadPoolExecutor

AsyncTask

Handler

ThreadLocal

HandlerThread

IntentService

Thread

具体使用

// 步骤1:创建线程类 (继承自Thread类)

class MyThread extends Thread{

// 步骤2:复写run(),内容 = 定义线程行为

@Override

public void run(){

... // 定义的线程行为

}

}

// 步骤3:创建线程对象,即 实例化线程类

MyThread mt=new MyThread(“线程名称”);

// 步骤4:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起 / 停止

// 此处采用 start()开启线程

mt.start();

匿名类使用

// 步骤1:采用匿名类,直接 创建 线程类的实例

new Thread("线程名称") {

// 步骤2:复写run(),内容 = 定义线程行为

@Override

public void run() {

// 步骤3:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起 / 停止

}.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();

匿名类使用

// 步骤1:通过匿名类 直接 创建线程辅助对象,即 实例化 线程辅助类

Runnable mt = new Runnable() {

// 步骤2:复写run(),定义线程行为

@Override

public void run() {

}

};

// 步骤3:创建线程对象,即 实例化线程类;线程类 = Thread类;

Thread mt1 = new Thread(mt, "窗口1");

// 步骤4:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起 / 停止

mt1.start();

6f5405a81b84

synchronized相关问题

1、使用注意问题:锁对象不能为空,作用域不宜过大,避免死锁

2、Lock和synchronized如何选择:

ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,

int maximumPoolSize,

long keepAliveTime,

TimeUnit unit,

BlockingQueue workQueue,

ThreadFactory threadFactory,

RejectedExecutionHandler handler)

corePoolSize:线程池核心线程数(平时保留的线程数)

maximumPoolSize:线程池最大线程数(当workQueue都放不下时,启动新线程,最大线程数)

keepAliveTime:超出corePoolSize数量的线程的保留时间。

unit:keepAliveTime单位

workQueue:阻塞队列,存放来不及执行的线程

ArrayBlockingQueue:构造函数一定要传大小

LinkedBlockingQueue:构造函数不传大小会默认为(Integer.MAX_VALUE ),当大量请求任务时,容易造成 内存耗尽。

SynchronousQueue:同步队列,一个没有存储空间的阻塞队列 ,将任务同步交付给工作线程。

PriorityBlockingQueue : 优先队列

threadFactory:线程工厂

handler:饱和策略

AbortPolicy(默认):直接抛弃

CallerRunsPolicy:用调用者的线程执行任务

DiscardOldestPolicy:抛弃队列中最久的任务

DiscardPolicy:抛弃当前任务

阿里Java开发手册建议,不要手动创建线程(new Thread),不要使用官方推荐的线程池(FixedThreadPool, SingleThreadPool, CachedThreadPool, ScheduledThreadPool ).使用 自行创建 ThreadPoolExecutor 方式。

AsyncTask

AsyncTask 类属于抽象类,即使用时需 实现子类

public abstract class AsyncTask {

...

}

// 类中参数为3种泛型类型

// 整体作用:控制AsyncTask子类执行线程任务时各个阶段的返回类型

// 具体说明:

// a. Params:开始异步任务执行时传入的参数类型,对应excute()中传递的参数

// b. Progress:异步任务执行过程中,返回下载进度值的类型

// c. Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致

// 注:

// a. 使用时并不是所有类型都被使用

// b. 若无被使用,可用java.lang.Void类型代替

// c. 若有不同业务,需额外再写1个AsyncTask的子类

}

6f5405a81b84

AsyncTask原理=2个线程池 + Handler

6f5405a81b84

Handler机制

Handler的处理过程运行在创建Handler的线程里

一个Looper对应一个MessageQueue

一个线程对应一个Looper

一个Looper可以对应多个Handler

线程是默认没有Looper的,线程需要通过Looper.prepare()、绑定Handler到Looper对象、Looper.loop()来建立消息循环

主线程(UI线程),也就是ActivityThread,在被创建的时候就会初始化Looper,所以主线程中可以默认使用Handler

可以通过Looper的quitSafely()或者quit()方法终结消息循环,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。

不确定当前线程时,更新UI时尽量调用post方法

重要的类 Handler Message MessageQueue Looper

总体流程是:Handler 发送 Message 到 MessageQueue 队列中,Looper 循环从 MessageQueue 中获取消息 发送给 Handler,最后Handler 的 handleMessage 处理消息。

6f5405a81b84

流程

6f5405a81b84

原理图

6f5405a81b84

说明

几个注意点

即 主线程的Looper对象自动生成,不需手动生成;而子线程的Looper对象则需手动通过Looper.prepare()创建

在子线程若不手动创建Looper对象 则无法生成Handler对象

Handler 在 dispatchMessage时:

若msg.callback属性不为空,则代表使用了post(Runnable r)发送消息,则直接回调Runnable对象里复写的run()

若msg.callback属性为空,则代表使用了sendMessage(Message msg)发送消息,则回调复写的handleMessage(msg)

post 和 sendMessage 区别

post不需外部创建消息对象,而是内部根据传入的Runnable对象 封装消息对象

回调的消息处理方法是:复写Runnable对象的run()

postDelay 原理

调用路径:1.Handler.postDelayed(Runnable r, long delayMillis)

2.Handler.sendMessageDelayed(getPostMessage(r), delayMillis)

3.Handler.sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)

4.Handler.enqueueMessage(queue, msg, uptimeMillis)

5.MessageQueue.enqueueMessage(msg, uptimeMillis)

原理流程:

postDelay()一个10秒钟的Runnable A、消息进队,MessageQueue调用 nativePollOnce() 阻塞,Looper阻塞;

紧接着post()一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用 nativeWake() 方法唤醒线程;

MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper;

Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()阻塞;

直到阻塞时间到或者下一次有Message进队;

Handler内存泄漏

Handler 的用法:新建 内部类 或 匿名内部类

泄漏原因:Java中,非静态内部类 匿名内部类 都默认持有 外部类的引用。主线程Looper对象的什么周期=该应用程序的生命周期。在Handler消息队列有未处理或正在处理的的消息时,默认持有外部类的引用销毁,由于引用关系,GC 无法回收。

解决方案:

静态内部类+弱引用

private static class FHandler extends Handler{

// 定义 弱引用实例

private WeakReference reference;

// 在构造方法中传入需持有的Activity实例

public FHandler(Activity activity) {

// 使用WeakReference弱引用持有Activity实例

reference = new WeakReference(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;

}

}

}

当外部类结束生命周期时,清空Handler内消息队列

@Override

protected void onDestroy() {

super.onDestroy();

mHandler.removeCallbacksAndMessages(null);

// 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期

}

问题补充

6f5405a81b84

面试常见问题

1、多个

2、有1个。通过 Looper 源码可知,使用 ThreadLocal 内的 ThreadLocalMap 保存 key->ThreadLocal,value->Looper。部分源码如下:

//Looper.java

private static void prepare(boolean quitAllowed) {

if (sThreadLocal.get() != null) {

throw new RuntimeException("Only one Looper may be created per thread");

}

sThreadLocal.set(new Looper(quitAllowed));

}

//ThreadLoal.java

public void set(T value) {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map != null)

map.set(this, value);

else

createMap(t, value);

}

3、内部类持有外部类的引用 �MessageQueue - Message - Handler - Activity

4、主线程 ActivityThread 中的 main() 函数中已经启动了 Looper.prepareMainLooper 和 Looper.loop

子线程要 prepare 和 loop,用完后要 quit

5、调用 Looper 的 quit() ;释放内存 释放线程

6、synchronized同步锁保证安全,所以 delaymsg 时间是不准确的

7、obtain()

8、首先主线程的Looper是不能quit的,会一直存在。所有的消息都会运行在其中。卡死和阻塞主线程会导致ANR,Looper的block只是睡眠

ThreadLocal (参考)

ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。

synchronized 和 ThreadLocal 对比:

对于多线程资源共享的问题,synchronized 仅提供一份变量,让不同的线程排队访问,而ThreadLocal为每一个线程都提供了一份变量,因此可以同时访问而互不影响。但是ThreadLocal却并不是为了解决并发或者多线程资源共享而设计的

所以ThreadLocal既不是为了解决共享多线程的访问问题,更不是为了解决线程同步问题,ThreadLocal的设计初衷就是为了提供线程内部的局部变量,方便在本线程内随时随地的读取,并且与其他线程隔离。

ThreadLocal的应用场景:

当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候

如:属性动画为每个线程设置AnimationHandler、Android的Handler消息机制中通过ThreadLocal实现Looper在线程中的存取、EventBus获取当前线程的PostingThreadState对象或者即将被分发的事件队列或者当前线程是否正在进行事件分发的布尔值

复杂逻辑下的对象传递

使用参数传递的话:当函数调用栈更深时,设计会很糟糕,为每一个线程定义一个静态变量监听器,如果是多线程的话,一个线程就需要定义一个静态变量,无法扩展,这时候使用ThreadLocal就可以解决问题。

public T get() {

//1、首先获取当前线程

Thread t = Thread.currentThread();

//2、根据当前线程获取一个map

ThreadLocalMap map = getMap(t);

.....

}

public void set(T value) {

//1、首先获取当前线程

Thread t = Thread.currentThread();

//2、根据当前线程获取一个map

ThreadLocalMap map = getMap(t);

if (map != null)

//3、map不为空,则把键值对保存到map中

map.set(this, value);

//4、如果map为空(第一次调用的时候map值为null),则去创建一个ThreadLocalMap对象并赋值给map,并把键值对保存到map中。

else

createMap(t, value);

}

get 和 set 方法里面都有一个 ThreadLocalMap。

Android早期版本,这部分的数据结构是通过Values实现的,Values中也有一个table的成员变量,table是一个Object数组,也是以类似map的方式来存储的。偶数单元存储的是key,key的下一个单元存储的是对应的value,所以每存储一个元素,需要两个单元,所以容量一定是2的倍数。这里的key存储的也是ThreadLocal实例的弱引用

如何保证线程安全

每个线程拥有自己独立的ThreadLocals变量(指向ThreadLocalMap对象 )

每当线程 访问 ThreadLocals变量时,访问的都是各自线程自己的ThreadLocalMap变量(键 - 值)

ThreadLocalMap变量的键 key = 唯一 = 当前ThreadLocal实例

HandlerThread

HandlerThread原理

Thread类 + Handler类机制

通过继承Thread类,快速地创建1个带有Looper对象的新工作线程

通过封装Handler类,快速创建Handler & 与其他线程进行通信

// 步骤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) {

...//消息处理

return true;

}

});

// 步骤4:使用工作线程Handler向工作线程的消息队列发送消息

// 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作

// a. 定义要发送的消息

Message msg = Message.obtain();

msg.what = 2; //消息的标识

msg.obj = "B"; // 消息的存放

// b. 通过Handler发送消息到其绑定的消息队列

workHandler.sendMessage(msg);

// 步骤5:结束线程,即停止线程的消息循环

mHandlerThread.quit();

IntentService

开启一个新的工作线程

@Override

public void onCreate() {

super.onCreate();

// 1. 通过实例化andlerThread新建线程 & 启动;故 使用IntentService时,不需额外新建线程

// HandlerThread继承自Thread,内部封装了 Looper

HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");

thread.start();

// 2. 获得工作线程的 Looper & 维护自己的工作队列

mServiceLooper = thread.getLooper();

// 3. 新建mServiceHandler & 绑定上述获得Looper

// 新建的Handler 属于工作线程 ->>分析1

mServiceHandler = new ServiceHandler(mServiceLooper);

}

/**

* 分析1:ServiceHandler源码分析

**/

private final class ServiceHandler extends Handler {

// 构造函数

public ServiceHandler(Looper looper) {

super(looper);

}

// IntentService的handleMessage()把接收的消息交给onHandleIntent()处理

@Override

public void handleMessage(Message msg) {

// onHandleIntent 方法在工作线程中执行

// onHandleIntent() = 抽象方法,使用时需重写 ->>分析2

onHandleIntent((Intent)msg.obj);

// 执行完调用 stopSelf() 结束服务

stopSelf(msg.arg1);

}

}

/**

* 分析2: onHandleIntent()源码分析

* onHandleIntent() = 抽象方法,使用时需重写

**/

@WorkerThread

protected abstract void onHandleIntent(Intent intent);

通过onStartCommand() 将Intent 传递给服务 & 依次插入到工作队列中

/**

* onStartCommand()源码分析

* onHandleIntent() = 抽象方法,使用时需重写

**/

public int onStartCommand(Intent intent, int flags, int startId) {

// 调用onStart()->>分析1

onStart(intent, startId);

return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;

}

/**

* 分析1:onStart(intent, startId)

**/

public void onStart(Intent intent, int startId) {

// 1. 获得ServiceHandler消息的引用

Message msg = mServiceHandler.obtainMessage();

msg.arg1 = startId;

// 2. 把 Intent参数 包装到 message 的 obj 发送消息中,

//这里的Intent = 启动服务时startService(Intent) 里传入的 Intent

msg.obj = intent;

// 3. 发送消息,即 添加到消息队列里

mServiceHandler.sendMessage(msg);

}

从上面源码可看出:IntentService本质 = Handler + HandlerThread:

通过HandlerThread 单独开启1个工作线程:IntentService

创建1个内部 Handler :ServiceHandler

绑定 ServiceHandler 与 IntentService

通过 onStartCommand() 传递服务intent 到ServiceHandler 、依次插入Intent到工作队列中 & 逐个发送给 onHandleIntent()

通过onHandleIntent() 依次处理所有Intent对象所对应的任务

不建议通过 bindService() 启动 IntentService

原因:bind的生命周期为

onCreate() ->> onBind() ->> onunbind()->> onDestory()

并没有执行 onStart 或 onStartCommand ,故不会将消息发送到消息队列,那么onHandleIntent()将不会回调,即无法实现多线程的操作

6f5405a81b84

6f5405a81b84

多线程并发问题

当只有一个线程写,其它线程都是读的时候,可以用volatile修饰变量

当多个线程写,那么一般情况下并发不严重的话可以用Synchronized,不过Synchronized有局限性,比如不能设置锁超时,不能通过代码释放锁。

ReentranLock 可以通过代码释放锁,可以设置锁超时。

高并发下,Synchronized、ReentranLock 效率低,因为同一时刻只有一个线程能进入同步代码块,如果同时有很多线程访问,那么其它线程就都在等待锁。这个时候可以使用并发包下的数据结构,例如ConcurrentHashMap,LinkBlockingQueue,以及原子性的数据结构如:AtomicInteger。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值