目录
1 Thread类及常见方法
Thread类是JVM用来管理线程的一个类,换句话说,每个线程都有一个唯一的Thread对象与之相关联。
1.1 Thread的常见构造方法
方法 | 说明 |
Thread( ) | 创建线程对象 |
Thread(Runnable target) | 使用Runnable对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runable target,String name) | 使用Runnable对象创建线程对象,并命名 |
Thread(String name)中,name参数,是给线程起了个名字,这里的名字,不影响程序执行,只是方便在调试的时候,快速找到该线程。
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这个是线程的名字");
Thread t4 = new Thread(new MyRunnable(),"这是线程的名字");
1.2 Thread的几个常见属性
属性 | 获取方法 |
ID 线程编号 | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台为线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | islnerrupted() |
关于Thread 的常见属性说明:
ID: 是线程的唯一标识,不同线程不会重复
名称:是各种调试工具会用到
状态:表示线程当前所处的一个情况,比如NEW、RUNNABLE、WAITING等等
优先级:优先级高的线程理论上来说更容易被调度到
是否为后台线程:JVM会在一个进程的所有非后台线程结束后,才会结束运行
是否存活:可以理解为run方法是否运行结束了
是否被中断:涉及到线程如何中断,下文会详细说明
1.3 启动线程 start()方法
从上一篇文章中提到了创建一个线程,可以通过覆写run方法 进行创建,但创建好的线程对象并不意味着线程就开始运行了,这时需要调用start方法,使线程成功运行起来。
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
没有调用start方法的时候,程序运行起来是这样的:
调用start()方法后:
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();//千万不能漏写
}
所以说,只有调用了start方法,才算真正在操作系统的底层创建出一个线程 。
1.4 中断线程
中断一个线程,中断的意思就是让一个线程停下来。让一个线程终止,办法就一种,就是让该线程的入口方法执行完毕。
目前常见的有以下两种方式:
1、给线程中设定一个结束标志位
2、调用interrupt()方法来通知
第一种方法:
public static boolean isQuit = false;//设定一个结束标志位
public class ThreadDemo9 {
public static boolean isQuit = false;
public static void main(String[] args) {
Thread t = new Thread(()->{
while (!isQuit){
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t 线程终止");
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
isQuit = true;
}
}
在上述代码中,如果不设置结束标志位,代码为while(true)循环,这就是一个死循环,会导致入口方法无法执行完毕,自然不能结束线程。
如果此时把循环条件用一个变量来控制,就能结束线程。该代码首先会启动 t 线程,让t 线程先跑,每隔一秒会进行打印,主线程main休眠3秒后,把isQuit这个退出标记修改为true,此时while循环条件为!true 即false,线程自然就跳出循环,打印终止语句,线程自然就结束了。
注意:lambda表达式 / 匿名内部类的变量捕获机制:如果把isQuit成员变量修改为局部变量,就不能成功运行,原因是变量捕获;JAVA要求变量捕获,捕获的变量必须是final或者实际final 或者更改为成员变量,lambda就不用受之前变量捕获规则的限制。
第二种方法
使用Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 来代替自定义标志位。
Thread内部包含了一个boolean类型的变量作为线程是否被中断的标记
public class ThreadDemo {
public static void main(String[] args) {
Thread t = new Thread(()->{
//currentThread是获取到当前线程实例
//此处currentThread得到的对象就是t
//isInterrupted就是t对象里自带的一个标志位
while (!Thread.currentThread().isInterrupted()) {
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//把t内部的标志位给设置成true
t.interrupt();
}
}
上述代码在运行过程中,会发现休眠时间到了之后,调用t.interrupt方法的时候,线程并没有真的结束,而是打印了个异常信息,又继续执行了 。
原因是 interrupt 方法。
interrupt方法的作用:
1 设置标志位为 true
2 如果该线程正在阻塞中(比如在执行sleep)此时就会把阻塞状态唤醒,通过抛出异常的方式让sleep立即结束。
注意:当sleep被唤醒的时候,sleep会自动把islnterrupted标志位给清空(true - false)
这就导致下次循环,循环仍然可以继续执行,如果需要结束循环,就得在catch中设置个break。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
清空标志位的原因,目的是为了让线程自身能够对于线程何时结束,有一个更明确的控制,当前,interrupt方法的效果,不是让线程立即结束,而是告诉线程,该结束了,至于是否真的要结束,立即结束还是等会结束,都是代码来灵活控制。
总结:interrupt只是通知,而不是命令。
1.5 等待线程 join()
由于线程之间是并发执行的,操作系统对于线程的调度是无序的,所以无法判定两个线程谁先执行结束,谁后执行结束。
有时,我们需要等待一个线程完成它的工作后,才能进行下一步工作。比如微信零钱得先存钱,才可以支付。
public class ThreadDemo11 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
System.out.println("hello t");
});
t.start();
t.join();
System.out.println("hello main");
}
}
上述代码,在main线程中,调用t.join方法,表示让main线程等待 t 线程先结束,再往下执行
在t.join执行的时候,如果 t 线程还没结束,main线程就会阻塞等待;如果 t 线程已经结束了,此时join不会阻塞,就会立即执行。此外,join还提供了两个版本,都可以填写一个参数,作为“超时时间”。
join的无参版本,是死等。
join的有参数版本,则是指定最大超时时间,如果等待的时间到了上限,还没等到,也就不等了。
方法 | 说明 |
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等millis毫秒 |
public void join(long millis,int nanos) | 同理,但可以更高精度 |
1.6 休眠当前线程 sleep()
sleep() 方法也是上一篇文章和这一篇代码中有使用过,不过有一点,因为线程的调度是不可控的,所以sleep方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。
方法 | 说明 |
public static void sleep(long millis) throws InterruptedException
|
休眠当前线程
millis 毫秒
|
public static void sleep(long millis, int nanos) throws
InterruptedException
|
可以更高精度的休眠
|
2 线程的状态
操作系统里的线程,自身是有一个状态的,但是Java Thread是对系统线程的封装,把这里的状态又进一步精细化了。代码中可以通过getState()方法去或去线程的状态。
线程的状态 | 说明 |
NEW | 系统中的线程还没创建出来,只是有一个Thread对象 |
TERMINATED | 系统中的线程已经执行完了,但Thread对象还在 |
RUNNABLE | 就绪状态,分为正在CPU上运行或者准备好随时可以去CPU上运行 |
BLOCKED | 表示等待锁出现的状态 |
WAITING | 使用wait方法出现的状态 |
Thread类以及一些常见方法就介绍到这里啦,后面文章就会更新多线程比较重点的地方啦,多线程带来的风险--线程安全问题