简介
本篇文章是带大家了解 Java多线程的基础知识.
主要内容: 介绍多线程的概念, 了解多线程的优点, 状态, 简单运用. 我是Android开发者, 所以在讲解过程中会插入 Android 的使用和实现.
预告: 这个只是第一篇. 后面会补上 同步异步, 阻塞队列, 线程池, 还有 Android 实现多线程等知识点.
目录:
1.进程与线程的区别
2.为什么使用多线程
3.线程的状态
4.线程的基本使用
1.进程与线程的区别
1.1什么是进程
我们经常会混淆进程和线程的概念. 首先进程是操作系统结构的基础, 是程序在一个数据集合上运行的过程, 是系统进行资源分配和调度的基本单位. 好吧, 我知道你们读了书上的原话还是一脸蒙比. 只要理解下面这句话就行了. 进程就是程序的实体.这样好理解了吧, 就是一个程序就是一个进程.
1.2什么是线程
线程也是进程的一部分, 进程包含线程, 是操作系统调度的最小单元, 也叫做轻量级进程. 在一个进程中可以创建多个线程. 譬如:一个程序(进程)里面有很多子任务, 有些负责请求网络, 有些负责处理内存, 有些负责下载, 这些子任务就可以理解成为线程. 每个线程都拥有各自的计数器, 堆栈, 和局部变量等属性, 并且能够访问共享的内存变量(手动敲黑板).
2.为什么使用多线程
2.1从系统来看
- 使用多线程可以减少程序的响应时间. 将耗时操作放到单独的一个线程去做. 使程序有更好的交互性
- 与进程相比, 线程的创建和切换开销更小.
- 多 CPU 或者多核计算机中使用多线程能提高 CPU 的利用率
- 使用多线程能简化程序结构, 使程序便于理解和维护.
2.2从 Android 来看
主线程(UI线程)
当应用启动,系统会创建一个主线程(main thread)。
这个主线程负责向UI组件分发事件(包括绘制事件),也是在这个主线程里,你的应用和Android的UI组件(components from the Android UI toolkit (components from the android.widget and android.view packages))发生交互。
所以 main thread 也叫 UI thread 也即 UI 线程。
所以获取到数据之后更新界面要在主线程中操作.子线程
耗时操作比如, 请求网络, 下载, 上传, 获取数据等耗时要放在子线程中操作.注意点
1.不要阻塞 UI 线程.
2.不要在子线程中更新UI。
3.线程的状态
在线程运行的过程中, 会处于6种不同的状态, 这6种线程状态分别为:
- New(创建): 线程被创建, 还没有调 用 Start 方法, 在线程运行之前还有一些基础工作要做.
- Runnable(可运行): 一旦调用 Start 方法, 线程就处于 Runnable 状态. 一个可运行的线程, 可能正在运行, 也肯能没有运行, 这要取决于操作系统给线程提供的运行时间
- Blocked(阻塞): 表示线程被锁阻塞, 它暂时不活动.
- Waiting(等待): 线程暂时不活动, 并且不运行任何代码, 这消耗最少的资源, 直到线程调度器重新激活它
- Timed waiting(超时等待): 和等待状态不同的是, 它是可以在指定时间自行返回的.
- Terminated(终止): 表示当前线程已经执行完毕. 导致线程终止有两种情况: 第一种是: 执行完 run 方法正常退出. 第二种: 因为没有捕获的异常而终止了 run 方法, 导致线程进入终止状态.
4.线程的基本使用
4.1创建线程
一般多线程的实现有3种方法:
1. 继承 Thread 类, 重写 run() 方法:
Thread 类本质上也是实现了 Runnable 接口的一个实例. 需要注意的是调用了 start() 后并不是立即执行多线程的代码, 而是该线程进入 Runnable 状态 , 由系统决定什么时候执行.
- 定义 Thread 类的子类, 并重写该类的 run() 方法, 该 run() 也被称为执行体.
- 创建 Thread 子类的实例, 初始化对象.
- 调用线程对象的 start() 方法.
//1. 定义 Thread 类的子类, 并重写该类的 run() 方法, 该 run() 也被称为执行体.
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
public static void main(String [] args){
//2. 创建 Thread 子类的实例, 初始化对象.
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
//3. 调用线程对象的 start() 方法.
myThread1.start();
myThread2.start();
}
2. (推荐)实现 Runnable 接口, 并实现该接口的 run() 方法:
接口的方式推荐使用, 避免了 Java 单继承的问题. 因为一个类应该在其需要加强或修改时才会被继承. 因此没有必要实现 Thread 类的其他方法.
- 自定义类实现 Runnable 接口, 实现 run() 方法.
- 创建 Thread 类的实例, 用实现 Runnable 接口的对象作为参数实例化该 Thread 对象.
- 调用 Thread 的 start() 方法.
//1. 自定义类实现 Runnable 接口, 实现 run() 方法.
public class MyThread implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
//2. 创建 Thread 类的实例, 用实现 Runnable 接口的对象作为参数实例化该 Thread 对象.
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
//3. 调用 Thread 的 start() 方法.
thread.start();
3. 实现 Callable 接口, 重写 call() 方法:
Callable 接口实际上是属于 Executor 框架中的功能类, Callable 接口与 Runnable 接口的功能类似, 但提供了比 Runnable 更强大的功能, 主要表现在以下 3 点:
优点:
- Callable 可以在任务结束后提供一个返回值.
- Callable 中 call() 方法可以抛出异常.
- 运行 Callable 可以拿到一个 Future 对象, Future 对象表示异步计算的结果. 可以使用 Future 来监视目标线程调用 call() 方法的情况. 注意: 调用 Future 的 get() 方法以获得结果时, 当前线程会阻塞, 知道 call() 方法返回结果.
使用步骤:
- 自定义类实现 Callable 接口, 实现 call() 方法.
- 创建 实现了 Callable 接口对象的实例.
- 创建一个Executor线程池的实例.
- 创建 Future 的实例.
创建 Future 实例, 用Thread 子类的对象 作为参数实例化该 Future 对象. - 调用 Future 的 submit 方法.
public class TestCallable{
//1. 自定义类实现 Callable 接口, 实现 call() 方法.
public class MyTestCallable implements Callable{
public String call() throws Exception{
retun "Hello world";
}
}
public static void main(String[] args){
//2. 创建实现了 Callable 接口对象的实例.
MyTestCallable mMyTestCallable = new MyTestCallable();
//3. 创建一个Executor线程池的实例.
ExecutorService mExecutorService = ExecutorService.niewSingleThreadPool();
//4. 创建 Future 的实例.
//5. 调用 Future 的 submit() 方法.
Future mfuture = mExecutorService.submit(mMyTestCallable);
try{
System.out.println(mfuture.get());
} catch (Exception e){
e.printStackTrace();
}
}
}
4.2线程的中断
主要方法:
public boolean isInterrupted():
每个线程都一个状态位用于标识当前线程对象是否是中断状态。isInterrupted() 可以获得线程的中断标识位, 中断标识位为 true 为中断, 反之为 false.
public void interrupt():
interrupt() 可以用来请求中断线程. 当一个线程调用 interrupt() 方法时, 线程的中断标识位被置为 true , 线程会时不时检测这个标识位,
public static boolean interrupted() :
interrupted对中断标识位进行复位.
注意:
在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法(sleep()、join()、wait()及可中断的通道上的 I/O 操作方.调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断标示位清除,把中断标志重新设置为false。 即只要执行了sleep()、join()、wait()方法,线程的中断标记就会被清除。
中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断,中断或者不中断。 某些线程非常重要,以至于它们应该不理会中断,而是在处理完抛出的异常之后继续执行,但是更普遍的情况是,一个线程将把中断看作一个终止请求。法)调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断标示位清除,即重新设置为false.
安全的终止线程
public class InterruptedDemo {
public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
while(true) {
System.out.println("The thread is waiting for interrupted!");
//中断处理逻辑
if(Thread.currentThread().isInterrupted()) {
System.out.println("The thread is interrupted!");
break;
}
//Thread.yield();
}
}
};
t1.start();
t1.interrupt();//中断线程
//System.out.println("The Thread is interrupted!");
}
}
4.3线程的优先级
在 Java 中, 每个线程都有优先级, 默认继承父线程的优先级.
优先级的高低只和线程获得执行机会的次数多少有关。并非线程优先级越高的就一定先执行,哪个线程的先运行取决于CPU的调度.
可以将优先级设置在 MIN_PRIORITY(在Thread类定义为1)与 MAX_PRIORITY(在Thread类定义为10)之间的任何值。
线程的默认优先级为 NORM_PRIORITY(在Thread类定义为5)。
优先级的级别: 从1 ~ 10, 由小到大, 数字越大, 优先级越高.
设置优先级: Thread对象的setPriority(int x);
获取优先级: Thread对象的getPriority();
4.4守护线程
守护线程, 也叫后台线程, 是有别于普通线程(用户线程)的处于后台运行的一种线程. 线程创建的默认是用户线程.
计时线程就是一个例子,他定时发送信号给其他线程或者清空过时的告诉缓存项的线程。当只剩下守护线程时,虚拟机就退出了,由于如果只剩下守护线程,就没必要继续运行程序了。
另外JVM的垃圾回收、内存管理等线程都是守护线程。
作用: 守护线程唯一的用途就是为其他线程提供服务。
特点: 若所有的用户线程(前台线程)都死亡,首页线程也自动死亡。
设置后台线程: Thread对象setDaemon(true);
setDaemon(true)必须在start()调用前。否则出现IllegalThreadStateException异常;
判断是否是后台线程: 使用Thread对象的 isDaemon() 方法;
注意: 在守护线程中新建的线程也是守护线程.