Android开发笔记(四十八)Thread类实现多线程

Thread概述

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


因为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张票;

线程类的代码如下:
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方法的源码,在上节《 Android开发笔记(四十七)Runnable接口实现多线程》中我们知道,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方法。


2、在分线程中直接调用的方法:
obtainMessage : 获取当前消息的对象
post、postDelayed、postAtTime : 详细说明见《 Android开发笔记(四十七)Runnable接口实现多线程
sendMessage : 向主线程发送消息
sendMessageDelayed : 延迟一段时间向主线程发送消息
sendMessageAtTime : 在指定时间向主线程发送消息
sendEmptyMessage : 向主线程发送空消息
sendEmptyMessageDelayed : 延迟一段时间向主线程发送空消息
sendEmptyMessageAtTime : 在指定时间向主线程发送空消息
removeMessages : 从消息队列中根据消息标识移除指定消息
hasMessages : 判断消息队列中是否存在指定消息标识的消息


总结一下Handler的消息流转流程:
1、Thread方式下运行:主线程创建Handler实例->启动分线程->分线程调用Handler实例的send系列方法->主线程分发消息dispatchMessage(消息中的Runnable对象为空)->主线程处理消息handleMessage。
2、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有两种方式:
1、调用Handler的obtainMessage方法,示例代码如下:
Handler mHandler = new Handler();
Message msg = mHandler.obtainMessage();

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


Thread+Handler的示例代码如下:
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

@SuppressLint("DefaultLocale")
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);
			}
		}
	}
}




点此查看Android开发笔记的完整目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值