笔记 33 | Android通信之Thread类实现多线程

地址

http://www.jianshu.com/p/af255c526f7e

目录

  • Thread概述
  • 售票员线程
  • Handler
  • Looper与Message

一.Thread概述

Thread类是真正的线程,查看源码可见Thread也实现了Runnable接口,但它内部有创建新的工作线程,所以Thread对象运行在与主线程不一样的分线程上。

Thread主要函数:

  • run()//包含线程运行时所执行的代码
  • start()//用于启动线程
  • sleep()/sleep(long millis)//线程休眠,交出CPU,让CPU去执行其他的任务,然后线程进入阻塞状态,sleep方法不会释放锁
  • yield()//使当前线程交出CPU,让CPU去执行其他的任务,但不会是线程进入阻塞状态,而是重置为就绪状态,yield方法不会释放锁
  • join()/join(long millis)/join(long millis,int nanoseconds)//等待线程终止,直白的说 就是发起该子线程的线程 只有等待该子线程运行结束才能继续往下运行
  • wait()//交出cpu,让CPU去执行其他的任务,让线程进入阻塞状态,同时也会释放锁
  • interrupt()//中断线程,自stop函数过时之后,我们通过interrupt方法和isInterrupted()方法来停止正在运行的线程,注意只能中断已经处于阻塞的线程
  • getId()//获取当前线程的ID
  • getName()/setName()//获取和设置线程的名字
  • getPriority()/setPriority()//获取和这是线程的优先级 一般property用1-10的整数表示,默认优先级是5,优先级最高是10,优先级高的线程被执行的机率高
  • setDaemon()/isDaemo()//设置和判断是否是守护线程
  • currentThread()//静态函数获取当前线程

Thread线程主要状态:

  • New 一旦被实例化之后就处于new状态
  • Runnable 调用了start函数之后就处于Runnable状态
  • Running 线程被cpu执行 调用run函数之后 就处于Running状态
  • Blocked 调用join()、sleep()、wait()使线程处于Blocked状态
  • Dead 线程的run()方法运行完毕或被中断或被异常退出,线程将会到达Dead状态

因为Thread对象运行在另外的线程,所以它与Runnable实例有如下主要区别:

  1. Thread对象可进行网络通信,而非Thread方式的Runnable实例不可进行网络通信。因为Android要求UI线程不能访问网络,所以运行在UI线程上的Runnable也就不能访问网络。
  2. 因为Android要求只有UI线程才能操作页面视图,所以运行在UI线程上的Runnable可直接访问视图或控件,而Thread对象不可直接访问视图或控件。Thread对象若想访问,只能发送消息通知Handler对象去操作UI。

因为Thread对象运行在另外的线程,所以它与Runnable实例有如下主要区别:

  1. Thread对象可进行网络通信,而非Thread方式的Runnable实例不可进行网络通信。因为Android要求UI线程不能访问网络,所以运行在UI线程上的Runnable也就不能访问网络。
  2. 因为Android要求只有UI线程才能操作页面视图,所以运行在UI线程上的Runnable可直接访问视图或控件,而Thread对象不可直接访问视图或控件。Thread对象若想访问,只能发送消息通知Handler对象去操作UI。

二.售票员线程

为加深对Thread类工作机制的了解,博主认真实践了网上广为流传的售票员线程例子,下面是Thread的三种调用方式:

  1. 同一Thread实例多次start,重复调用会抛出异常。因为start是同步方法,不允许同一时刻多次运行;
  2. 创建多个Thread实例分别start,三个线程每个各卖100张,总共卖了300张票;
  3. 只创建一个Runnable实例,使用该实例启动三个线程一起卖票,总共卖了100张票;
  • TicketThread
public class TicketThread extends Thread {  

    private int ticketCount = 100;  

    @Override  
    public void run() {  
        for (; ticketCount>0; ticketCount--) {  
            String desc = String.format("%d %d 当前余票为%d张",  
                    System.currentTimeMillis(), getId(), ticketCount);  
            System.out.println(desc);  
        }  
    }  
}
  • 多线程调用的三种方式
public class ThreadTest {  

    public static void main(String[] arg) {  
        //方式一,同一Thread实例多次start,重复调用会抛出异常。因为start是同步方法,不允许同一时刻多次运行  
//      TicketThread seller = new TicketThread();  
//      seller.start();  
//      seller.start();  
//      seller.start();  

        //方式二,创建多个Thread实例分别start,三个线程每个各卖100张,总共卖了300张票  
//      new TicketThread().start();  
//      new TicketThread().start();  
//      new TicketThread().start();  

        //方式三,只创建一个Runnable实例,使用该实例启动三个线程一起卖票,总共卖了100张票  
        Runnable mSeller = new Runnable() {  
            private int ticketCount = 100;  
            @Override  
            public void run() {  
                for (; ticketCount > 0; ticketCount--) {  
                    String desc = String.format("%d 当前余票为%d张",   
                            System.currentTimeMillis(), ticketCount);  
                    System.out.println(desc);  
                }  
            }  
        };  
        new Thread(mSeller).start();  
        new Thread(mSeller).start();  
        new Thread(mSeller).start();  
    }  
}

其中方式三由于使用的是Runnable实例,故而无法打印线程号,无法确定是多线程交替卖票。但可通过下列手段进行观察:

  1. 从方式三的日志看出,当前余票数目不是连续递减的,间接证明存在多线程的情况;
  2. 在日志中打印时间戳,可发现方式三的耗时要小于方式一(大约是二分之一不到)。方式一已经明确是单线程,那么方式三加快速度的原因也只能是多线程工作了。

三.Handler

Handler用于UI线程与分线程之间的通信,分线程利用Handler实例向UI线程发送消息,UI线程收到消息后在Handler对象中进行处理。下面是Handler的常用方法:

  1. 在UI线程中运行,只能重写的方法,不能直接调用: handleMessage : 存放消息处理的具体逻辑 dispatchMessage : 一般不用重写。用来分发消息,如果消息中有设置Runnable对象,则消息交给Runnable对象处理;如果消息中未设置Runnable对象,则消息交给主线程处理。一般Thread线程不设置Runnable对象,下面是Android中dispatchMessage方法的源码:
public void dispatchMessage(Message msg) {  
    if (msg.callback != null) {  
        handleCallback(msg);  
    } else {  
        if (mCallback != null) {  
            if (mCallback.handleMessage(msg)) {  
                return;  
            }  
        }  
        handleMessage(msg);  
    }  
}

上面对dispatchMessage的说明很容易让人犯糊涂,我们再看一下postDelayed方法的源码,我们知道,Handler的postDelayed方法可用于启动Runnable对象:

public final boolean postDelayed(Runnable r, long delayMillis) {  
    return sendMessageDelayed(getPostMessage(r), delayMillis);  
}  

private static Message getPostMessage(Runnable r) {  
    Message m = Message.obtain();  
    m.callback = r;  
    return m;  
}

从以上源码可以看出,postDelayed方法中把要启动的Runnable对象给设置到Message回调变量中,这样主线程处理消息时调用的是该Runnable对象,而不是自身的handleMessage方法,所以启动Runnable不需要重写handleMessage方法,而Thread启动就要重写handleMessage方法。

  1. 在分线程中直接调用的方法:

obtainMessage : 获取当前消息的对象 sendMessage : 向主线程发送消息 sendMessageDelayed : 延迟一段时间向主线程发送消息 sendMessageAtTime : 在指定时间向主线程发送消息 sendEmptyMessage : 向主线程发送空消息 sendEmptyMessageDelayed : 延迟一段时间向主线程发送空消息 sendEmptyMessageAtTime : 在指定时间向主线程发送空消息 removeMessages : 从消息队列中根据消息标识移除指定消息 hasMessages : 判断消息队列中是否存在指定消息标识的消息

总结一下Handler的消息流转流程:

  • Thread方式下运行:主线程创建Handler实例->启动分线程->分线程调用Handler实例的send系列方法->主线程分发消息dispatchMessage(消息中的Runnable对象为空)->主线程处理消息handleMessage。
  • Runnable方式下运行:主线程创建Handler实例->主线程调用Handler实例的post系列方法(内部其实是send方法,也就是说,主线程自己调用send方法)->主线程分发消息dispatchMessage(消息中的Runnable对象不为空)->Runnable对象启动运行。

四.Looper与Message

Looper类主要是对消息队列MessageQueue进行管理,一般情况下不用关心。有用到的话,就是在构造Handler时传入指定的Looper对象。

Message类是Handler机制中存放消息的包裹,其作用类似于组件通信Intent机制中的Bundle类。Message类的主要参数说明如下: what : 整型的消息标识,在Handler的removeMessages和hasMessages方法中作为消息判断的唯一标识 arg1 : 整型数,可存放消息的处理结果 arg2 : 整型数,可存放消息的处理代码 obj : Object类型,可存放消息传递的数据结构 replyTo : Messenger类型,回应信使,在跨进程通信中使用,线程间通信用不着

获取一个Message有两种方式:

  • 调用Handler的obtainMessage方法,示例代码如下:
Handler mHandler = new Handler();  
Message msg = mHandler.obtainMessage();
  • 调用Message的obtain方法,示例代码如下:
Handler mHandler = new Handler();  
Message msg = Message.obtain(mHandler);

Thread+Handler的示例代码如下:

public class ThreadActivity extends Activity implements OnClickListener {  

    private TextView tv_thread;  
    private long mBeginTime;  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_thread);  

        Button btn_single = (Button) findViewById(R.id.btn_single);  
        Button btn_multi = (Button) findViewById(R.id.btn_multi);  
        btn_single.setOnClickListener(this);  
        btn_multi.setOnClickListener(this);  

        tv_thread = (TextView) findViewById(R.id.tv_thread);  
    }  

    @Override  
    public void onClick(View v) {  
        if (v.getId() == R.id.btn_single) {  
            mBeginTime = System.currentTimeMillis();  
            new TicketThread().start();  
        } else if (v.getId() == R.id.btn_multi) {  
            mBeginTime = System.currentTimeMillis();  
            new Thread(mSeller).start();  
            new Thread(mSeller).start();  
            new Thread(mSeller).start();  
        }  
    }  

    private Handler mHandler = new Handler() {  
        @Override  
        public void handleMessage(Message msg) {  
            super.handleMessage(msg);  
            long cost = System.currentTimeMillis() - mBeginTime;  
            String desc = String.format("消息标识%d:%s耗时%d毫秒",   
                    msg.what, (String)(msg.obj), cost);  
            tv_thread.setText(desc);  
        }  
    };  

    private Runnable mSeller = new Runnable() {  
        private int ticketCount = 10000;  

        @Override  
        public void run() {  
            for (; ticketCount > 0; ticketCount--) {  
                String desc = String.format("%d 当前余票为%d张",   
                        System.currentTimeMillis(), ticketCount);  
                System.out.println(desc);  
            }  
            if (ticketCount <= 0) {  
                Message msg = Message.obtain(mHandler);  
                msg.obj = "多个窗口卖票";  
                mHandler.sendMessage(msg);  
            }  
        }  
    };  

    private class TicketThread extends Thread {  
        private int ticketCount = 10000;  

        @Override  
        public void run() {  
            for (; ticketCount>0; ticketCount--) {  
                String desc = String.format("%d %d 当前余票为%d张",  
                        System.currentTimeMillis(), getId(), ticketCount);  
                System.out.println(desc);  
            }  
            if (ticketCount <= 0) {  
                Message msg = mHandler.obtainMessage();  
                msg.obj = "单个窗口卖票";  
                mHandler.sendMessage(msg);  
            }  
        }  
    }  
}

原文发布于微信公众号 - 项勇(xiangy_life)

原文发表时间:2017-11-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏双十二技术哥

Android性能优化(十一)之正确的异步姿势

在前面的性能优化系列文章中,我曾多次说过:异步不是灵丹妙药,不正确的异步方式不仅不能较好的完成异步任务,反而会加剧卡顿。Android开发中我们使用异步来进行耗...

802
来自专栏SDNLAB

源码解读ODL与OpenFlow交换机建立过程

编者按:OpenDaylight两大技术特色:1.采用了OSGi框架;2.引入了SAL,而今天我们主要介绍服务抽象层(SAL)适配的南向协议之一OF协议模块。 ...

3284
来自专栏xx_Cc的学习总结专栏

iOS底层原理总结 - RunLoop

4457
来自专栏Java架构师进阶

精选11道Java技术面试题并有答案(包含部分阿里和华为的面试题)

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法去内,然后在堆区创建一个java.lang.Class对象,用来封装在...

763
来自专栏yang0range

Android的消息机制(一)——概述

从Android的开发角度来说,Handler是Android消息机制的上层接口,这使得开发过程中只需要和Handler交互即可。 Handler的使用过程比...

502
来自专栏KK的小酒馆

传统多线程开发Android开发高级进阶

之前的文章里写过了AsyncTask的一些坑,这次就不讲它了,使用传统的 Handler和Message来进行线程的使用,并且第一次添加了CallBack方式的...

992
来自专栏移动端开发

iOS 从实际出发理解多线程

前言 ----       多线程很多开发者多多少少相信也都有了解,以前有些东西理解的不是很透,慢慢的积累之后,这方面的东西也需要自己好好的总结一下。多线程从我...

1977
来自专栏IT技术精选文摘

ZooKeeper 分布式锁实现

932
来自专栏JMCui

Netty 系列一(核心组件和实例).

    早期的 Java API 只支持由本地系统套接字库提供所谓的阻塞函数来支持网络编程。由于是阻塞 I/O ,要管理多个并发客户端,需要为每个新的客户端So...

793
来自专栏everhad

笔记:BroadcastReceiver的运行过程

广播概述 广播用来在组件之间传递消息,可以是同进程或跨进程。 广播机制是基于发布订阅的事件驱动模型,使用上比Binder通信(跨进程接口回调)更低耦合、简单。 ...

1837

扫码关注云+社区