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全景助力数字乡村建设
一、数字乡村建设加速经济发展随着数字化建设的推进,数字化农业产业正在成为农业产业发展的主导力量,因此数字化技术赋予农业产业竞争力的能力不可小觑。数字化乡村建设背景下,数字化信息技术将全面改造升级农村产业,从农业、养殖…...
【数据结构入门】-链表之双向循环链表
个人主页:平行线也会相交 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【数据结构初阶(C实现)】 文章目录链表初始化打印链表尾插尾删新建一个节点头插头删查找在pos之前插入*删除pos位…...
Jenkins自动化部署入门
Jenkins自动化部署入门 一、简介 Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能。 Jenkins自动化部署实现原理 二、Jenkins部…...
Springboot 读取模板excel信息内容并发送邮件, 并不是你想想中的那么简单
Springboot 读取模板excel信息内容并发送邮件 背景技术选型搭建过程数据加密隐藏问题暴露背景追溯解决背景 在我们日常开发中, 会遇到这样一种场景, 就是读取表格中的数据, 并将数据以附件的形式通过邮箱发送到表格中的每个人 即: excel 读取 excel 写入 发送邮件(携带附件), 例…...
蓝桥杯真题31日冲刺 |第一天
蓝桥杯真题31日冲刺 |第一天 一:完全平方数 题目:[链接](完全平方数 - 蓝桥云课 (lanqiao.cn)) 思路: 将 每个 完全平方数都 消掉,剩下的就是 不能构成平方的数 以12 为例: 所以 12 只要再 乘个三 即可满足 代…...
STM32开发(18)----CubeMX配置RTC
CubeMX配置RTC前言一、什么是RTC?RTC时钟源RTC备份域二、实验过程1.CubeMX配置2.代码实现3.实验结果总结前言 本章介绍使用STM32CubeMX对RTC进行配置的方法,RTC的原理、概念和特点,配置各个步骤的功能,并通过实验方式验证。 一、…...
Qt 单例模式第一次尝试
文章目录摘要单例模式如何使用Qt 的属性系统总结关键字: Qt、 单例、 的、 Q_GLOBAL_STATIC、 女神节摘要 世界上第一位电脑程序设计师是名女性:Ada Lovelace (1815-1852)是一位英国数学家兼作家,她是第一位主张计算机不只可以用来算数的人…...
C语言--一维数组
数组概念 数组:是一种构造数据类型,用以处理批量的同种类型的数据。 主要特点:数据量大 ,类型相同 一维数组的定义 语法: 类型说明符 数组名[整型常量表达式]; 注意: 方括号里面的内容用于指…...
DataGear 4.5.1 发布,数据可视化分析平台
DataGear 4.5.1 发布,严重 BUG 修复,具体更新内容如下: 修复:修复SQL数据集对于DB2、SQLite等数据源预览时会报错的BUG;修复:修复系统对于MySQL、MariaDB等数据源中无符号数值类型有时报错的BUG࿱…...
Springboot——@valid 做字段校验和自定义注解
文章目录前言注意实现测试环境验证自带的注解自定义valid注解自定义注解和处理类创建参数接收类,并增加字段注解接口中使用自测环节正常测试异常测试自定义全局异常监听扩展递归参数下valid不识别的坑前言 再项目开发中,针对前端传递的参数信息…...
c语言基础练习题详解
💞💞 1.C语言程序的基本单位是(C)。 A.程序行 B. 语句 C. 函数 D.字符 💞💞 2.已知各变量的类型说明如下: int m6,n,a,b; unsigned long w8;…...
做soho建立网站/seo课程培训机构
在Vs2005中新建一个Web项目,添加两个Web窗体(Default、Default2),在Default窗体上添加两个标准控件,一个TextBox(TextBox1)、一个Button(Button1),设置Button…...
大连网站制作推广/常用的网络推广手段有哪些
启动和关闭服务指令 启动:redis-server.exe --service-start 关闭:redis-server.exe --service-stop 下面也可以 redis-server --service-start redis-server --service-stop 卸载服务 指令:redis-server --service-uninstall...
wordpress会员文章/google关键词工具
云计算带来的是IT产业的转型和升级。不仅各个微观经济实体成为了云计算产业链中的参与者,各国政府也同样重视这一产业的重要变革。 毕竟,就如同制造业的变革导致了全球范围的重新分工,云计算的出现也将引发IT产业在世界范围内的再分工。 …...
网站站群/网络营销出来做什么
这次学习NetworkRepresentation Learning with Rich Text Information这篇论文,是关于embedding方面的。 1 摘要 表示学习已经在很多项目任务中表现出了它的功效,比如图像识别或文本采集。网络表示学习旨在对于每个节点的进行矢量表示,这种…...
企业网站登录入口官网/黄山seo推广
//得到导入表的信息:导入库名,导入函数ID,导入函数地址 void CPe32 ::GetImportTableInfo() {if (m_bSuccess){//空结尾的导入表结构成员IMAGE_IMPORT_DESCRIPTOR zero {0};//得到导入目录结构IMAGE_DATA_DIRECTORY ImportDataDirectory m_pPeHeader->OptionalHeader.Dat…...
县城做网站/线上营销模式有哪些
视频地址:尚硅谷大数据技术之Scala入门到精通教程(小白快速上手scala)_哔哩哔哩_bilibili 尚硅谷大数据技术Scala教程-笔记01【Scala课程简介、Scala入门、变量和数据类型、运算符、流程控制】尚硅谷大数据技术Scala教程-笔记02【函数式编程】…...