Java的线程和操作系统线程的关系
线程是操作系统中的概念.操作系统内核实现了线程这样的机制,并且对用户层提供了一些使用.
Java标准库中的Thread类可以视为是对操作系统提供的API进行了进一步的抽象和封装.
Thread类
在Java中,与线程创建、启动有关的是一个类,叫做Thread。接下来就通过介绍这个类中的一些方法来了解Java中的线程
Thread类的几个常见属性
属性 | 获取方法 |
ID | getId() |
名称 | getName() |
状态 | getState() |
是否后台线程 | isDaemon() |
是否存活 | isAilve() |
是否被中断 | isInterrupted() |
优先级 | getPriority() |
ID 是线程的唯⼀标识,不同线程不会重复
名称是各种调试⼯具⽤到
状态表⽰线程当前所处的⼀个情况,下⾯我们会进⼀步说明
关于后台线程,只需要牢记:JVM只有在所有非后台线程结束后,才会结束运行
是否存活,即简单的理解,为 run ⽅法是否运⾏结束了
线程的中断问题,下⾯我们进⼀步说明
优先级⾼的线程理论上来说更容易被调度到
Thread类的构造方法 || 线程的创建
方法 | 说明 |
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用Runnable对象创建线程对象 |
Thread(String name) | 创建线程对象并重命名为name |
Thread(Runnable target,String name) | 使用Runnable对象创建线程对象,并重命名为name |
了解了这四种构造方法后,创建线程对象主要分为两大种:继承Thread类和实现Runnable接口
run()的用法在下文会解释
1)继承Thread类
class MyThread extends Thread{
//重写run方法
@Override
public void run() {
System.out.println("这是一个线程");
}
}
public class Text01 {
public static void main(String[] args) {
//创建线程对象
Thread t = new MyThread();
}
}
2)实现runnable接口
class MyThread implements Runnable{
//重写run方法
@Override
public void run() {
System.out.println("这是一个线程");
}
}
public class Text01 {
public static void main(String[] args) {
//创建 Runnable 接口的对象
Runnable target = new MyThread();
//通过Runnable接口创建线程对象
Thread t = new Thread(target);
}
}
3) 继承Thread类,但是通过匿名内部类的方式实现
public class Text01 {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
System.out.println("这是一个线程");
}
};
}
}
4)实现Runnable接口,但是通过匿名内部类的方式实现
public class Text01 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("这是一个线程");
}
});
}
}
5)通过lamabda表达式创建
这种方式是对 实现Runnable接口,但是通过匿名内部类的方式实现 的一种简写
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("这是一个线程");
});
}
线程的运行
线程的运行是通过Thread类对象的 start()方法开始的,调用start方法后,jvm就会自动帮我们执行run方法,开启这个线程,可以理解为 run方法中的内容就是这个线程要执行的工作
public class Text01 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("这是一个线程");
});
t.start();
}
}
在运行这段代码后,命令台就会输出 这是一个线程
值得注意的是,线程之间是并行的,
举个例子(sleep方法是让线程休眠指定时间,单位ms):
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while(true) {
System.out.println("这是一号线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Thread t2 = new Thread(() -> {
while(true) {
System.out.println("这是二号线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t1.start();
t2.start();
}
这段代码的结果为:
代码中是两个死循环,如是按照以前的串行方式,必然只会打印一个,但是因为线程是并行的,所以可以同时运行.
等待一个线程
在实际应用中,有时我们需要等待一个线程的结果才能继续执行下面的代码
比如:计算1-10000的和,可以设计两个线程分别计算1-5000的和 和 5001-10000的和
那么在main中创建并执行这两个线程之后,就需要等待两个线程计算完毕才能输出计算的结果,如果不进行等待 那么输出的结果很大概率是错误的。
因此 Thread类就提供了一个方法 :join(),调用这个方法后 会等待调用这个方法的线程对象执行完毕才会执行join 下面的代码,举个例子:
public class Text01 {
public static int sum1 = 0;
public static int sum2 = 0;
public static void main(String[] args) throws InterruptedException {
//创建两个线程 分别计算1-5000的和 和 5001-10000的和
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
sum1 += i;
}
});
Thread t2 = new Thread(() -> {
for (int i = 5001; i < 10000; i++) {
sum2 += i;
}
});
//启动两个线程
t1.start();
t2.start();
//等待两个线程执行完毕
t1.join();
t2.join();
//输出结果
System.out.println("sum:" + (sum1 + sum2));
}
}
有两点需要注意
1.join方法可能会抛出一个 InterruptedException 异常,调用join方法的时候需要对这个异常进行处理(try catch 或者 再次抛出给方法的调用者)
2.join的位置不可以随便( 在上面的代码中start t1、t2后 使用了t1.join t2.join 意为:启动t1线程、启动t2线程、等待t1线程执行结束、等待t2线程执行结束)
join()方法在使用时还可以传入参数
方法 | 说明 |
join() | 等待线程结束 |
join(long millis) | 等待线程结束,最多等待millis毫秒 |
join(long millis,int nanos) | 同上,但是更精确 |
获取当前线程的引用
获取当前线程的引用使用currentThread()方法
有时我们在创建线程时,需要调用线程的其他方法,这时就需要使用currentThread()获取当前线程的引用后才能调用其他方法
假设在线程执行的开始 我要打印该线程的名字:
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(t1.getName());
//这里报错时因为 t1还未创建 所以无法识别t1是什么
});
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
//在这里调用currentThread()获得当前线程的引用 即t2,后调用getName输出名字
});
//启动两个线程
t1.start();
t2.start();
}
休眠线程
休眠即让该线程不去cpu上工作,有⼀点要记得,因为线程的调度是不可控的,所以,这个⽅法只能保证 实际休眠时间是⼤于等于参数设置的休眠时间的。
方法 | 说明 |
sleep(long millis) | 休眠millis毫秒 |
sleep(long millis,int nanos) | 同上,但是更精确 |
要注意:sleep方法会抛出InterruptedException 异常。
什么时候会抛出这个异常? 在sleep被异常唤醒时
如果线程因为调⽤ sleep⽅法⽽阻塞挂起,当sleep被异常唤醒,它就会抛出InterruptedException,此时就会清除线程的中断标志
中断一个线程
在线程的应用中,有时会发生变数导致我们需要停止一个正在运行中的线程。
线程终止有三种方法
1)线程自然终止,即运行完毕run方法中的内容
2)设置标识符,通知该线程应该终止
3)使用stop方法,这个方法不推荐使用,因为是极其不安全的!
上述三种(实际两种)方法,如果我们希望不执行完毕这个线程,那么只能使用第二种:设置标识符
创建一个boolean对象isQuit,修改这isQuit的值来控制线程是否继续执行(不推荐)
public class Text01 {
public static boolean isQuit = false;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while(!isQuit){
System.out.println("running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
//main中休眠5s让t工作
Thread.sleep(5000);
//将isQuit设置为true来中断线程
isQuit = true;
}
}
观察运行结果可以发现,t线程运行5s之后终止了
终止的另一种发放就是调用线程的属性 “是否被中断” 来判断
public class Text01 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while(!Thread.currentThread().isInterrupted()){
System.out.println("running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
//main中休眠5s让t工作
Thread.sleep(5000);
//将t的属性 "是否被中断" 设置为true
t.interrupt();
}
}
观察运行结果发现,t线程运行一段时间后 抛出了一个异常 "InterruptedException"(很眼熟对不对),但是并没有停止运行
思考一下为什么会抛出异常?
在t的运行中 t的运行逻辑为 打印一次running 之后sleep上5s,打印一次的耗时可以说非常小
所以当我们随机访问t的”时间轴“时,t很大概率在sleep.
在main中 我们设置t的属性“是否被中断”为true,此时就会访问t,但是此时t在sleep
那么,t就会被异常唤醒,在上文的"休眠线程"中说过:如果线程因为调⽤ sleep⽅法⽽阻塞挂起,当sleep被异常唤醒,它就会抛出InterruptedException,此时就会清除线程的中断标志
中断标志被清除后,while就会判断 !Thread.currentThread.isInterrupted 为true(因为Thread.currentThread.isInterrupted 被重置为了false)所以循环并没有被打破,t线程继续执行
思考 在这种情况下如何终止呢?
因为抛出异常后我们可以在catch中做额外的语句,所以我们可以在catch中再次设置“是否被中断”为true,这样就可以正常的终止这个线程了
public class Text01 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while(!Thread.currentThread().isInterrupted()){
System.out.println("running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//这里将打印异常信息删除仅仅为了运行结果好看
Thread.currentThread().interrupt();
}
}
});
t.start();
//main中休眠5s让t工作
Thread.sleep(5000);
//将t的属性 "是否被中断" 设置为true
t.interrupt();
}
}
这次可以观察到,t线程执行5s之后正常终止
以上两种方法介绍完毕后,我们发现 要去终止这个线程必须需要线程内部进行”配合“
如上面的判断 !Thread.currentThread.isInterrupted
如果线程不主动配合我们进行终止线程,那我们就无法正常的终止这个线程(这是由Java语法所规定的)