线程很神奇,Android可不光是是main主线程和一些其他线程,还有许多隐藏的boss在幕后操纵着一切。
例如 android3.1的Galaxy Tab上,使用Eclipse 的 DDMS视图你可以看到如下线程,竟然有8个之多:
MAIN,Heap Workder(执行finalize函数和引用对象清理),
GC ( Garbage Collector,垃圾收集),Signal Catcher ( 捕捉 Linux 信号进行处理)
JDWP ( Java Debug Wire Protocol, 调试协议服务),Compiler (JIT compiler, 即使编译器)
Binder Thread #1 (Binder 通讯),Binder Thread #2
不管是什么 Thread 都有优先级;
Thread类定义了三个常量,
MIN_PRIORITY(1) NORM_PRIORITY(5)—默认 MAX_PRIORITY(10)
thread.setPriority(Thread.MAX_PRIORITY);//最高优先级,比UI线程还高
如果优先级小于1或者大于10,就会抛出llegalArgumentException。
还有另一种基于Linux的优先级:android.os包里的Process.setThreadPriority API,其定义了8个优先级,
注意,Linux的优先级是从 -20(最高)到 19(最低)。
AsyncTask
应用处理顺序通常是:
1.在UI线程收到事件;
2.在非UI线程中处理相应事件;
3.UI根据处理结果进行刷新;
而AsyncTask类简化了这个通用的模型,对用户隐藏了Thread、Runnable及其他对象;
下列AsyncTask的 protected 方法 会从UI线程调用:
onPreExecute()
onProgressUpdate(Progress... values)
onPostExecute(Result result)
onCancelled()
onCancelled(Result result)
onProgressUpdate()要在 doInBackground()发发中紧接着publishProgress()之后调用,典型的例子就是后台下载文件时刷新前台进度条:
AysyncTask<String, Object, Void> task = new AsyncTask<String, Object, Void>(){ private ByteArrayBuffer downloadFile(String urlString, byte[] buffer){ try{ URL url = new URL(urlString); URLConnection connection = url.openConnection(); InputStream is = cnnection.getInputStream(); ByteArrayBuffer baf = new ByteArrayBuffer(640 *1024); int len; while((len = is.read(buffer)) != -1){ baf.append(buffer, 0, len); } return baf; }catch(MalformedURLException e){ return null; }catch(IOException e){ return null; } } @Override protected Void doInBackground(String... params){ if(params != null && params.length >0 ){ byte[] buffer = new byte[4* 1024];//试试不同的大小,1会严重降低性能 for(String url : params){ long time = System.currentTimeMillis(); ByteArrayBuffer baf = downloadFile(url, buffer); time = System.currentTimeMillis - time; publishProgress(url, baf, time); } }else{ publishProgress(null, null); } return null; //即使不关心任何结果,也要返回东西 } @Override protected void onProgressUpdate(Object... values){ String url = (String) values[0]; ByteArrayBuffer buffer = (ByteArrayBuffer) values[1]; if(buffer!=null){ long time = (Long) values[2]; }else{ } } };
顺便说的是,doInBackground(),在Android 1.6之前,任务是串行执行的,只需一个后台线程。从1.6开始,线程池取代了单个的后台线程,线程池允许并行,提成性能。
而在Honneycomb之后,又恢复到默认情况下只有一个后台线程的模式。在Honeycomb中添加了新的APIexecuteOnExecutor(),可以使用AsyncTask.SERIAL_EXECUTOR 串行执行或AsyncTask.THREAD_POOL_EXECUTOR并行执行。
Handler 和 Looper
1.Handler
public class MyThread extends Thread{ private static final String TAG = "MyThread"; private Handler mHandler; public MyThread(String name){ super(name); } public Handler getHandler(){ return mHandler; } @Override public void run(){ Looper.prepare(); //把Looper绑定到此线程 mHandler = new Handler(){ @Override public void handleMessage(Message msg){ switch(msg.what){ //处理消息 } } };//Handler 绑定到此线程 Looper.loop(); //不要忘了调用loop()去启动消息循环 // 只有循环停止了 (比如调用了 Looper.quit()), Looper才会返回 } }Handler对象在 run()方法中创建,因为它需要被绑定到指定的Looper, 这个Looper就是在 run()方法中调用
2.Looper
Android提供了HandlerThread类来帮助更方便的使用Looper的线程,也利于避免潜在的竞争状态。
public class MyHandlerThread extends HandlerThread{ private Handler mHandler; public MyHandlerThread(String name){ super(name); } public Handler gethHandler(){ return mHandler; } @Override public void start(){ super.start(); Looper looper = getLooper(); mHandler = new Handler(looper){ @Override public void handlerMessage(Message msg){ switch (msg.what){ //这里处理消息 } } }; } }这里Handler对象是在start()方法而不是在run()方法创建的,只要在start()之后就可以用getHandler()获取。
By the way, java.util.concurrent包中定义了许多用于并发的类。
同步、易变、内存
如果想在多个线程之间共享对象,可以使用synchronized关键字确保数据访问是线程安全的。
public class MyClass{ private int mValue; public MyClass(int n){ mValue = n; } public synchronized void add(int a){ mValue +=a; } public synchronized void multiplyAndAdd(int m, int a){ mValue = mValue*m + a; } }
public class MyClass{ private static int mValue = 0; public static synchronized void setValue(int n){ mValue = n; } public static synchronized int getValue(){ return mValue; } public static void loop(){ int value; while((value = getValue()) != 100){ try{ Thread.sleep(1000); }catch(Exception e){ //忽略 } } } }
public class MyClass{ private static volatile int mValue=0; //添加volatile,删除同步关键字 public static void setValue(int n){ mValue = n; //如果是mValue += n语句,因为不是原子操作,还得使用 synchronized } public static void loop(){ while(mValue != 100){ try{ Thread.sleep(1000); }catch(Exception e){ } } } }volatile关键字只能解决变量声明是原子的那些并发问题,如果变量不是原子的,就必须使用synchronized关键字。
原子操作
Java原子操作是指:不会被打断地的操作。这些原始类型通常使用32位或者64位表示,这又引入了另一个小小的神话:原始类型的大小是由语言保证的。这是不对的。java语言保证的是原始类型的表数范围而非JVM中的存储大小。因此,int型总是有相同的表数范围。在一个JVM上可能使用32位实现,而在另一个JVM上可能是64位的。在此再次强调:在所有平台上被保证的是表数范围,32位以及更小的值的操作是原子的。
java同步机制的4中实现:为了解决多线程中对同一变量的访问冲突
1.ThreadLocal 2.synchronized 3.wait()与notify() 4.volatile
ThreadLocal
ThreadLocal 保证不同线程拥有不同实例,相同线程一定拥有相同的实例,即为每一个使用该变量的线程提供一个该变量值的副本,每一个线程都可以独立改变自己的副本,而不是与其它线程的副本冲突。
优势:提供了线程安全的共享对象
与其它同步机制的区别:同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信;而 ThreadLocal 是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源,这样当然不需要多个线程进行同步了。
volatile
volatile 修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。
优势:这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
缘由:Java 语言规范中指出,为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。而 volatile 关键字就是提示 VM :对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。
使用技巧:在两个或者更多的线程访问的成员变量上使用 volatile 。当要访问的变量已在 synchronized 代码块中,或者为常量时,不必使用。
线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B。只在某些动作时才进行A和B的同步,因此存在A和B不一致的情况。volatile就是用来避免这种情况的。 volatile告诉jvm,它所修饰的变量不保留拷贝,直接访问主内存中的(读操作多时使用较好;线程间需要通信,本条做不到)
volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。
您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
(1)对变量的写操作不依赖于当前值;
(2)该变量没有包含在具有其他变量的不变式中。
Synchronized既保证了多线程的并发有序性(即同步),又保证了多线程的内存可见性。
比较一下volatile和synchronized的不同是最容易解释清楚的。volatile是变量修饰符,而synchronized则作用于一段代码或方法。
既然volatile关键字已经实现了线程间数据同步,又要synchronized干什么呢?呵呵,它们之间有两点不同。首先,synchronized获得并释放监视器——如果两个线程使用了同一个对象锁,监视器能强制保证代码块同时只被一个线程所执行——这是众所周知的事实。但是,synchronized也同步内存:事实上,synchronized在“主”内存区域同步整个线程的内存。
因此,执行如下几步:
1. 线程请求获得监视this对象的对象锁(假设未被锁,否则线程等待直到锁释放)
2. 线程内存的数据被消除,从“主”内存区域中读入(Java虚拟机能优化此步)
3. 代码块被执行
4. 对于变量的任何改变现在可以安全地写到“主”内存区域中
5. 线程释放监视this对象的对象锁
因此volatile只是在线程内存和“主”内存间同步某个变量的值,而synchronized通过锁定和解锁某个监视器同步所有变量的值。显然synchronized要比volatile消耗更多资源。