Thread类,java.lang包,默认导入
跑起来的程序称为进程,没跑起来的程序称为可执行文件
进程中至少包含一个线程为主线程,main方法就是主线程入口方法,run方法会在合适的时机被JVM调用执行,main方法与run方法都为回调函数
Thread类提供run方法,需要自己通过继承的方式重新编写run方法里面的逻辑满足需求
run方法中不能抛出异常,无外层方法接收,也不可改变run方法的签名
进程创建第一个线程的开销最大,剩余的线程开销都比较小,也就是main线程开销大
创建线程类之后调用start方法开启新的执行流,两个执行流抢占式执行
创建线程方法
1. 继承Thread类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("这⾥是线程运⾏的代码");
}
}
MyThread t = new MyThread();
t.start();
2. 实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("这⾥是线程运⾏的代码");
}
}
Thread t = new Thread(new MyRunnable());
t.start(); // 线程开始运行
3. 匿名内部类创建Thread子类对象
Thread t1 = new Thread(){
@Override
public void run() {
System.out.println("使用匿名类创建一个 Thread 子类对象");
}
}
此处写{}意思是定义一个类可以定义子类的属性和方法,最主要的目的就是重写run方法,t指向的实例并非是单纯的Thread 其实是重写了run方法的Thread子类
匿名内部类的写法其实是为了描述这个方法,设置回调函数,但是Java的方法不能脱离类,所以就要用最简单的方式在外面套一层壳
4. 匿名内部类创建Runnable子类对象
Thread t2 = new Thread(new Runnable() {
@Override
public void run(){
System.out.println("使用匿名类创建一个Runnable子类对象");
}
})
5. Lambda表达式创建Runnable子类对象
最简洁的写法,Runnable是函数式接口,只需要重写run方法即可,所以可以用Lambda表达式替代匿名内部类,括号里面放的是形参列表
Thread t3 = new Thread(()->{
System.out.println("使用匿名类创建Thread子类对象");
})
编译器编译的时候 Thread构造方法有好几个版本 按照方法签名挨个匹配
Thread常见属性与方法
创建线程对象
Thread(String name) //构造函数传入名称
Thread(String name,Runnable target) //传入Runnbale对象创建线程并命名
常见属性
属性 | 获取方法 | 描述 |
---|---|---|
ID | getId() | JVM自动分配的身份标识,会保证唯一性 |
名称 | getName() | |
状态 | getState() | 获取线程状态(就绪状态,阻塞状态...) |
优先级 | getPriority() | 线程优先级,Java中设置优先级效果不明显,仅对内核调度器产生一定影响,还是随机调度 |
是否后台线程 | isDeamon() | 是否是守护线程(后台线程,不会阻止进程结束,如JVM内置的线程,main线程结束后进程还会执行),代码创建的线程默认是前台线程,没执行完进程不会结束,配合setDeamon使用,传入true时表示将该线程设置为后台线程 |
是否存活 | isAlive() | 表示内核中线程(PCB)是否还存在(与Java中线程生命周期不完全一样,线程run结束完内核PCB就会释放,但是线程实例变量还会存在,此时isAlive为false) |
启动一个线程
Thread类使用start方法启动一个线程,同一个Thread对象start方法只能调用一次
调用start方法,会调用系统的API,完成在内核创建一个线程的操作
经典面试题: start和run方法的区别
start方法创建出内核线程,进入就绪状态,等待系统调用线程执行run方法
终止一个线程
让线程run方法执行完毕
提前终止: 让run方法提前结束
Thread Lamda有自己独立的栈帧, 无法直接进行操作
为了让线程结束要在内部引入标识位,以能够实现外部线程通过操作这个标识位绑定条件判断来使这个线程结束
所以Thread类内置了这个标志变量:interrupted
方法 | 说明 |
---|---|
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞 |
public static boolean interrupted() | 判断当前线程中断标识位是否设置(类方法) |
public boolean isInterrupted() | 判断对象关联的线程标志位是否设置 |
常见结构:
while(!Thread.currentThread().interrupted()) {
...
}
//假设这里的线程对象是t
//主线程中
t.interrupt();//调用实例方法interrupt改变标识位
如果没有sleep,interrupt可以让线程顺利结束,有sleep的话会触发interruptException,将线程唤醒,同时晴空interrupt位设为false,此时如果是循环结构的话,在catch代码中直接加上break语句,结束run方法中的循环体执行完毕
-
实际开发中,catch里要怎么写(如何处理程序异常)
-
-
尝试自动恢复,能自动恢复就自动恢复(如网络通信异常,尝试重连)
-
记录日志,情况不是很严重记录下即可,不需要立即解决
-
发出报警,针对严重问题,自动发邮件,打电话,短信,微信
-
少数正常业务逻辑,会依赖catch来结束循环
-
thread收到通知的⽅式有两种:
-
如果线程因为调⽤wait/join/sleep等⽅法⽽阻塞挂起,则以InterruptedException异常的形式通知,清除中断标志,当出现InterruptedException的时候,要不要结束线程取决于catch中代码的写法.可以选择忽略这个异常,也可以跳出循环结束线程.
-
否则,只是内部的⼀个中断标志被设置,thread可以通过Thread.currentThread().isInterrupted()判断指定线程的中断标志被设置,不清除中断标志,这种⽅式通知收到的更及时,即使线程正在sleep也可以⻢上收到。
线程终止其实是软性操作,需要自定义代码逻辑配合实现,JavaAPI中没有提供强制终止线程的操作
等待线程
线程随机调度,但可以通过一些API,影响线程执行的顺序(常用场景:一个线程执行完之后才能够进行下一步操作,等待线程结束)
有两个线程t1,t2,t2需要等待t1线程,此时t1先结束,t2才能结束
join方法使得t2线程阻塞
执行join方法,会看被等待线程是否在运行,如果运行中主动等待线程就会阻塞,暂时不参与CPU执行,运行结束,主动等待的线程就会从阻塞中恢复过来并继续往下执行
在t1执行的这一段时间,让t2主动放弃了去调度器调度
-
相关方法:
方法 | 说明 |
---|---|
public void join() | 等待线程结束,一直死等,不建议使用 |
public void join(long millis) | 等待线程结束,最多等指定的毫秒数(有等待时间限制) |
public void join(long millis, int nanos) | 更高精度 |
任何线程都可以调用join方法,在哪个线程中调用了join方法,哪个线程就阻塞等待
样例程序:
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Runnable target = () -> {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName()
+ ": 我还在⼯作!");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": 我结束了!")
};
Thread thread1 = new Thread(target, "李四");
Thread thread2 = new Thread(target, "王五");
System.out.println("先让李四开始⼯作");
thread1.start();
thread1.join();
System.out.println("李四⼯作结束了,让王五开始⼯作");
thread2.start();
thread2.join();
System.out.println("王五⼯作结束了");
}
}
执行结果,主线程在thread1.join()语句执行时阻塞等待thread1的run方法执行完毕,t2同理
interrupt方法也可以把阻塞等待的join提前唤醒触发异常
获得当前线程引用(run方法内部)
方法 | 说明 |
---|---|
public static Thread currentThread(); | 返回当前线程对象的引用 |
如果是继承Thread的方法创建线程,还可以使用this引用
如果是实现Runnbale接口或者Lambda表达式的方式将任务放入构造方法中创建线程,就只能使用currentThread()方法获取线程引用
休眠当前线程
public static void sleep(long millis) throws InterruptedException
线程的状态
-
原生系统API只有两个线程状态:
-
就绪状态(可以被调度到CPU执行或者正在执行)
-
阻塞状态(不可执行)
-
Java中线程的所有状态:
-
NEW:对象创建好了,还没调用start方法在系统中创建线程
-
RUNNABLE:可工作的,表示正在执行或者准备就绪
-
BLOCKED:由于锁竞争引起的阻塞
-
WAITING:不带时间的阻塞(死等),必须满足一定的条件才能解除,join和wait方法调用都会进入WAITING
-
TIMED_WAITING: 指定时间的阻塞,到达一定时间解除阻塞,sleep方法,带限制时间的join方法
-
TERMINATED: Thread对象依然存在,但是系统内部的线程已经执行完了