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。
- Android中线程Thread的使用
- Android中使用Thread线程出现的问题
- C++11中thread线程的使用
- php中关于线程thread的使用
- 线程thread的使用
- 一个简单的例子解析android中Handler线程和普通Thread的使用
- Android中使用Handler和Thread线程执行后台操作
- Android中使用Handler和Thread线程执行后台操作
- Android中Timer与Thread的使用
- Android中Service(服务)和Thread(线程)的关系
- Android学习笔记(10)---关于线程Thread的使用
- Java中线程池(Thread Pool)的使用
- Android 线程Thread的释放
- Android Thread的使用
- Android 中 C++ Thread线程用法
- Android 中 C++ Thread线程用法
- Android 中 C++ Thread线程用法
- Android线程管理之Thread使用总结
- 连续因子
- YII中Ueditor富文本编辑器文件和图片上传的配置
- 1126. Eulerian Path (25)
- Android fragment中广告图片轮播效果的实现(附图 )
- Actor并发模型入门
- Android中线程Thread的使用
- C++ using关键字作用总结
- 半平面交(未完成)
- MySQL 主从复制与读写分离概念及架构分析 (转)
- 静态资源的引用
- 深度学习(2):Recurrent Neural Networks
- ARM基础 七、ARM伪操作和伪指令
- 微信小程序要这样去解读
- CodeForces Round #403 补题