1、线程是什么
关于线程,Android开发经常遇到的一个和线程相关的异常报错:NetworkOnMainThreadException,因为网络请求不可以运行在主线程(又称UI线程)。和网络请求一样的还有I/O操作、数据库操作等耗时任务一样都只能在子线程运行,他们都被成为耗时操作。那么线程到底是什么?线程是操作系统进行调度的最小单元,我们写的每一行代码都是在线程内执行的。所有更新UI的操作包括view、dialog、toast等展示和更新都必须在主线程执行,但如果在主线程执行耗时操作就会导致其他UI更新无法及时别响应,影响用户体验甚至引起ANR异常用户可能会决定退出甚至卸载您的应用。,所以耗时操作都必须在子线程执行。
子线程可以执行耗时操作但也不是越多越好,因为只有线程数量不超过cpu核心数量才会同时执行,一定那超过也是需要通过时间轮转调度而非同时进行。线程的存在即便什么也不执行也是会占用一定的内存开销并且执行创建和销毁线程的操作也会有系统开销,因此会有线程池的概念。
当应用组件启动时,如果应用没有运行任何其他组件,Android 系统会使用单个执行线程为应用启动新的 Linux 进程。默认情况下,同一应用的所有组件都在同一进程和线程(称为主线程)中运行
Android 的单线程模型有以下两条规则:
请勿阻塞界面线程。
请勿不能从界面或主线程之外的任何线程更新界面。
为了帮助您遵循这些规则,Android 提供了多种从其他线程访问界面线程的方式。以下列出了几种有用的方法:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
不过,随着操作变得越来越复杂,为了便于维护可以考虑在工作器线程中使用 Handler 处理从界面线程传送的消息。
2、使用子线程
主线程我们知道是在ThreadActivity的main方法中启动的,下面总结以下怎样开启一个子线程,结合日志一起看:
先打印一下主线程的名称和id:
Log.e(TAG, " 当前线程= " + " name="+Thread.currentThread().getName()+ " id="+Thread.currentThread().getId());
日志打印:05-04 04:04:52.520 5942-5942/com.example.testdemo3 E/ThreadActivity: 当前线程= name=main id=1
第一种、通过Thread+Runable的匿名内部类实现:
Thread thread = new Thread(new Runnable() {@Overridepublic void run() {//耗时任务Log.e(TAG, " Thread-run 当前线程= " + " name="+Thread.currentThread().getName()+ " id="+Thread.currentThread().getId());}
});thread.setName("俺是第一");if (threadIsRuning){thread.interrupt();threadIsRuning = !threadIsRuning;// 停止一个线程}else {thread.start();threadIsRuning = !threadIsRuning;}
日志信息:05-04 04:04:56.661 5942-5981/com.example.testdemo3 E/ThreadActivity: Thread-run 当前线程= name=俺是第一 id=217
第二种 和第一种相同只是将Runnable单独拎出来,写一个类实现Runnable接口
// 定义Runnable的实现类
public static class MyRunnable implements Runnable{@Overridepublic void run() {try {Thread.sleep(2000);Log.e(TAG, "Runnable-run 当前线程= " + " name="+Thread.currentThread().getName()+ " id="+Thread.currentThread().getId());} catch (InterruptedException e) {e.printStackTrace();}}
}MyRunnable myRunnable = new MyRunnable();
// myRunnable.run();//还是当前线程Thread thread02 = new Thread(myRunnable);thread02.setName("俺是第二");thread02.start();
日志信息:05-04 04:05:08.664 5942-5982/com.example.testdemo3 E/ThreadActivity: Runnable-run 当前线程= name=俺是第二 id=218
第三种 继承Thread类重写run方法
// 定义Thread类的子类
public static class MyThread extends Thread{//重写Thread类中的run方法,设置线程任务@Overridepublic void run() {Log.e(TAG, "MyThread- Thread-run 当前线程= " + " name="+Thread.currentThread().getName()+ " id="+Thread.currentThread().getId());//run方法此处打印: 05-04 03:52:55.402 5466-5466/com.example.testdemo3 E/ThreadActivity: MyThread- Thread-run 当前线程= name=main id=1//start方法时此处打印:05-04 04:15:42.852 5942-6057/com.example.testdemo3 E/ThreadActivity: MyThread- Thread-run 当前线程= name=Thread-219 id=219}
}
//启动线程MyThread myThread = new MyThread();myThread.start(); //重新启动线程
// myThread.run();//还是在当前线程,不会重新启动线程
如果上面是执行myRunnable.run();//还是当前线程的日志信息:05-04 03:52:55.402 5466-5466/com.example.testdemo3 E/ThreadActivity: MyThread- Thread-run 当前线程= name=main id=1
如果上面是执行thread02.start(); //05-04 04:15:42.852 5942-6057/com.example.testdemo3 E/ThreadActivity: MyThread- Thread-run 当前线程= name=Thread-219 id=219
这里调用了启动线程、停止线程、set和get线程名称以及id等方法。
2、Thread和Runnable什么关系
Thread就是Runnable的一个实现类:
Runnable:
Thread:
这两中方式我们该怎样选,个人认为如果想简单点就用Thread想灵活一些请考虑Runnable。使用Runnable开启线程也离不开Thread,如果觉得调用runnable的run方法也可以开启线程执行任务就有点跑偏了。
先看一下Thread类的start()方法和run()方法的不同就理解为什么说跑偏了。
Thread类的start()方法和run()方法的主要区别在于人家扮演的角色压根就不一样。以下是两者的主要区别:
首先达到的效果不同。run()方法是线程中实际运行的代码,他就是仅代表调用run方法而已;start()方法是启动一个新线程,它会创建一个新的线程并在新的线程中并行执行run方法的任务。因此在实际的多线程编程中,应该通过调用start()方法来启动线程。
启动次数不同。run()方法是一个类中的普通方法,可以多次调用,每次调用都会执行一次任务,而start()方法只能被调用一次,如果再次调用start()方法,会抛出IllegalThreadStateException异常。//todo 每次停止线程,再启动需要重新new一个线程,否则报错: java.lang.IllegalThreadStateException at java.lang.Thread.start(Thread.java:878) at com.example.testdemo3.activity.ThreadActivity$2.onClick(ThreadActivity.java:62)
所以runnable的run方法和Thread类的run()方法一样,run()方法是线程中实际运行的代码,他就是仅代表调用run方法而已,我们还是需要通过Thread的start方法开启新线程执行任务。