Java多线程还不会的进来吧,为你量身打造
💗推荐阅读文章💗
- 🌸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方法中的程序代码*/@Overridepublic 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);}...@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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 父类名或者接口名(){// 方法重写@Overridepublic 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{@Overridepublic void chili() {System.out.println("余干辣椒~");}}// 多态Chili abc=new Abc();*/// 返回的一个Chili的子类(相当于定义了一个匿名的类,并且创建了这个匿名类的实例对象)Chili abc = new Chili() { // abc是Chili接口的子类对象// 重写抽象方法@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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"){@Overridepublic 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 {//创建线程1Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("线程1:" + i);}}});//创建线程2Thread t2 = new Thread(new Runnable() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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") {@Overridepublic void run() {while (true) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName());}}};Thread t2 = new Thread("t2") {@Overridepublic void run() {while (true) {try {t1.join();} catch (InterruptedException e) {e.printStackTrace();}}}};Thread t3 = new Thread("t3") {@Overridepublic 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() {@Overridepublic void run() {for (int i = 0; i < 2000; i++) {System.out.println("守护线程1: " + i);}}});//将t1设置为守护线程t1.setDaemon(true);Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("用户线程2: " + i);}}});Thread t3 = new Thread(new Runnable() {@Overridepublic 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() {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("线程1: " + i);}}});Thread t2 = new Thread(new Runnable() {@Overridepublic 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() {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("线程1: " + i);}}});Thread t2 = new Thread(new Runnable() {@Overridepublic 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(); // 中断线程,将中断状态设置为trueSystem.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> {@Overridepublic 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>() {@Overridepublic String call() {String result = api.sendMsg();return result;}};// 下载文件的任务Callable<String> uploadCallable = new Callable<String>() {@Overridepublic String call() {String result = api.sendMsg();return result;}};// 封装成TaskFutureTask<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 "文件下载成功!";}
}
相关文章:
Java多线程还不会的进来吧,为你量身打造
💗推荐阅读文章💗 🌸JavaSE系列🌸👉1️⃣《JavaSE系列教程》🌺MySQL系列🌺👉2️⃣《MySQL系列教程》🍀JavaWeb系列🍀👉3️⃣《JavaWeb系列教程》…...
8 神经网络及Python实现
1 人工神经网络的历史 1.1 生物模型 1943年,心理学家W.S.McCulloch和数理逻辑学家W.Pitts基于神经元的生理特征,建立了单个神经元的数学模型(MP模型)。 1.2 数学模型 ykφ(∑i1mωkixibk)φ(WkTXb)y_{k}\varphi\left(\sum_{i1…...
使用QIS(Quantum Image Sensor)图像重建总结(1)
最近看了不少使用QIS重建图像的文章,觉得比较完整详细的还是Abhiram Gnanasambandam的博士论文:https://hammer.purdue.edu/articles/thesis/Computer_vision_at_low_light/20057081 1 介绍 讲述了又墨子的小孔成像原理,到交卷相机…...
【SpringCloud】SpringCloud教程之Nacos实战(二)
目录前言一.Nacos实现配置管理二.Nacos拉取配置三.Nacos配置热更新(自动刷新,不需要重启服务)1.在有Value注入变量所在类添加注解2.新建类用于属性加载和配置热更新四.Nacos多环境配置共享1.多环境共享配置2.配置的加载优先级测试3.配置优先级前言 Nacos实战一&…...
利用Qemu工具仿真ARM64平台
Windows系统利用Qemu仿真ARM64平台0 写在最前1 Windows安装Qemu1.1 下载Qemu1.2 安装Qemu1.3 添加环境变量1.4测试安装是否成功2. Qemu安装Ubuntu-Server-Arm-642.1 安装前的准备2.2 安装Ubuntu server arm 64位镜像3 Windows配置Qemu网络和传输文件3.1 参考内容3.2 Windows安装…...
【Hello Linux】进程控制 (内含思维导图)
作者:小萌新 专栏:Linux 作者简介:大二学生 希望能和大家一起进步! 本篇博客简介:简单介绍下进程的控制 包括进程启动 进程终止 进程等待 进程替换等概念 进程控制介绍进程创建fork函数fork函数的返回值fork函数的使用…...
嵌入式linux物联网毕业设计项目智能语音识别基于stm32mp157开发板
stm32mp157开发板FS-MP1A是华清远见自主研发的一款高品质、高性价比的Linux单片机二合一的嵌入式教学级开发板。开发板搭载ST的STM32MP157高性能微处理器,集成2个Cortex-A7核和1个Cortex-M4 核,A7核上可以跑Linux操作系统,M4核上可以跑FreeRT…...
【黄河流域公安院校网络空间安全技能挑战赛】部分wp
文章目录webbabyPHPfunnyPHPEzphp**遍历文件目录的类**1、DirectoryIterator:2、FilesystemIterator:3、**Globlterator**读取文件内容的类:SplFileObjectMisc套娃web babyPHP <?php highlight_file(__FILE__); error_reporting(0);$num $_GET[nu…...
五点CRM系统核心功能是什么
很多企业已经把CRM客户管理系统纳入信息化建设首选,用于提升核心竞争力,改善企业市场、销售、服务、渠道和客户管理等几个方面,并进行创新或转型。CRM系统战略的五个关键要点是:挖掘潜在客户、评估和培育、跟进并成交、分析并提高…...
window.print() 前端实现网页打印详解
目录 前言 一、print()方法 二、打印样式 2.1使用打印样式表 2.2使用媒介查询 2.3内联样式使用media属性 2.4在css中使用import引入打印样式表 三、打印指定区域部分内容 3.1方法一 3.2方法二 3.3方法三 四、强制插入分页 4.1page-break-before(指定元素前…...
php程序员应具有的7种能力
php程序员应具有什么样的能力,才能更好的完成工作,才会有更好的发展方向呢?在中国我想您不会写一辈子代码的,那样不可能,过了黄金期,您又怎么办呢?看了本文后,希望对您有所帮助。 一…...
quarkus 生产环境与k8s集成总结
quarkus 生产环境与k8s集成总结 大纲 基础准备quarkus2.13.7脚手架工程配置GraalVM-java11 安装配置配置maven3.8.7linux环境下云原生二进制文件打包环境搭建编译运行quarkus二进制文件quarkus二进制文件制作为docker镜像并运行使用k8s部署quarkus二进制文件 基础准备 生产…...
蓝桥杯训练day2
day21.二分(1)789. 数的范围(2)四平方和(1)哈希表做法(2)二分做法(3)1227. 分巧克力(4)113. 特殊排序(5)1460. 我在哪?2.双指针(1)1238. 日志统计(2)1240. 完全二叉树的权值(3&#…...
为什么99%的程序员都做不好SQL优化?
连接层 最上层是一些客户端和链接服务,包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于 TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程 池的概念,为通过认证安全接入的客户端提供线程。同样…...
Jenkins最新版安装调试
清理旧的jenkins: find / -name jenkins* 一项一项的清理:rm -rf /var/log/jenkins* 下载最新版jenkins镜像:jenkins-redhat-stable安装包下载_开源镜像站-阿里云 上传到服务器: 安装命令: yum install -y jenkins…...
简略说一下go的sync.RWMutex锁
在简略的说之前,首先要对RW锁的结构有一个大致的了解 type RWMutex struct {w Mutex // 写锁互斥锁,只锁写锁,和读锁无关writerSem uint32 // sema锁--用于“写协程”排队等待readerSem uint32 // sema锁--用于“读协程”排队…...
软考马上要报名了,出现这些问题怎么办?
目前,四川、山东、山西、辽宁、河北等地已经率先发布了2023年上半年软考报名通知。 四川:2023年3月13日-4月4日 山东:2023年3月17日9:00-4月3日16:00 山西:2023年3月14日9:00-3月28日11:00 辽宁:2023年3月14日8:30…...
单链表(增删查改)
目录一、什么是单链表?二、单链表的增删查改2.1 结构体变量的声明2.2 申请新结点2.2 链表的头插2.3 链表的尾插2.4 链表的头删2.5 链表的尾删2.6 链表的查找2.7 链表的任意位置后面插入2.8 链表的任意位置后面删除2.9 链表的销毁2.10 链表的打印三、代码汇总3.1 SLi…...
端口复用(bind error: Address already in use 问题)
欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 端口复用专栏:《Linux从小白到大神》《网络编程》 在前面讲解TCP状态转换中提到过一个2MSL…...
数字化引领乡村振兴,VR全景助力数字乡村建设
一、数字乡村建设加速经济发展随着数字化建设的推进,数字化农业产业正在成为农业产业发展的主导力量,因此数字化技术赋予农业产业竞争力的能力不可小觑。数字化乡村建设背景下,数字化信息技术将全面改造升级农村产业,从农业、养殖…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
