线程学习.docx
《线程学习.docx》由会员分享,可在线阅读,更多相关《线程学习.docx(34页珍藏版)》请在冰豆网上搜索。
线程学习
线程学习
一.线程
一般来说我们把正在计算机中运行的程序叫做进程,而线程就是某个单一顺序的控制流。
从os的角度来讲,进程是资源分配的单位,线程是调度的基本单位。
1.线程类Thread
1.1Thread类的继承关系
Thread类继承自Object类,Android中的线程既可以通过继承Runable接口来实现,也可以通过扩展Thread类实现。
类继承关系如下:
Java.lang.Object
Java.lang.Thread
直接子类:
HandlerThread
1.2Thread类的几个常量
int
MAX_PRIORITY
最高优先级
int
MIN_PRIORITY
最低优先级
int
NORM_PRIORITY
一般(默认)的优先级
1.3Thread类的构造方法
Thread()
创建一个不包含Runable对象的新线程,新线程名
Thread(Runnablerunnable)
创建一个包含Runable对象的新线程,新线程名
Thread(Runnablerunnable,StringthreadName)
创建一个包含Runable对象的新线程,已有线程名
Thread(StringthreadName)
创建一个不包含Runable对象但有名称的线程
Thread(ThreadGroupgroup,Runnablerunnable)
创建一个包含Runable对象的新线程,新线程名
Thread(ThreadGroupgroup,Runnablerunnable,StringthreadName)
创建一个包含Runable对象,给定了线程名的新线程,该线程属于ThreadGroup并作为一个参数传递
Thread(ThreadGroupgroup,StringthreadName)
创建一个不包含Runable对象但给定了线程名的线程,线程属于ThreadGroup并作为一个参数传递
Thread(ThreadGroupgroup,Runnablerunnable,StringthreadName,longstackSize)
创建一个包含Runable对象,给定了线程名的线程,该线程属于ThreadGroup并作为一个参数传递
1.4Thread的公共方法
staticint
activeCount()
返回当前活动线程群和子群中活动线程的个数
finalvoid
checkAccess()
不做任何事
int
countStackFrames()
这个方法是过时了的,它的执行结果是不明确的,它取决于线程是否暂停
staticThread
currentThread()
返回线程的调用,也就是当前的线程
void
destroy()
该方法已经过时不用了
staticvoid
dumpStack()
为这个线程打印到表示当前堆栈的标准错误流文本中
staticint
enumerate(Thread[]threads)
复制一个具有所有在一个线程组和分组的,并作为接收器的线程数组到一个线程数组中去,并作为一个参数传递
staticMap
getAllStackTraces()
返回一个具有所有活动的线程的map到他们的堆栈中去
ClassLoader
getContextClassLoader()
返回这个线程的所在的类
staticThread.UncaughtExceptionHandler
getDefaultUncaughtExceptionHandler()
返回默认的异常处理以及线程终止时捕获到的异常
long
getId()
返回线程的ID
finalString
getName()
返回线程的名称
finalint
getPriority()
返回线程的优先级
StackTraceElement[]
getStackTrace()
返回一个表示当前线程的堆栈的StackTraceElement数组
Thread.State
getState()
返回线程的状态
finalThreadGroup
getThreadGroup()
返回线程所属的线程组
Thread.UncaughtExceptionHandler
getUncaughtExceptionHandler()
返回线程没有捕获到的异常
staticboolean
holdsLock(Objectobject)
指示当前线程是否在指定的对象上上锁
void
interrupt()
线程中断请求
staticboolean
interrupted()
返回一个boolean值来指示当前线程是否有一个悬而未决的中断请求true表示有,false表示没有
finalboolean
isAlive()
返回true表示接收器已经启动并在运行代码
finalboolean
isDaemon()
返回一个boolean值,指示接收器是否是一个守护线程true表示是,false表示否。
一个守护线程只能运行在没有其他的守护线程的情况下
boolean
isInterrupted()
返回一个boolean值,指示是否有悬而未决的中断请求,true是,false否
finalvoid
join()
阻塞当前线程(Thread.currentThread()),直到接收器执行完并且死了
finalvoid
join(longmillis,intnanos)
阻塞当前线程(Thread.currentThread())直到接收器执行完并且死了或者超过了指定的时间
finalvoid
join(longmillis)
阻塞当前线程(Thread.currentThread())直到接收器执行完并且死了或者超过了指定的时间
finalvoid
resume()
此方法过时
void
run()
调用接收器拥有的Runable对象的run()方法
void
setContextClassLoader(ClassLoadercl)
为接收器设置上下文
finalvoid
setDaemon(booleanisDaemon)
设置接收器是否是守护线程
staticvoid
setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandlerhandler)
设置默认的未捕获到的异常处理
finalvoid
setName(StringthreadName)
设置线程的名字
finalvoid
setPriority(intpriority)
设置线程的优先级
void
setUncaughtExceptionHandler(Thread.UncaughtExceptionHandlerhandler)
设置异常捕获处理
staticvoid
sleep(longmillis,intnanos)
使线程在睡眠的时间间隔(毫秒和纳秒)后发送此消息
staticvoid
sleep(longtime)
使线程在睡眠的时间间隔(毫秒)后发送此消息
synchronizedvoid
start()
启动线程
synchronizedfinalvoid
stop(Throwablethrowable)
这个方法已经过时,使用它不安全
finalvoid
stop()这个方法过时
finalvoid
suspend()该方法已经过时
String
toString()
返回一个字符串,该字符串简明的描述类线程
staticvoid
yield()使调用的线程将执行时间给与另一个已经准备运行的线程
1.5Thread简单使用实例
我们这里模拟一个文件的加载,用进度条来显示加载进度,用到线程来跟新进度条的值,其中mcount的初始值为0:
dialog.setTitle("提示");dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMessage("正在加载...");
//设置进度条是否不明确
dialog.setIndeterminate(false);
dialog.show();
newThread(){
publicvoidrun(){
try{
while(mcount<=100){
mcount++;
dialog.setProgress(mcount);
Thread.sleep(100);
}
dialog.dismiss();
}catch(Exceptione){
dialog.dismiss();
}
}
}.start();
运行效果:
2.daemon守护线程
所谓守护线程就是运行在程序后台的线程,守护线程的唯一作用是为其他线程提供服务,比如垃圾回收线程就是一个守护线程。
程序的主线程不会是守护线程。
如果虚拟机中只有守护线程,则虚拟机会退出。
也就是说某个时候,虚拟机中可能有多个线程在运行,当所有的非守护线程结束的时候,虚拟机就会退出。
所以在Thread类的方法中的setDaemon(true)方法就是告诉虚拟机不用等待这个线程结束。
而isDawmon方法是判断一个线程是否是守护线程。
比如现在我们这里有一个打印数据的线程:
publicvoidrun(){
try{
for(inti=0;i<10;i++){
Thread.sleep(1000);
System.out.println("打印"+i+"次");
}
}catch(Exceptione){
e.printStackTrace();
}
}
publicstaticvoidmain(String[]args){
ThreadDemo_DaemonActivityt2=newThreadDemo_DaemonActivity();
t2.setDaemon(true);//设置t为守护线程true不循环false无限循环
t2.start();
点击运行之后我们可以看到控制台并没有数据打印出来,这是因为我们设置了setDaemon(true),这个时候因为只有这个守护线程,所以虚拟机不执行如何事物而退出。
控制台:
但如果我们将setDaemon设置为false时,这个线程就是一个普通的线程,于是控制台就可以看到打印出来的信息了:
我们可以在上面的例子中增加一个非守护线程来看一下会是什么样的一种情况:
publicvoidrun(){
try{
for(inti=0;i<5;i++){
Thread.sleep(1000);
System.out.println("前台线程打印"+i+"次");
}
}catch(Exceptione){
e.printStackTrace();
}
}
这个时候我们看到控制台的输出情况:
守护线程明显还没有执行完,但是由于唯一的用户线程已经执行完了,虚拟机也就退出了。
注意:
setDaemon(true)这个方法必须在线程启动之前调用,如果在线程已经在运行的情况下调用会产生异常。
当在一个守护线程中产生了其他的线程,那么不用设置Daemon属性,都将是守护线程。
3.线程、服务之间的区别
首先我门应该明确的是服务它并不是线程,服务可以在线程中工作。
服务和线程是两个不同的概念。
线程的运行是独立与Activity的,也就是说如果当你finish掉一个Activity后,如果没有主动的停止线程或者线程里面的run还没执行完的话,线程是一直在跑的。
而且不能在不同的Activity中对同一个线程进行控制。
例如:
我们有一个线程需要在不停的隔一段时间就去连接服务器做某种同步的话,该线程就要求在Activity还没有启动的时候就应该在运行,这个时候当我们启动了Activity后是不能控制之前创建的那个线程的。
因此,这里我们可以创建并启动一个Service,在Service里面创建、运行并控制该线程,因为任何一个Activity都可以控制同一个Service。
我们可以把服务想象成一种信息服务,我们可以在任何有Contex的地方调用Context.startService等来实现控制。
然后线程是做不到的。
说了这么多,那什么时候使用线程,什么时候使用服务呢?
1.在应用中,如果是长时间在后台运行,而且不需要交互的情况下,使用服务
2.同样是在后台运行,而且不需要交互的情况下,如果只是为了完成某个任务,之后就不需要运行,而且可能是多个任务,使用线程
3.如果任务占用CPU时间多,资源大的情况下,使用线程
4.线程的使用
4.1单线程模型
当第一次启动一个Android程序时,Android会自动创建一个称为main的主线程,也称为UI线程,它主要负责把相应的事件分派到相应的控件。
关于线程我们在使用前应该特别注意两条准则:
不要阻塞UI线程,以及一切UI操作都应该在UI线程中执行。
在没有考虑到单线程的影响的情况下使用单线程会引起按Android应用程序的性能低下,因为所有的程序都要在一个线程中执行,如果要执行一些耗时的操作的时候,比如访问网络,就会阻塞整个用户界面。
因此,我们就会想到新建一个线程来执行这些操作。
在单线程模式下,为了解决类似的问题,Android提供了以下几个主要方法。
4.2AsyncTask
要避免在UI线程中执行这些耗时的操作,我们可能就会想到在后台线程或者工作线程在中执行这些任务。
比如我们现在要实现单机某个按钮显示一张网络图片:
publicvoidonClick(Viewv){
newThread(newRunnable(){
publicvoidrun(){
Bitmapb=loadImageFromNetwork();
mImageView.setImageBitmap(b);
}
}).start();
}
这看起来是已经解决了UI线程的阻塞问题,然而我们忘记了一点,那就是AndroidUI操作并不是线程安全的并且这些操作必须在UI线程中执行。
不过Android已经为我们提供了几种在其他线程中访问UI线程的方法:
∙Activity.runOnUiThread(Runnable)
∙View.post(Runnable)
∙View.postDelayed(Runnable,long)
∙Hanlder
因此上面的代码我们就可以修改为:
publicvoidonClick(Viewv){
newThread(newRunnable(){
publicvoidrun(){
finalBitmapb=loadImageFromNetwork();
mImageView.post(newRunnable(){
mImageView.setImageBitmap(b);
});
}
}).start();
}
这样看起来也许会有点复杂,一个Runable下面又有一个Runable。
当使用一些更复杂的操作并且要更频繁的跟新UI的时候会使得代码看起来更复杂。
其实Android中还提供了一个用于管理线程的类AsyncTask,它能使得创建需要与用户界面长时间运行交互的任务变得简单。
上面的代码再次修改如下:
publicvoidonClick(Viewv){
newDownloadImageTask().execute(");
}
privateclassDownloadImageTaskextendsAsyncTask{
protectedBitmapdoInBackground(String...urls){
returnloadImageFormNetwork(urls[0]);
}
protectedvoidonPostExecute(Bitmapresult){
mImageView.setImageBitmap(result);
}
}
需要注意的是,AsyncTask类的实例必须在UI线程中创建并且只能被使用一次。
∙doInBackground()方法会自动地在工作者线程中执行
∙onPreExecute()、onPostExecute()和onProgressUpdate()方法会在UI线程中被调用
∙doInBackground()方法的返回值会被传递给onPostExecute()方法
∙在doInBackground()方法中你可以调用publishProgress()方法,每一次调用都会使UI线程执行一次onProgressUpdate()方法
∙你可以在任何时候任何线程中取消这个任务
∙AsyncTask定义了三种泛型类型Params,Progress和Result。
*Params启动任务执行的输入参数,比如HTTP请求的URL。
*Progress后台任务执行的百分比。
*Result后台执行任务最终返回的结果,比如String。
运行中这几个方法的调用顺序:
我们在开发者文档中可以更详细的了解到AsyncTask的这几个方法:
abstractResult
doInBackground(Params...params)
重写这个方法在后台线程中进行各种计算
void
onCancelled(Resultresult)
当cancle(boolean)方法被调用了,并且doInBackground方法以已经完成时,此方法在UI线程中执行。
void
onCancelled()
应用最好覆盖onCancelled(Object)方法
void
onPostExecute(Resultresult)
在执行了doInBackground(Params...).后在UI线程中执行此方法
void
onPreExecute()
在执行了doInBackground(Params...).前UI线程中执行此方法
void
onProgressUpdate(Progress...values)
在执行publishProgress(Progress...)后,在UI线程中执行此方法
finalvoid
publishProgress(Progress...values)
当后台计算仍在运行的时候调用此方法可以将doInBackground(Params...)的结果更新到UI线程
下面我们可以来看一个具体的实例:
从网络上下载图片:
classmyAsyncTaskextendsAsyncTask{
protectedBitmapdoInBackground(String...arg0){
//这个方法是Task中第二个被调用的方法,arg0必须要与AsyncTask中第一个参数的类型对应。
URLurl=null;
try{
url=newURL(arg0[0]);
//取得传进来的URL
}catch(MalformedURLExceptione){
e.printStackTrace();
returnnull;
}
//连接到指定的URL,获取数据
try{
HttpURLConnectionconnect=(HttpURLConnection)url.openConnection();
connect.setDoInput(true);
//如果打算使用URL连接进行输入,则将DoInput标志设置为true;如果不打算使用,则设置为false。
默认值为true。
//方便后面使用getInputStream
connect.connect();
//获取数据
InputStreaminput=connect.getInputStream();
intlength=(int)connect.getContentLength();
totalSize=length;
if(length!
=-1){
byte[]imgData=newbyte[length];
byte[]buffer=newbyte[512];
intreadLen=0;
intdestPos=0;
while((readLen=input.read(buffer))>0){
System.arraycopy(buffer,0,imgData,destPos,readLen);
destPos+=readLen;
size=destPos;
publishProgress((int)((size/(float)length)*100));
Thread.sleep(100);
}
bitmap=BitmapFactory.decodeByteArray(imgData,0,
imgData.length);
}
}catch(IOExceptione){
e.printStackTrace();
returnnull;
}catch(InterruptedExceptione){
e.printStackTrace();
returnnull;
}
returnbitmap;
}
protectedvoidonPostExecute