认识线程
概念
一个线程就是一个 "执行流". 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 "同时" 执行着多份代码
什么是线程?
线程的实现方式有很多种:
进程和线程的关系?——面试题
- 多个进程的内存相互隔离(独立),一个进程中的多个线程,可以共享内存
- 进程包含线程,每个进程至少包含一个线程,即主线程
- 进程是系统分配资源的最小单位,线程是系统调度cpu的最小单位
- 线程的创建,销毁代价比进程小(相较进程开销小(状态转化,进程开销大于线程))
- 线程(如果有bug)可能会造成整个进程挂掉;
- 进程可能是独立运行(可能存在进程通信)
多线程的好处?
- 充分利用cpu资源,提高执行效率
- io等阻塞时(如果希望能同时接收输入)
缺陷/注意
- 线程的创建/销毁具有一定系统开销,一般用于执行耗时较长的任务
- 增加编码的复杂程度:(执行顺序和se执行顺序不同,学习线程安全)
线程的状态
java线程的状态
- new :创建态
- runnable:可运行态
- 程序无法判断某个时间是就绪还是运行,此状态对程序没有意义
- 等待
- 超时等待
- 阻塞
- 销毁
Thread类及常见方法
Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关 联。
用我们上面的例子来看,每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象 就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。
Thread的常见构造方法
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
【了解】 Thread(ThreadGroup group,Runnable target) | 线程可以被用来分组管理,分好的组即为线程组,这个目前我们了解即可 |
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");
Thread的几个的常见属性
属性 | 获取方法 |
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
- ID 是线程的唯一标识,不同线程不会重复
- 名称是各种调试工具用到
- 状态表示线程当前所处的一个情况,下面我们会进一步说明
- 优先级高的线程理论上来说更容易被调度到
- 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
- 是否存活,即简单的理解,为 run 方法是否运行结束了
- 线程的中断问题,下面我们进一步说明
Thread类中的常用方法
创建线程——面试题
在理解层面:只有new Thread 创建线程,而new Runnable 只是一个任务的定义/描述
方法一:继承Thread类
写法1:自定义类继承Thread
1.1继承Thread类来创建线程类
class MyThread extends Thread{
@Override
public void run() {
System.out.println("线程运行的代码");
}
}
1.2 创建类的实例
MyThread t = new MyThread();
写法2:通过匿名内部类
//继承的写法2.使用一个匿名内部类
Thread t = new Thread(){//属于继承Thread但没有名称的子类
@Override
public void run() {
System.out.println("匿名内部类 run");
}
};
用start方法启动线程
t.start();
方式二:实现Runnable接口
写法1:实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("方式二创建线程");
}
}
Runnable r = new MyRunnable();
Thread t2 = new Thread(r);
写法2:使用匿名内部类
//实现Runnable写法2:匿名内部类
Runnable r2 = new Runnable() {//属于Runnable接口的实现类(没有名字)
@Override
public void run() {
System.out.println("匿名内部类run");
}
};
Thread t2 = new Thread(r2);
//也可以把匿名内部类对象,直接写在构造方法的参数上
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类run");
}
});
//了解:lambda表达式
Thread t4 = new Thread(() -> System.out.println("匿名内部类run"));
t4.start();
调用Thread的start方法
t1.start(); t2.start(); t3.start();
线程的启动
覆写run方法是提供给线程要做的事情的指令清单;
线程对象可以理解为需要干什么(但还没有开始)
调用start()方法,线程知道现在开始执行
start()-》 申请系统调度,执行thread中任务(重写的run方法)
可以使用jconsole命令观察线程
面试 题:Thread中,start()和run()有什么区别?
- start:启动线程的方式
- run:属于线程任务的描述
当线程启动后才会执行线程!
获取当前线程引用
当前线程:
一般上下文语义,是描述某一行,说当前线程——某行代码所在的线程
方法 | 说明 |
public static Thread currentThread(); | 返回当前线程对象的引用 |
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
等待一个线程
有时需要等一个线程执行完成后再进行下一步工作。
方法 | 说明 |
public void join() 当前线程等待,直至线程引用死亡 | 等待线程结束 |
public void join(long millis) 当前线程最多等待给定的毫秒数,或者线程引用死亡 | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
休眠当前进程
因为线程调度是不可控的,所以这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。
方法 | 说明 |
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis 毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠 |
中断一个线程
情景:李四一旦进到工作状态,他就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我们需要增加一些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那张三该如何通知李四停止呢?这就涉及到我们的停止线程的方式了。
目前常见的方式:
1.通过共享的标记进行
2.调用interrupt()方法来通知
方式一:使用自定义的变量来作为标志位.
- 需要给标志位上加 volatile 关键字(这个关键字的功能后面介绍).
- 自定义标志位,能够实现某些条件的中断 ,但如果线程处于等待/超时等待/阻塞, 就没法中断
public class 自定义标志位 {
//先设计一个标志位:表示是否被中断。这里volatile后续学习
private static volatile boolean 是否被中断 = false;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
//循环10次,每次打印次数,并休眠1秒
try {
//判断条件加上标志位
for (int i = 0; i < 10 && !是否被中断; i++) {
System.out.println(i);
//自定义标志位,能够实现某些条件的中断
//但如果线程处于等待/超时等待/阻塞, 就没法中断
//修改时间为100秒就没法中断
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
//要中断t线程:等3后再中断
Thread.sleep(3000);
是否被中断 = true;
}
}
方式二:使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位.
Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记
方法 | 说明 |
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方式通知,否则设置标志位 |
public static boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后清除标志位 |
public boolean isInterrupted() | 判断对象关联的线程的标志位是否设置,调用后不清除标志位 |
- 使用thread对象的interrupted()方法通知线程结束
public class ByInterrupt {
private static class MyRunnable implements Runnable{
@Override
public void run() {
//while(!Thread.currentThread().isInterrupted()) 也可以
while(!Thread.interrupted()){
System.out.println(Thread.currentThread().getName()
+ ": 别管我,我忙着转账呢!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName()
+ ": 有内鬼,终止交易!");
// 注意此处的 break
break;
}
}
System.out.println("interrupt successfully!");
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable r1 = new MyRunnable();
Thread t1 = new Thread(r1,"李四");
System.out.println(Thread.currentThread().getName()
+ ": 转账");
t1.start();
t1.sleep(1000);
System.out.println("中断");
t1.interrupt();
}
}
Thread收到通知的方式有两种:
- 如果线程因为调用wait/join/sleep等方法二阻塞挂起,则以interruptedException异常的形式通知,清除中断标志
- 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择忽略这个异常, 也可以跳出循环结束线程
- 否则,只是内部的一个中断标志被设置,thread 可以通过
- Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
- Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志
这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。
中断的方式是:1. 抛异常中断 2. 抛异常后会重置/还原中断标志位
观察标志位是否清除
标志位是否清除, 就类似于一个开关.
Thread.isInterrupted() 相当于按下开关, 开关自动弹起来了. 这个称为 "清除标志位"
Thread.currentThread().isInterrupted() 相当于按下开关之后, 开关弹不起来, 这个称为 "不 清除标志位"
- 使用Thread.isInterrupted(),线程中断会清除标志位
public class FlagClear {
//interrupted() 清除标志位
public static void main1(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0;i<10;i++)
System.out.println(Thread.interrupted());
}
},"李四");
t1.start();
//中断
t1.interrupt();
}
//第一个为true,其余为false,因为清除标志位
//isInterrupted() 不清除标志位
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0;i<10;i++)
System.out.println(Thread.currentThread().isInterrupted());
}
},"张三");
t1.start();
t1.interrupt();
}
}
//全为true,没有清除标志位