💗推荐阅读文章💗
- 🌸JavaSE系列🌸👉1️⃣《JavaSE系列教程》
- 🌺MySQL系列🌺👉2️⃣《MySQL系列教程》
- 🍀JavaWeb系列🍀👉3️⃣《JavaWeb系列教程》
- 🌻SSM框架系列🌻👉4️⃣《SSM框架系列教程》
🎉本博客知识点收录于🎉👉🚀《JavaSE系列教程》🚀—>✈️12【多线程、锁机制、lock锁】✈️
文章目录
- 一、多线程概念
- 1.1 程序的并发与并行
- 1.1.1 程序的并行
- 1.1.2 程序的并发
- 1.2 进程与线程
- 1.2.1 进程
- 1.2.2 线程
- 1.2.3 多线程并发就一定快吗?
- 二、Java中的多线程
- 2.1 Java线程体验
- 2.1.1 线程初体验
- 2.1.2 线程执行流程
- 2.2 线程类
- 2.2.1 常用方法
- 2.2.2 使用Runnable创建线程
- 2.2.3 Thread和Runnable的区别
- 2.2.4 使用匿名内部类创建线程
- 1)回顾匿名内部类:
- 2)使用匿名内部类创建线程
- 2.2.5 使用Lambda表达式创建线程
- 2.3 线程的操作
- 2.3.1 线程的休眠
- 2.3.2 线程的加入
- 1)join方法示例
- 2)join方法的应用场景
- 3)join方法注意事项
- 2.3.3 守护线程
- 2.3.4 线程优先级
- 2.3.5 线程礼让
- 2.3.6 线程中断
- 1)interrupt中断线程
- 2)中断线程的其他情况
- 2.3.7 线程的其他方法
- 1)线程退出
- 2)线程挂起
- 2.4 Callable实现线程
- 2.4.1 Callable的使用
- 2.4.2 Callable案例
一、多线程概念
在实际应用中,多线程非常有用。例如,一个浏览器可以同时下载几幅图片,一个WEB浏览器需要同时服务来自客户端的请求,我们的电脑管家也可以一边杀毒一边清理垃圾再一边进行电脑体检等任务,这些都是多线程的应用场景。
1.1 程序的并发与并行
1.1.1 程序的并行
程序的并行指的是多个应用程序真正意义上的同时执行,CPU分配多个执行单元共同执行这些任务,效率高,但这依赖于CPU的硬件支持,需要CPU多核心的支持,单核处理器的CPU是不能并行的处理多个任务的。
1.1.2 程序的并发
程序的并发指的是多个应用程序交替执行,CPU分配给每个应用程序一些“执行时间片”用于执行该应用程序,由于CPU的处理速度极快,并且分配个每个线程的“执行时间片”极短,给人们造成视觉上的误感,让人们以为是“同时”执行,其实是交替执行。
需要注意的是:虽然是交替执行,但是程序的并发解决了多个程序之间不能“同时”执行的问题,并且程序的并发利用了CPU的空余时间,能将CPU的性能较好的发挥,另外并发不受CPU硬件的限制,实际开发中,并发往往使我们考虑的重点。
Tips:程序并行执行需要依赖于CPU的硬件支持,而并发却不需要;
1.2 进程与线程
1.2.1 进程
- 进程:是指一个内存中运行的应用程序,我们开启的应用如QQ、微信、google浏览器、idea开发工具等都是一个应用,一个应用最少具备一个进程,也有可能有多个进程,每个进程都有一个独立的内存空间,进程是系统运行程序的基本单位;
Tips:多个进程的执行可以是并行也可以是并发;
1.2.2 线程
- 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,是一个程序内部的一条执行路径,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序;
关于进程和线程的概念我们理解即可,上图中电脑管家的“首页体检”、“病毒查杀”等功能也有可能是一个进程来完成,关于病毒查杀功能下面可能还有其他小功能,有可能是线程完成,也有可能还是一个独立的进程来完成;
1.2.3 多线程并发就一定快吗?
我们知道,并发本质上其实是多条线程交替执行,线程在交替过程中需要损耗一部分性能,由于CPU分配给这些线程执行的时间片非常短,线程交替也非常频繁,因此线程交替是一个比较消耗性能的步骤;
在大部分情况下,多线程的并发能够提升我们程序的执行速度,如:
- 当应用程序需要同时处理多个任务时,每一个任务都需要花费大量的时间,这个时候我们可以开辟多条程序执行线路来并发的"同时"处理多个任务;
- 但是当任务处理时间很短,这个时候根本不需要开启多个线程来"同时"处理多个任务,因为任务处理时间非常短暂,还没等CPU切换到其他线程任务就执行完毕了,这个时候多线程反而使得程序效率低;
这就好比如我们的任务是"烧水",我们需要烧开10壶水,每一壶水的烧开都是一个漫长的时间过程。
- 在单线程环境中:在水烧开的过程中,CPU只能干等着,等第一壶水烧开了后,才可以烧第二壶水,以此类推…这样效率非常慢
- 在多线程环境中:在水烧开的过程中,CPU去分配时间去其他的线程,让其他的线程也来烧水,这样可以让多个水壶同时烧水,效率快;
这样下来,多线程效率更高;
但是现在我们的任务如果变为了"拍蒜",我们需要拍10个蒜,拍一瓣蒜的速度非常快;
- 在单线程环境中:拿起一把刀拍一个蒜,然后马上拍另一瓣蒜…拍10个蒜的时间花费8秒。
- 在多线程环境中:拿起一把刀拍一个蒜,然后马上换另一把刀拍一个蒜…拍10个蒜的时间花费15秒。
这样下来,单线程效率更高;
Tips:在上述案例中,不管是"烧水"还是"拍蒜"都是一个人(CPU核心)在操作多个器具(调度多个线程),如果出现了多个人来同时操作多个器具那就不属于并发的范畴了,而是属于并行;
二、Java中的多线程
2.1 Java线程体验
Java使用java.lang.Thread
类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序代码。
继承Thread类都将变为线程类,调用Thread类中的start()方法即开启线程;当线程开启后,将会执行Thread类中的run方法,因此我们要做的就是重写Thread中的run方法,将线程要执行的任务由我们自己定义;
2.1.1 线程初体验
- 定义线程类:
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro: 继承Thread类称为线程类
*/
public class MyThread extends Thread {
public MyThread() {
}
/**
* 重写父类的构造方法,传递线程名称给父类
*
* @param name
*/
public MyThread(String name) {
super(name);
}
/*
重写run方法,当线程开启后,将执行run方法中的程序代码
*/
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(getName() + "线程正在执行: " + i);
}
}
}
- 测试类:
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
MyThread thread = new MyThread("线程1");
// 开启新的线程
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main线程正执行: " + i);
}
}
}
运行结果:
运行测试代码,观察是否交替执行;如果没有,可能是因为执行任务太少,CPU分配的一点点时间片就足以将线程中的任务全部执行完毕,可以扩大循环次数;观察效果;
2.1.2 线程执行流程
首先程序运行开启main线程执行代码,执行start()
方法时开启一条新的线程来执行任务,新的线程与main线程争夺CPU的执行权在交替执行;
2.2 线程类
2.2.1 常用方法
构造方法:
public Thread()
:分配一个新的线程对象。public Thread(String name)
:分配一个指定名字的新的线程对象。public Thread(Runnable target)
:分配一个带有指定目标新的线程对象。public Thread(Runnable target,String name)
:分配一个带有指定目标新的线程对象并指定名字。
常用方法:
public String getName()
:获取当前线程名称。public void start()
:导致此线程开始执行; Java虚拟机调用此线程的run方法。public void run()
:此线程要执行的任务在此处定义代码。public static void sleep(long millis)
:使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。public static Thread currentThread()
:返回对当前正在执行的线程对象的引用。
我们前面定义线程时说到过,run方法中规定了线程执行的任务,因此我们重写run方法即可;
现在我们翻开run方法的源码看看:
public class Thread implements Runnable {
private volatile String name;
private int priority;
private Thread threadQ;
....
/* What will be run. */
private Runnable target;
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
...
@Override
public void run() {
if (target != null) {
target.run();
}
}
...
}
发现执行的是Runnable对象的run方法,我们打开Runnable查看源码:
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
发现Runnable是个接口,并且只有一个抽象方法run()
@FunctionalInterface:标注此注解的接口只有一个抽象方法,也被称为函数式接口;
2.2.2 使用Runnable创建线程
我们前面翻阅源码得知,Thread执行的run方法实质就是执行Runnable接口中的run方法,因此我们可以传递一个Runnable对象给Thread,此Runnable封装了我们要执行的任务;
采用java.lang.Runnable
也是非常常见的一种,我们只需要重写run方法即可。
步骤如下:
- 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
- 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
- 调用线程对象的start()方法来启动线程;
- 定义Runnable接口:
package com.dfbz.demo02;
/**
* @author lscl
* @version 1.0
* @intro: 创建一个类实现Runnable接口
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
// 获取当前线程对象的引用
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + "执行: " + i);
}
}
}
- 测试类:
package com.dfbz.demo02;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
// 任务对象
MyRunnable runnable = new MyRunnable();
// 将任务对象传递给线程执行
Thread thread = new Thread(runnable,"线程1");
// 开启线程
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main线程执行: " + i);
}
}
}
运行结果:
2.2.3 Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
总结:
实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去共享同一个资源。
- 可以避免java中的单继承的局限性。
- 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
扩充:在Java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用Java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程。
2.2.4 使用匿名内部类创建线程
1)回顾匿名内部类:
- 定义吃辣接口:
package com.dfbz.demo03;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public interface Chili {
void chili();
}
- 定义人类来实现接口并且重写方法:
package com.dfbz.demo03;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Person implements Chili {
@Override
public void chili() {
System.out.println("贵州煳辣椒~");
}
}
- 测试类(不适用匿名内部类):
package com.dfbz.demo03;
/**
* @author lscl
* @version 1.0
* @intro: 不使用匿名内部类
*/
public class Demo01 {
public static void main(String[] args) {
// 需要自己创建一个真实的类(Person),然后重写抽象方法(chili)
Chili chili=new Person();
chili.chili();
}
}
- 使用匿名内部类:
格式如下:
接口名 xxx=new 父类名或者接口名(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};
测试代码:
package com.dfbz.demo03;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02 {
public static void main(String[] args) {
/*
相当于:
class Abc(匿名) implements Chili{
@Override
public void chili() {
System.out.println("余干辣椒~");
}
}
// 多态
Chili abc=new Abc();
*/
// 返回的一个Chili的子类(相当于定义了一个匿名的类,并且创建了这个匿名类的实例对象)
Chili abc = new Chili() { // abc是Chili接口的子类对象
// 重写抽象方法
@Override
public void chili() {
System.out.println("余干辣椒~");
}
};
// 调用重写的方法
abc.chili();
}
}
我们发现可以直接new接口的方式重写其抽象方法,返回一个该接口的子类(该子类是匿名的);
2)使用匿名内部类创建线程
package com.dfbz.demo03;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03 {
public static void main(String[] args) {
/**
相当于:
public class Xxx implements Runnable{
@Override public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程1执行: " + i);
}
}
}
Runnable runnable = new Xxx();
*/
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程1执行: " + i);
}
}
};
// 创建一个线程类,并传递Runnable的子类
Thread thread = new Thread(runnable);
// 开启线程
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main线程: " + i);
}
}
}
2.2.5 使用Lambda表达式创建线程
- 示例代码:
package com.dfbz.demo03;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02 {
public static void main(String[] args) {
// 使用Lambda表达式获取Runnable实例对象
Runnable runnable = () -> {
for (int i = 0; i < 1000; i++) {
System.out.println("线程1: " + i);
}
};
Thread thread = new Thread(runnable);
thread.run();
for (int i = 0; i < 1000; i++) {
System.out.println("main线程: " + i);
}
}
}
2.3 线程的操作
2.3.1 线程的休眠
public static void sleep(long millis)
:让当前线程睡眠指定的毫秒数
测试代码:
package com.dfbz.demo04;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) {
// 使用匿名内部类开启1个线程
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//当i等于50的时候让当前线程睡眠1秒钟(1000毫秒)
if (i == 50) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}.start();
// 使用匿名内部类开启第2个线程
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}.start();
}
}
在JDK1.5退出了TimeUnit
类,该类可以根据时间单位来对线程进行睡眠操作;
示例代码:
public static void main(String[] args) {
new Thread("线程A"){
@Override
public void run() {
try {
// jdk1.5推出的新的睡眠方法
TimeUnit.SECONDS.sleep(1);
System.out.println("线程A....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
System.out.println("main..");
}
2.3.2 线程的加入
多条线程时,当指定线程调用join方法时,线程执行权交给该线程,必须等到调用join方法的线程执行完全部任务后才会释放线程的执行权,其他线程才有可能争抢到线程执行权;
public final void join()
:让调用join方法的线程在当前线程优先执行,直至调用join方法的线程执行完毕时,再执行本线程;public final void join(long millis)
:让线程执行millis毫秒,然后将线程执行权抛出,给其他线程争抢
1)join方法示例
【示例代码】:
package com.dfbz.demo04;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02 {
public static void main(String[] args) throws InterruptedException {
//创建线程1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程1:" + i);
}
}
});
//创建线程2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程2:" + i);
if (i == 500) {
try {
//当i等于500的时候,让t1线程加入执行,直至执行完毕
// t1.join();
//当i等于500的时候,让t1线程加入执行,执行10毫秒之后交出执行权
t1.join(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
t1.start();
t2.start();
}
}
2)join方法的应用场景
【join方法小案例】:
static int num = 0;
public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=10;
}
};
t1.start();
System.out.println(num); // ?
}
我们在main线程中开启了一个新的线程(t1),t1线程对num进行赋值,然后再main线程中进行打印,很显然num的值为0,因为t1线程的阻塞不会让main线程也阻塞,当t1线程阻塞时,main线程会继续往下执行;
【使用join方法改造】:
static int num = 0;
public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 10;
}
};
try {
// 必须让t1线程执行完毕才能执行下面的代码
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num); // 10
}
Tips:join方法一般应用于线程2依赖于线程1执行的返回结果时;
3)join方法注意事项
【注意事项1】:当线程执行join方法传递时间参数时,如果join线程任务执行完毕,则不必等待join时间结束;
static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count = 10;
}
};
long startTime = System.currentTimeMillis();
t1.start();
// 让t1线程执行完毕
// t1.join();
// 让t1线程执行1s,然后代码继续往下执行
t1.join(1000);
// 让t1线程执行3s,但如果t1线程执行完毕了,该方法也会结束
// t1.join(3000);
long endTime = System.currentTimeMillis();
// count【10】,time【2011】
System.out.printf("count【%s】,time【%s】", count, (endTime - startTime));
}
- 执行效果如下:
t1.join();
count【10】,time【2003】
----------------------------------------
t1.join(1000);
count【0】,time【1005】
----------------------------------------
t1.join(3000);
count【10】,time【2006】
【注意事项2】:当线程执行join方法时,优先执行join线程的任务,等到join线程任务执行完毕时才会执行本线程,但如果还有其他线程与执行join方法的线程同时存在时,则其他线程与join线程交替执行;
public static void main1(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
};
Thread t2 = new Thread("t2") {
@Override
public void run() {
while (true) {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t3 = new Thread("t3") {
@Override
public void run() {
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
};
t1.start();
t2.start();
t3.start();
}
执行代码,发现t1和t3线程交替执行;
2.3.3 守护线程
当用户线程(非守护线程)运行完毕时,守护线程也会停止执行,但由于CPU运行速度太快,当用户线程执行完毕时,将信息传递给守护线程,会有点时间差,而这些时间差会导致还会执行一点守护线程;
Tips:不管开启多少个线程(用户线程),守护线程总是随着第一个用户线程的停止而停止,例如JVM的垃圾回收器线程就是一个守护线程;
public final void setDaemon(boolean on)
:设置线程是否为守护线程
示例代码:
package com.dfbz.demo04;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo03 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 2000; i++) {
System.out.println("守护线程1: " + i);
}
}
});
//将t1设置为守护线程
t1.setDaemon(true);
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("用户线程2: " + i);
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("用户线程3: " + i);
}
}
});
//开启三条线程,不管是t2还是t3线程执行完毕,守护线程都会停止
t1.start();
t2.start();
t3.start();
}
}
2.3.4 线程优先级
默认情况下,所有的线程优先级默认为5,最高为10,最低为1。优先级高的线程更容易让线程在抢到线程执行权;
通过如下方法可以设置指定线程的优先级:
public final void setPriority(int newPriority)
:设置线程的优先级。
示例代码:
package com.dfbz.demo04;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo04 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程1: " + i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程2: " + i);
}
}
});
//设置优先级
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
}
2.3.5 线程礼让
在多线程执行时,线程礼让,告知当前线程可以将执行权礼让给其他线程,礼让给优先级相对高一点的线程,但仅仅是一种告知,并不是强制将执行权转让给其他线程,当前线程将CPU执行权礼让出去后,也有可能下次的执行权还在原线程这里;如果想让原线程强制让出执行权,可以使用join()方法
public static void yield()
:将当前线程的CPU执行权礼让出来;
示例代码:
package com.dfbz.demo04;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo05 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("线程1: " + i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i == 10) {
//当i等于10的时候该线程礼让(礼让之后有可能下次线程执行权还被线程2抢到了)
Thread.yield();
}
System.out.println("线程2: " + i);
}
}
});
t1.start();
t2.start();
}
}
2.3.6 线程中断
1)interrupt中断线程
public void interrupt()
:将当前线程中断执行,并且将线程的中断标记设置为true;但是需要注意,如果被中断的线程正在sleep、wait、join等操作,那么将会出现InterruptedException
异常,并且清空打断标记(此时打断标记还是false);public boolean isInterrupted()
:获取当前线程的中断标记;
示例代码:
package com.dfbz.demo01;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo13_线程中断 {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
while (true) {
System.out.println("t1: " + Thread.currentThread().isInterrupted());
}
}, "t1");
t1.start();
Thread.sleep(10);
t1.interrupt(); // 中断线程,将中断状态设置为true
System.out.println(t1.isInterrupted()); // true
}
}
Tips:中断线程并且不是将线程停止,只是将线程的中断标记设置为true;
借助中断标记,我们可以采用如下的方式来优雅的停止线程:
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
while (true) {
// 获取当前线程的中断标记
boolean interrupted = Thread.currentThread().isInterrupted();
if (interrupted) {
System.out.println("线程被中断【" + interrupted + "】....");
System.out.println("释放资源....");
break;
} else {
System.out.println("执行任务【" + interrupted + "】.....");
}
}
}, "t1");
t1.start();
Thread.sleep(10);
t1.interrupt(); // 中断线程,将中断状态设置为true
}
2)中断线程的其他情况
需要注意的是,被中断的线程如果正在处于sleep、wait、join等操作中,将会抛出InterruptedException
异常,然后清空打断标记(此时打断标记还是false);
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
// 启动线程
t1.start();
Thread.sleep(50);
// 中断t1线程,将中断标记设置为true(但此时t1线程正在sleep,因此线程会出现异常,并且中断标记还是false)
t1.interrupt();
System.out.println(t1.isInterrupted());
}
2.3.7 线程的其他方法
1)线程退出
public final void stop()
:退出当前线程
示例代码:
package com.dfbz.demo04_线程的其他操作;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01_线程的退出 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
System.out.println("hello【" + i + "】");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
t1.start();
Thread.sleep(2000);
// 退出线程
t1.stop();
System.out.println("end");
}
}
2)线程挂起
public final void suspend()
:暂停当前线程的执行;public final void resume()
:恢复被暂停的线程;
示例代码:
package com.dfbz.demo04_线程的其他操作;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo02_线程的挂起与恢复 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
System.out.println("hello【" + i + "】");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
t1.start();
Thread.sleep(2000);
// 挂起线程
t1.suspend();
System.out.println("线程挂起...");
Thread.sleep(2000);
t1.resume();
System.out.println("线程恢复....");
}
}
2.4 Callable实现线程
2.4.1 Callable的使用
我们前面学习过,Thread是Java中的线程类,Runnable接口封装了线程所要执行的任务;当线程开启后(调用start方法)则会执行Runnable中的run方法;Callable适用于执行某个任务后需要有返回值响应的情况。例如发送短信是否成功、订单是否更新成功、发起远程调用响应的结果等…
- Callable使用示例:
package com.dfbz.demo02;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) throws Exception {
// 创建一个Callable任务
MyCallable myCallable = new MyCallable();
// 封装成task(线程要执行的任务,最终会执行task里面封装的Callable里面的任务)
FutureTask<String> task1 = new FutureTask<>(myCallable);
FutureTask<String> task2 = new FutureTask<>(myCallable);
// 开启线程执行任务
new Thread(task1).start();
new Thread(task2).start();
// 获取任务执行结果(会造成线程阻塞,必须等线程任务完全执行完毕才会有结果返回)
Object result_1 = task1.get();
Object result_2 = task2.get();
System.out.println("执行结果:【" + result_1 + "】");
System.out.println("执行结果:【" + result_2 + "】");
}
}
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + "【" + i + "】");
}
return "执行任务成功!";
}
}
2.4.2 Callable案例
创建API类,分别提供发送短信方法、文件下载方法;使用异步(使用多线程)和非异步方式(不使用多线程),查看执行效率;
package com.dfbz.demo02;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author lscl
* @version 1.0
* @intro:
*/
public class Demo01 {
public static void main(String[] args) throws Exception {
// 没有使用多线程异步调用
sync();
// async();
}
// 异步调用
public static void async() throws ExecutionException, InterruptedException {
long startTime = System.currentTimeMillis();
Api api = new Api();
// 发送短信的任务
Callable<String> msgCallable = new Callable<String>() {
@Override
public String call() {
String result = api.sendMsg();
return result;
}
};
// 下载文件的任务
Callable<String> uploadCallable = new Callable<String>() {
@Override
public String call() {
String result = api.sendMsg();
return result;
}
};
// 封装成Task
FutureTask<String> msgTask = new FutureTask<String>(msgCallable);
FutureTask<String> uploadTask = new FutureTask<String>(uploadCallable);
// 执行任务
new Thread(msgTask).start();
new Thread(uploadTask).start();
// 获取线程任务执行的结果集
String msgResult = msgTask.get();
String uploadResult = msgTask.get();
System.out.println("发送短信:【" + msgResult + "】");
System.out.println("下载文件:【" + uploadResult + "】");
long endTime = System.currentTimeMillis();
System.out.println("花费时间:【" + (endTime - startTime) + "】");
}
// 同步调用
public static void sync() {
long startTime = System.currentTimeMillis();
Api api = new Api();
// 发送短信
String msgResult = api.sendMsg();
// 下载文件
String uploadResult = api.upload();
System.out.println("发送短信:【" + msgResult + "】");
System.out.println("下载文件:【" + uploadResult + "】");
long endTime = System.currentTimeMillis();
System.out.println("花费时间:【" + (endTime - startTime) + "】");
}
}
class Api {
/**
* 模拟发送短信
*
* @return
*/
public String sendMsg() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "短信发送成功!";
}
/**
* 模拟下载文件
*
* @return
*/
public String upload() {
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "文件下载成功!";
}
}