Android 基础总结:( 十六)Android Thread

Thread的基础知识

什么是线程?

线程(threads, 台湾称 执行绪),也被称为轻量进程(lightweight processes)。计算机科学术语,指运行中的程序的调度单位。

线程是进程中的实体,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程不拥有系统资源,只有运行必须的一些数据结构;它与父进程的其它线程共享该进程所拥有的全部资源。线程可以创建和撤消线程,从而实现程序的并发执行。一般,线程具有就绪、阻塞和运行三种基本状态。

在多中央处理器的系统里,不同线程可以同时在不同的中央处理器上运行,甚至当它们属于同一个进程时也是如此。大多数支持多处理器的操作系统都提供编程接口来让进程可以控制自己的线程与各处理器之间的关联度(affinity)。

线程安全?

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。

线程安全问题都是由全局变量及静态变量引起的。

若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

线程池

java的线程池是通过HashMap获取当前的线程,保持线程同步。

线程池功能 

应用程序可以有多个线程,这些线程在休眠状态中需要耗费大量时间来等待事件发生。其他线程可能进入睡眠状态,并且仅定期被唤醒以轮循更改或更新状态信息,然后再次进入休眠状态。为了简化对这些线程的管理,.NET框架为每个进程提供了一个线程池,一个线程池有若干个等待操作状态,当一个等待操作完成时,线程池中的辅助线程会执行回调函数。线程池中的线程由系统管理,程序员不需要费力于线程管理,可以集中精力处理应用程序任务。

线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程.每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中.如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙.如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值.超过最大值的线程可以排队,但他们要等到其他线程完成后才启动

什么情况下不能使用线程池 

1、如果需要使一个任务具有特定优先级

2、如果具有可能会长时间运行(并因此阻塞其他任务)的任务

3、如果需要将线程放置到单线程单元中(线程池中的线程均处于多线程单元中)

4、如果需要永久标识来标识和控制线程,比如想使用专用线程来终止该线程,将其挂起或按名称发现它

System.ThreadingPool类实现了线程池,这是一个静态类,它提供了管理线程的一系列方法

Threading.QueueUserItem方法在线程池中创建一个线程池线程来执行指定方法(用委托WaitCallBack表示),并将该线程排入线程池的队列等待执行。

public static Boolean QueueUserWorkItem(WaitCallback wc,Object state);

线程同步的方式和机制

临界区、互斥区、事件、信号量四种方式

临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)的区别:

1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。

2、互斥量:采用互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享。

3、信号量:它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。

4、事 件: 通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作。

Android Thread学习

 单线程模型

当一个程序第一次启动时,Android会同时启动一个对应的主线程(Main Thread),主线程主要负责处理与UI相关的事件,如用户的按键事件,用户接触屏幕的事件以及屏幕绘图事件,并把相关的事件分发到对应的组件进行处理。所以主线程通常又被叫做UI线程。在开发Android应用时必须遵守单线程模型的原则: Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。

子线程更新UI

Android的UI是单线程(Single-threaded)的。为了避免拖住GUI,一些较费时的对象应该交给独立的线程去执行。如果幕后的线程来执行UI对象,Android就会发出错误讯息CalledFromWrongThreadException。以后遇到这样的异常抛出时就要知道怎么回事了!

 Message Queue

在单线程模型下,为了解决类似的问题,Android设计了一个Message Queue(消息队列), 线程间可以通过该Message Queue并结合Handler和Looper组件进行信息交换。下面将对它们进行分别介绍:

Message

Message消息,理解为线程间交流的信息,处理数据后台线程需要更新UI,则发送Message内含一些数据给UI线程。

Handler

Handler处理者,是Message的主要处理者,负责Message的发送,Message内容的执行处理。后台线程就是通过传进来的Handler对象引用来sendMessage(Message)。而使用Handler,需要implement 该类的 handleMessage(Message)方法,它是处理这些Message的操作内容,例如Update UI。通常需要子类化Handler来实现handleMessage方法。

Message Queue

Message Queue消息队列,用来存放通过Handler发布的消息,按照先进先出执行。

每个message queue都会有一个对应的Handler。Handler会向message queue通过两种方法发送消息:sendMessage或post。这两种消息都会插在message queue队尾并按先进先出执行。但通过这两种方法发送的消息执行的方式略有不同:通过sendMessage发送的是一个message对象,会被Handler的handleMessage()函数处理;而通过post方法发送的是一个runnable对象,则会自己执行。

Looper

Looper是每条线程里的Message Queue的管家。Android没有Global的Message Queue,而Android会自动替主线程(UI线程)建立Message Queue,但在子线程里并没有建立Message Queue。所以调用Looper.getMainLooper()得到的主线程的Looper不为NULL,但调用Looper.myLooper()得到当前线程的Looper就有可能为NULL。

对于子线程使用Looper,API Doc提供了正确的使用方法:

class LooperThread extends Thread {
	public Handler mHandler;
	public void run() {
		Looper.prepare(); // 创建本线程的Looper并创建一个MessageQueue
		mHandler = new Handler() {
			public void handleMessage(Message msg) {
				// process incoming messages here
			}
		};
		Looper.loop(); // 开始运行Looper,监听Message Queue
	}
}

这个Message机制的大概流程:

1. 在Looper.loop()方法运行开始后,循环地按照接收顺序取出Message Queue里面的非NULL的Message。

2. 一开始Message Queue里面的Message都是NULL的。当Handler.sendMessage(Message)到Message Queue,该函数里面设置了那个Message对象的target属性是当前的Handler对象。随后Looper取出了那个Message,则调用该Message的target指向的Hander的dispatchMessage函数对Message进行处理。

在dispatchMessage方法里,如何处理Message则由用户指定,三个判断,优先级从高到低:

1) Message里面的Callback,一个实现了Runnable接口的对象,其中run函数做处理工作;

2) Handler里面的mCallback指向的一个实现了Callback接口的对象,由其handleMessage进行处理;

3) 处理消息Handler对象对应的类继承并实现了其中handleMessage函数,通过这个实现的handleMessage函数处理消息。

由此可见,我们实现的handleMessage方法是优先级最低的!

3. Handler处理完该Message (update UI) 后,Looper则设置该Message为NULL,以便回收!

在网上有很多文章讲述主线程和其他子线程如何交互,传送信息,最终谁来执行处理信息之类的,个人理解是最简单的方法——判断Handler对象里面的Looper对象是属于哪条线程的,则由该线程来执行!

1. 当Handler对象的构造函数的参数为空,则为当前所在线程的Looper;

2. Looper.getMainLooper()得到的是主线程的Looper对象,Looper.myLooper()得到的是当前线程的Looper对象。

1.3 AsyncTask版:

public class ListProgressDemo extends ListActivity {
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.listprogress);
		((Button) findViewById(R.id.load_Handler))
				.setOnClickListener(new View.OnClickListener() {
					public void onClick(View view) {
						data = null;
						data = new ArrayList();
						adapter = null;
						showDialog(PROGRESS_DIALOG);
						new ProgressThread(handler, data).start();
					}
				});
	}
	@Override
	protected Dialog onCreateDialog(int id) {
		switch (id) {
		case PROGRESS_DIALOG:
			return ProgressDialog.show(this, "", "Loading. Please wait...",
					true);
		default:
			return null;
		}
	}
}
private class ProgressThread extends Thread {
	private Handler handler;
	private ArrayList data;
	public ProgressThread(Handler handler, ArrayList data) {
		this.handler = handler;
		this.data = data;
	}
	@Override
	public void run() {
		for (int i = 0; i < 8; i++) {
			data.add("ListItem"); // 后台数据处理
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				Message msg = handler.obtainMessage();
				Bundle b = new Bundle();
				b.putInt("state", STATE_ERROR);
				msg.setData(b);
				handler.sendMessage(msg);
			}
			Message msg = handler.obtainMessage();
			Bundle b = new Bundle();
			b.putInt("state", STATE_FINISH);
			msg.setData(b);
			handler.sendMessage(msg);
		}
	} // 此处甚至可以不需要设置Looper,因为Handler默认就使用当前线程的Looper
	private final Handler handler = new Handler(Looper.getMainLooper()) {
		public void handleMessage(Message msg) { // 处理Message,更新ListView
			int state = msg.getData().getInt("state");
			switch (state) {
			case STATE_FINISH:
				dismissDialog(PROGRESS_DIALOG);
				Toast.makeText(getApplicationContext(), "加载完成!",
						Toast.LENGTH_LONG).show();
				adapter = new ArrayAdapter(getApplicationContext(),
						android.R.layout.simple_list_item_1, data);
				setListAdapter(adapter);
				break;
			case STATE_ERROR:
				dismissDialog(PROGRESS_DIALOG);
				Toast.makeText(getApplicationContext(), "处理过程发生错误!",
						Toast.LENGTH_LONG).show();
				adapter = new ArrayAdapter(getApplicationContext(),
						android.R.layout.simple_list_item_1, data);
				setListAdapter(adapter);
				break;
			default:
			}
		}
	};
	private ArrayAdapter adapter;
	private ArrayList data;
	private static final int PROGRESS_DIALOG = 1;
	private static final int STATE_FINISH = 1;
	private static final int STATE_ERROR = -1;
}

Android另外提供了一个工具类:AsyncTask。它使得UI thread的使用变得异常简单。它使创建需要与用户界面交互的长时间运行的任务变得更简单,不需要借助线程和Handler即可实现。

1) 子类化AsyncTask。

2) 实现AsyncTask中定义的下面一个或几个方法。

onPreExecute() 开始执行前的准备工作;

doInBackground(Params...) 开始执行后台处理,可以调用publishProgress方法来更新实时的任务进度;

onProgressUpdate(Progress...) 在publishProgress方法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。

onPostExecute(Result) 执行完成后的操作,传送结果给UI 线程。

这4个方法都不能手动调用。而且除了doInBackground(Params...)方法,其余3个方法都是被UI线程所调用的,所以要求:

1) AsyncTask的实例必须在UI thread中创建;

2) AsyncTask.execute方法必须在UI thread中调用;

同时要注意:该task只能被执行一次,否则多次调用时将会出现异常。而且是不能手动停止的,这一点要注意,看是否符合你的需求!

在使用过程中,发现AsyncTask的构造函数的参数设置需要看明白:AsyncTask

Params对应doInBackground(Params...)的参数类型。而new AsyncTask().execute(Params... params),就是传进来的Params数据,你可以execute(data)来传送一个数据,或者execute(data1, data2, data3)这样多个数据。

Progress对应onProgressUpdate(Progress...)的参数类型;

Result对应onPostExecute(Result)的参数类型。

当以上的参数类型都不需要指明某个时,则使用Void,注意不是void。不明白的可以参考上面的例子,或者API Doc里面的例子。


创建Thread的两种方式

两种创建线程的方式:

第一种方式:使用Runnable接口创建线程。

第二种方式:直接继承Thread类创建对象使用Runnable接口创建线程。

1.可以将CPU,代码和数据分开,形成清晰的模型。

2.线程体run()方法所在的类可以从其它类中继承一些有用的属性和方法。

3.有利于保持程序的设计风格一致。

直接继承Thread类创建对象:

1.Thread子类无法再从其它类继承(java语言单继承)。

2.编写简单,run()方法的当前对象就是线程对象,可直接操作。

在实际应用中,几乎都采取用Thread创建线程的步骤 步骤为:

(1)定义线程类:

public class  线程类名 extends Thread {
    ……
    public void run(){
            …… //编写线程的代码
    }
}

(2)创建线程类对象

(3)启动线程: 线程类对象.start();

public static void main(String[] args) {
	线程类名 线程类对象 = new 线程类名();
	线程类对象.start();
}

用Runnanble 创建线程的步骤:

1.定义一个Runnable接口类。

2.在此接口类中定义一个对象作为参数run()方法。

3.在run()方法中定义线程的操作。

4.在其它类的方法中创建此Runnable接口类的实例对象,并以此实例对象作为参数创建线程类对象。

5.用start()类方法启动线程。

使用Runnable接口方法创建线程和启动线程。

//使用Runnable接口方法创建线程和启动线程。
public class MyThread implements Runnable {
	int count = 1, number;
	public MyThread(int num) {
		number = num;
		System.out.println("创建线程" + number);
	}
	public void run() {
		while (true) {
			System.out.println("线程" + number + ":计数" + count);
			if (++count == 3)
				return;
		}
	}
	public static void main(String args[]) {
		for (int i = 0; i < 2; i++) {
			new Thread(new MyThread(i + 1)).start(); // 启动线程
		}
	} 
}


Android 多线程处理之多线程用法大集合

刚运动完好困,洗洗睡了




你可能感兴趣的:(Android,基础)