文章目录
认识Thread类
Thread类是
JVM用来管理线程的⼀个类
,换句话说,每个线程都有⼀个唯⼀的Thread对象与之关联
Thread类提供了创建和管理线程的方法和功能,通过使用Thread类,可以同时执行多个任务,实现并行处理。
Thread常见的构造方法
Thread() | 创建Thread对象 |
---|---|
Thread(Runnable target) | 使用Runnable对象创建Thread对象 |
Thread(Runnable target, String name) | 使用Runnable对象创建Thread对象,并命名 |
Thread(String name) | 创建Thread对象,并进行命名 |
Thread类中几个常见的静态方法(通过类名调用)
返回类型 | 方法名 | 说明 |
---|---|---|
Thread | currentThread() | 获取当前线程对象的引用 |
Thread | interrupted() | 检查当前线程是否已经被中断 |
void | sleep(long millis) | 使当前正在执行的线程休眠(单位毫秒) |
void | dumpStack() | 将当前线程的堆栈跟踪信息输出到标准错误流 |
void | yield() | 提示调度器当前线程愿意放弃当前的CPU时间片 |
Thread类中几个常见的实例方法(通过实例化对象使用)
其中标红的为Thread的属性
返回类型 | 方法名 | 说明 |
---|---|---|
long | getId() | 获得ID |
String | getName() | 获取线程名称 |
int | getPriority() | 优先级 |
StackTraceElement[] | getStackTrace() | 堆栈跟踪信息 |
Thread.State | getState() | 线程状态 |
void | interrupt() | 中断对象关联的线程 |
boolean | isAlive() | 是否存活 |
boolean | isDaemon() | 是否为后台线程 |
boolean | isInterrupted() | 判断当前线程的中断标志位是否设置 |
void | setDaemon(boolean on) | 设置为后台线程 |
void | setName(String name) | 设置线程名字 |
void | run() | 覆写run方法 |
void | start() | 启动线程 |
void | join() | 等待线程 |
void | sleep(long millis) | 休眠当前线程 |
注
:
• ID是线程的唯⼀标识,不同线程不会重复,这里的id是java给的id
• 名称在各种调试工具用到,通过jconsole.exe可查看
• 状态表示线程当前所处的⼀个情况
• 优先级高的线程理论上来说更容易被调度到
• 关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有非后台线程结束后,才会结束运行,而后台线程不影响java进程的结束。
• 是否存活,简单的理解为run方法是否运行结束了
方法的使用
public class Demo12 {
public static void main(String[] args) {
Thread t=new Thread(()->{
for (int i = 0; i < 5; i++){
System.out.println(Thread.currentThread().getName()+"我还没结束");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"我即将结束");
});
System.out.println(Thread.currentThread().getName()+":创建的线程ID:"+t.getId());
System.out.println(Thread.currentThread().getName()+":创建的线程名称:"+t.getName());
System.out.println(Thread.currentThread().getName()+":创建的线程状态:"+t.getState());
System.out.println(Thread.currentThread().getName()+":创建的线程优先级:"+t.getPriority());
System.out.println(Thread.currentThread().getName()+":创建的线程后台线程:"+t.isDaemon());
System.out.println(Thread.currentThread().getName()+":创建的线程存活:"+t.isAlive());
System.out.println(Thread.currentThread().getName()+":创建的线程被中断:"+t.isInterrupted());
t.start();
while (t.isAlive()) {}
System.out.println(Thread.currentThread().getName()
+ ": 状态: " + t.getState());
}
}
创建线程
认识了基本的方法之后我们就要开始创建第一个线程了,一定要通过线程变量名.start()
去创建线程。start()方法是Java提供的API来调用系统中创建线程的方法,run()方法
是线程要干的事,在线程创建好之后会自动调用
实现类继承Thread类
通过创建一个新的类继承Thread类,并重写其run方法
,然后在创建该类的实例并调用start方法
来启动线程。
我们在覆写run方法定义线程进行的操作是写一个循环,但是为了便于观察,我们让线程休眠(阻塞状态),这时候会报异常(受检查异常
)
用try-catch处理异常
在main方法里使用Thread.sleep同样需要处理异常,此时抛一个InterruptedException异常即可
public class Demo1 {
static class MyThread extends Thread{
//继承父类需要重写父类中的run方法
@Override
public void run() {
//执行操作
}
}
public static void main(String[] args) throws InterruptedException {
Thread t=new MyThread();//向上转型
t.start();//真正创建了进程
}
}
}
实现Runnable接口
通过创建一个新的类实现Runnable接口,并重写其run方法,然后将该类的实例作为target参数传递给Thread类的构造函数,最后调用Thread对象的start方法来启动线程。
public class Demo2 {
static class Myrunnable implements Runnable{
@Override
public void run() {
//执行操作
}
}
public static void main(String[] args) throws InterruptedException {
Runnable runnable=new Myrunnable();
Thread t=new Thread(runnable);//带参数的构造方法,也做到了解耦合
t.start();//使得该线程开始执行;Java 虚拟机调用该线程的 run 方法
}
}
使用匿名内部类
无论是继承Thread类还是实现Runnable接口,都可以使用匿名内部类的方式来创建线程,这种方式更加简洁。
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
//匿名内部类,创建了Thread的子类,创建了实例,并赋值给t这个对象,在{}里可以定义子类的方法,属性,重写父类的方法
//如果某些代码是一次性的,不需要重复使用的,就可以使用匿名内部类
Thread t = new Thread() {
@Override
public void run() {
//执行操作
}
};
t.start();//使得该线程开始执行;Java 虚拟机调用该线程的 run 方法。
}
}
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable=new Runnable() {
@Override
public void run() {
//执行操作
}
};
//使⽤匿名类创建 Runnable ⼦类对象
Thread t=new Thread(runnable);//这种方式方便以后修改代码,降低耦合,可以通过其他方式去完成线程,比如线程池,协程,到时候只需要改变传入的参数即可
t.start();//使得该线程开始执行;Java 虚拟机调用该线程的 run 方法。
}
}
使用lambda表达式(推荐)
lambda——本质上是匿名函数,主要用途作为“回调函数”
()->{}为函数式接口
创建了一个匿名的函数式接口的子类,并创建了对应的实例,并重写了里面的方法
举例
:交替打印 “hello Thread” 和 “hello main”
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (true) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();//使得该线程开始执行;Java 虚拟机调用该线程的 run 方法。
while (true) {
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
打印的结果(是个死循环)
多运行几次可以发现hello Thread和Hello main的打印顺序并没有一致,这是由于系统调度器随机调度
导致的
使用Callable创建线程
Callable和Runnable是并行关系
创建Callable对象,重写call()方法,不同于Runnable的是,Callable需要通过FutureTask/Future把Callable任务包装成一个FutureTask对象
获取结果通过get()方法,如果任务未完成,get方法会阻塞
public class Demo29 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable=new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int result=0;
for (int i = 1; i <= 1000; i++) {
result+=i;
}
return result;
}
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t=new Thread(futureTask);
t.start();
System.out.println(futureTask.get());
}
}
通过线程池创建线程
Java 提供了 java.util.concurrent 包中的 ExecutorService 接口来管理线程池。
Executors.newCachedThreadPool()是Executors类中的一个静态方法,它创建了一个线程池,这个线程池会根据需要创建新线程
,但是当现有线程空闲(60秒)时会被终止并移除。这意味着,这个线程池在需要时能够迅速响应并执行任务,而在任务完成后不会保留大量空闲线程,从而节省了系统资源。
需要通过submit()
提交任务给线程池
public class Demo33 {
public static void main(String[] args) {
ExecutorService pool= Executors.newCachedThreadPool();
for(int i=0;i<10;i++){
int id=i;
pool.submit(()->{//提交任务
System.out.println("任务 "+id);
});
}
pool.shutdown();//关闭线程池
}
}
中断线程
interrupt()
:中断本线程(将中断状态标记为true)
isInterrupted()
:判断当前线程的中断标志位是否设置,调用后清除标志位,不建议使用,静态方法为所有线程共用的
interrupted()
:判断对象关联的线程的标志位是否设置,调用后不清除标志位
举例
:main线程中断t线程
public class Demo10 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while(!Thread.currentThread().isInterrupted()){//未被中断
System.out.println("Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//break;立即终止
//先执行一些其他逻辑的代码再break,稍后终止
//什么都不写 不终止
break;
}
}
System.out.println("t:结束");
});
t.start();
Thread.sleep(3000);
System.out.println("main线程尝试终止t线程");
t.interrupt();//线程结束,会唤醒休眠的sleep(阻塞),sleep会把isInterrupted设置成true
}
}
thread收到通知的方式有两种:
-
如果线程因为调用wait/join/sleep等方法而阻塞挂起,则以InterruptedException异常的形式通知,清除中断标志。当出现InterruptedException的时候,要不要结束线程取决于catch中代码的写法.可以选择忽略这个异常,也可以跳出循环结束线程.
-
只是内部的⼀个中断标志被设置,thread可以通过 Thread.currentThread().isInterrupted()判断指定线程的中断标志被设置,不清除中断标志
这种方式通知收到的更及时,即使线程正在sleep也可以马上收到。
线程等待
有时,我们需要等待⼀个线程完成它的工作后,才能进行自己的下⼀步工作。
举例
:main线程等待t线程结束
public class Demo11 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
for (int i = 0; i < 3; i++) {
System.out.println("Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
System.out.println("t线程结束" );
});
t.start();
t.join();//main线程等待t线程结束,可以设置谁先后结束
//Thread.sleep(2000);//按时间来比不稳定
System.out.println("main线程结束");
}
}
在主线程中调用线程对象.join()
;就是等待线程对象执行完再执行主线程。
注
:
死等:调用线程对象.join();就会让该线程执行完才继续执行外面的线程,如果线程对象对应的线程一直不结束那么外面的线程就会一直等
超出时间:调用线程对象.join(long millis);就会在该线程执行millis毫秒后执行外面的线程。
如果遇到调用join前线程已经结束,外面的线程不会陷入等待。
线程休眠
前面已经用了很多了,需要注意的是因为线程的调度是不可控的
,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。也是因为这个,不能用时间来等待线程结束(不稳定)
在系统让线程休眠sleep中的参数毫秒后,线程会被唤醒从阻塞状态变成就绪状态
,但不会马上执行,涉及到调度开销。
并且在Windows和Linux系统上达到毫秒级误差。
获取线程状态
在操作系统里面进程和线程最重要的状态就是:就绪状态
和阻塞状态
。
线程的状态是一个枚举类型 Thread.State。
状态 | 说明 |
---|---|
new | Thread对象已经创建,但是start()方法没有调用 |
terminated | Thread对象还在,但是内核中线程已经结束了 |
Runnable | 就绪状态,线程已经在CPU上执行或者在CPU上等待执行 |
timed_waiting | 由于像sleep(),join(时间) 这种固定时间产生的阻塞 |
waiting | 由于像wait(),join() 这种不固定时间产生的阻塞 |
blocked | 由于锁竞争产生的阻塞 |
线程状态的切换通常涉及以下几个关键的操作:
调度:操作系统根据线程的优先级和资源可用性来调度线程。
同步:线程在进入同步代码块或方法时可能需要等待监视器锁。
等待/通知:线程可能需要等待其他线程发出的信号或通知。
超时:某些等待操作有超时限制,超时后线程可能自动恢复到可运行状态。
中断:线程可以被其他线程中断,这可能导致它从等待或超时等待状态中恢复。