Android修炼之道—多线程与同步

线程很神奇,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()方法中调用
Looper.prepare()创建的。 因此在线程产生之前,调用 getHandler() 将返回null.


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;
  }
}

使用synchronized关键字

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){
        //忽略
      }
    }
  }
}

使用volatile关键字

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同步、异步

一、关键字:
thread(线程)、thread-safe(线程安全)、intercurrent(并发的)
synchronized(同步的)、asynchronized(异步的)、
volatile(易变的)、atomic(原子的)、share(共享)


    要跨线程维护正确的可见性,只要在几个线程之间共享非 final 变量,就必须使用 synchronized(或 volatile)以确保一个线程可以看见另一个线程做的更改。
    为了在线程之间进行可靠的通信,也为了互斥访问,同步是必须的。这归因于java语言规范的内存模型,它规定了:一个线程所做的变化何时以及如何变成对其它线程可见。
    显然,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。
  也就是说互斥是两个线程之间不可以同时运行,他们会相互排斥,必须等待一个线程运行完毕,另一个才能运行,
而同步也是不能同时运行,但他是必须要安照某种次序来运行相应的线程(也是一种互斥)!

锁提供了两种主要特性:互斥(mutual exclusion)和可见性(visibility)。
    互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。
    可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,
    线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题
    为了防止多个线程并发对同一数据的修改,所以需要同步,否则会造成数据不一致(就是所谓的:线程安全。如java集合框架中 Hashtable和Vector是线程安全的。
    我们的大部分程序都不是线程安全的,因为没有进行同步,而且我们没有必要,因为大部分情况根本没有多线程环境)。


原子操作

     Java原子操作是指:不会被打断地的操作。
     那难道原子操作就可以真的达到线程安全同步效果了吗?实际上有一些原子操作不一定是线程安全的。那么,原子操作在什么情况下不是线程安全的呢?也许是这个原因导致的:java线程允许线程在自己的内存区保存变量的副本。允许线程使用本地的私有拷贝进行工作而非每次都使用主存的值是为了提高性能(本人愚见:虽然原子操作是线程安全的,可各线程在得到变量(读操作)后,就是各自玩弄自己的副本了,更新操作(写操作)因未写入主存中,导致其它线程不可见)。
     那该如何解决呢?因此需要通过java同步机制。
     在java中,32位或者更少位数的赋值是原子的。在一个32位的硬件平台上,除了double和long型的其它原始类型通常都是使用32位进行表示,而double和long通常使用64位表示。另外,对象引用使用本机指针实现,通常也是32位的。对这些32位的类型的操作是原子的。

     这些原始类型通常使用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消耗更多资源。







你可能感兴趣的:(多线程,同步异步)