Android中线程Thread的使用

来源:互联网 发布:最新网络电话软件下载 编辑:程序博客网 时间:2024/05/21 06:49

定义

主线程是指进程拥有的线程,或叫UI线程,Java中默认情况下一个进程只有一个线程,这个线程就是主线程。主线程不能执行耗时的任务,所以在一些耗时处理时,就得使用子线程。Android沿用了Java的线程模型,从Android3.0开始系统要求网络访问也必须在子线程中进行,否则网络访问将会失败并抛出NetworkOnMainThreadException异常。在Android里如果主线程被执行耗时任务导致阻塞后还会造成ANR的发生。

线程五个基本状态

新建状态(New)

线程对象创建后就会进入了新建状态,如:Thread t = new MyThread();

就绪状态(Runnable)

当调用线程对象的start()方法后,如:t.start();,线程进入就绪状态。此状态的线程只是明确做好了准备,随时等待CPU调度执行,并未开始执行

运行状态(Running)

当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。

阻塞状态(Blocked)

处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,阻塞状态后要再到运行状态,必须是先经过就绪状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

2.同步阻塞: 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

3.其他阻塞: 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程又再重新转入就绪状态。

死亡状态(Dead)

线程执行完了或者因异常退出了run()方法,该线程结束生命周期

多线程的使用

Java中主要提供两种方式实现线程,分别为继承java.lang.Thread类与实现java.lang.Runnable接口。

继承Thread类

// 新建MyThread类并继承Thread类class MyThread extends Thread {       @Override    public voidrun() {        for(int i = 0; i < 999; i++) {            //TODO...        }    }}// 调用使用线程Thread myThread = new MyThread();              // 新建状态myThread.start();                              // 就绪状态

实现Runable接口

// 新建MyRunnable类并实现Runnable接口class MyRunnable implements Runnable {    @Override    public voidrun() {        for(int i = 0; i < 100; i++) {            //TODO...        }    }}// 调用使用线程Runnable myRunnable = new MyRunnable();         // 创建Runable类对象Thread thread = new Thread(myRunnable);         // 将Runnable对象作为Thread target创建新的线程thread.start();                                 // 就绪状态

同时使用Thread的被继承类和Runnable接口

Runnable myRunnable = new MyRunnable();Thread myThread = new MyThread(myRunnable);myThread.start();
这种方式也能创建一个新的线程。但是,此线程执行到底是MyRunnable接口中的run方法还是MyThread类中的run方法呢?答案是:MyThread类中的run()方法。因为Thread类本身也实现了Runnable接口,而重定Runnable的run方法的。Thread源码大概是这样:

public class Thread implements Runnable {    ……    @Override    public voidrun() {        if(target != null) {           target.run();        }    }    ……}

从源码可以看到,当执行到Thread类中的run()方法时,会首先判断target是否存在,存在则执行target中的run()方法,也就是实现了Runnable接口并重写了run()方法的类中的run()方法。但是上述情况,由于多态的存在,即MyThread中run()方法重写了父类的run()方法,所以根本就没有执行到Thread类中的run()方法,而是直接先执行了MyThread类中的run()方法。

操作线程的方法

线程的休眠(sleep)

使用sleep()方法时,必须加入InterruptedException异常捕捉,使用sleep()方法的会使线程进入睡觉状态,也就是上面提到的阻塞状态,接收的参数是毫秒,在设定的毫秒时间内醒来后,它并不能保证能进入运行状态,只能保证它进入就绪状态。使用如下:

……try {   Thread.sleep(2000);} catch(InterrupedException e) {   e.printStackTrace();}……

线程的加入(join)

假如存在线程A和线程B,线程A正在运行状态,而现在要求线程A先暂停,让线程B先执行完毕,然后再继续执行线程A,此时就应使用join()方法来完成。这就好比您正在看电视,却突然有人上门送快递,那么您就先开门签收快递后再继续看电视一样。使用如下:

……try {   threadA.sleep(100);   threadB.join();} catch(Exception e) {   e.printStackTrace();}……

线程的中断(interrup)

现在已经不提倡使用stop方法来停止线程,因为那是不安全的,最好的停止线程方式是定义一个类成员变量,然后在循环过程中去判断此变量是否为假,便退出当前循环。但如果线程正在sleep()或wait()中,便无法使用类成员变量来判断,此时可以使用interrup()方法离开run()方法,同时结束线程,但程序会抛出InterruptedException异常。使用如下:

// 线程类class MyThread extends Thread {    volatileboolean mStop = false;         //volatile 用来修饰被不同线程访问和    @Override    public voidrun() {        while(!mStop) {            try{               Thread.sleep(1000);               // TODO...            }catch (InterruptedException e) {               e.printStackTrace();            }        }    }}// 启动线程MyThread myThread = new MyThread();myThread.start();// 停止线程myThread.mStop = true;myThread.interrupt();

线程礼让(yield)

sleep()方法使当前运行中的线程睡眠一段时间,这段时间的长短是由参数设定的,yield()方法使当前线程让出CPU占有权,但让出的时间是不可设定的,而且也不会释放锁标志。

使用yield()方法可使同样优先级的线程有进入可执行状态的机会,如果没有相同优先级的线程或者被礼让线程放弃执行权时,原线程会再度回到就绪状态。对于支持多任务的操作系统来说,不需要调用yeild()方法,因为操作系统会为线程自动分配CPU时间片来执行。

线程的优先级

Thrread类中包含的成员变量代表了线程的某些优先级,比如Thread.MIN_PRIORITY(常数1)、Thread.MAX_PRIORITY(常数2)、Thread.NORM_PRIORITY(常数5)。其中每个线程的优先级都在Thread.MIN_PRIORITY ~ Thread.MAX_PRIORITY之间,默认是Thread.NORM_PRIORITY。

线程的优先级可使用setPriority()方法调整,如果使用该方法设置的优先级不在1~10之内,将产生IllegalArgumentException异常。

线程同步

单一程程序中,每次只能做一件事情,它是串行执行的;但多线程程序是可以异步并发处理同一件事情,这样就会发生两个线程抢占资源的问题,使如一个线程定在写数据,而另一个线程刚才在读数据,那么就会产生很多未知的错误情况出现,这也是多线程最危险的事情。

同步块

Java中提供了同步机制,可以有效地防止资源冲突。使用关键字synchronized。如:

public class Person {       privateString mName;    public voidsetName(String name) {        synchronized(mName){           this.mName = name;        }    }}

这里要锁定的是对象类成员变量mName,而且只有在同一个Person对象才行效,否则是互不影响的。比如Person A = new Person()和Person B = newPerson()中,A和B之间是不受synchronized制约的。

又如:

public class Person{    privateString mName;    public voidsetName(String name) {       synchronized(Person.class) {           this.mName = name;        }    }}

上面这锁是对类级别的,锁定的是Person.class,不管是否同一对象,都会实行同步机制。

同步方法

同步方法跟同步块原理一样,只是把共享资源的操作放置在方法中,语法如:

synchronized void f() {    // TODO...}

线程间的通信

调用wait()方法和sleep()方法都可以使线程从运行状态进入就绪状态,而sleep()方法的线程是不释放锁的,wait()方法只能在同步块或同步方法中使用,wait()方法的线程是释放锁的。wait()可带参数或不带参数,其中wait(time)的情况跟sleep(time)一样,在一定时间内暂停;而如果wait()无参数情况下是永久无限地等待下去,需要使用notify()或notifyAll()方法才能唤醒。

例如:

synchronized(obj) {    while(mIsWait) {        obj.wait();    }    ...}

上面代码,假如存在线程A,它获得了obj锁后,遇到mIsWait为true,则执行了obj.wait();语句,使线程A处理wait状态并放弃对象锁。随后另一线程B,执行下面代码:

synchronized(obj) {    mIsWait= false;    obj.notify();    ...}

这时当线程B完全执行了整个同步块的代码后,线程A会被唤醒,并且重新获得了obj锁对象。如果存在线程A1、线程A2、线程A3多个线程都在obj.wait()中,则线程B调用obj.notify()只能唤醒线程A1、线程A2、线程A3中的其中一个,具体哪一个由JVM决定。如果线程B执行的是obj.notifyAll()则能全部唤醒线程A1、线程A2、线程A3,但由于锁的原因,因此线程A1、线程A2、线程A3只有一个线程有机会获得锁继续执行。

异步转同步CountDownLatch

CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。简单地说就是有A和B两个方法都是异步执行,由于某些需求,想让A方法执行完之后再执行B方法。例如:

CountDownLatch mCountDownLatch;private void fun() {   mCountDownLatch = new CountDownLatch(1);    // 这是一个异步操作的方法   asynchronousFun();    try {        // 这里作等待,mCountDownLatch.countDown()后       mCountDownLatch.await();    } catch(InterruptedException e) {       e.printStackTrace();    }    // 这个方法被在asynchronousFun()执行完后再执行    waitFun();}private void asynchronousFun() {    newThread(new Runnable() {       @Override        publicvoid run() {            try{               Thread.sleep(5000);            }catch (InterruptedException e) {               e.printStackTrace();            }           mCountDownLatch.countDown();        }    }).start();}private void waitFun() {    // TODO...}

上例中CountDownLatch对象初始化时传入数字1,每使用一次countDown()方法计数减1,当数字减到0时,await()方法后的代码将可以执行,未到0之前将一直阻塞等待。

Atomic系列对象

在多线程下,像i++、++i之类的操作都是不安全的,在使用时不可避免的会用到synchronized关键字进行锁定对象,而Atomic系统对象则通过一种线程安全的操作方法,是以原子方式更新对应值,从而可以不使用synchronized关键字情况下也能达到预期安全效果,而且效率还有可能比synchronized略高,Atomic系列对象有:AtomicBoolean、AtomicInteger、AtomicLong,等。示例:

private AtomicBoolean exists = newAtomicBoolean(false);public void run() {   exists.set(false);    ……    if(exists.compareAndSet(false, true)) {        //TODO...    } else {        //TODO...    }}

compareAndSet方法第一个参数是期望判断返回的值,上面就是如果为false时方法就返回true;第二个参数是设置的新值,就是在判断完后立即设置的一个新值。

AtomicInteger的使用也很简单,示例:

AtomicInteger number = new AtomicInteger(1);int result = number.incrementAndGet();
incrementAndGet()方法好比++i;所以上例第二行中,result和number的值都是2;

CopyOnWriteArrayList

CopyOnWriteArrayList是ArrayList 的一个线程安全的变体,其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的。

这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法更有效。在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时,它也很有用。“快照”风格的迭代器方法在创建迭代器时使用了对数组状态的引用。此数组在迭代器的生存期内不会更改,因此不可能发生冲突,并且迭代器保证不会抛出ConcurrentModificationException。创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改。在迭代器上进行的元素更改操作(remove、set和add)不受支持。这些方法将抛出UnsupportedOperationException。允许使用所有元素,包括null。


0 0
原创粉丝点击