Java零基础入门笔记:多线程
前言
本笔记是学习狂神的java教程,建议配合视频,学习体验更佳。
【狂神说Java】Java零基础学习视频通俗易懂_哔哩哔哩_bilibili
第1-2章:Java零基础入门笔记:(1-2)入门(简介、基础知识)-CSDN博客
第3章:Java零基础入门笔记:(3)程序控制-CSDN博客
第4章:Java零基础入门笔记:(4)方法-CSDN博客
第5章:Java零基础入门笔记:(5)数组-CSDN博客
第6章:Java零基础入门笔记:(6)面向对象-CSDN博客
第7章:Java零基础入门笔记:(7)异常-CSDN博客
-
线程
线程简介
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。线程可以与同属一个进程的其他线程共享进程所拥有的全部资源,如内存空间、文件句柄等。然而,线程也有自己的独立属性,例如线程栈、线程ID以及线程的运行状态等。一个进程可以包含多个线程,这些线程可以并发执行,从而提高程序的运行效率和响应速度。
- 程序:指令和数据的有序集合,静态。
- 进程(Process):程序的一次执行过程,动态。是系统资源分配的单位。
- 线程(Thread):一个进程可包含若干线程。线程是cpu调度和执行的单位。
- main()称之为主线程,为系统的入口,用于执行整个程序
-
- 多任务:现实中太多这样同时做多件事情的例子了,看起来是多个任务都在做,其实本质上我们的大脑在同一时间依旧只做了一件事情。
- 多线程:原来是一条路,慢慢因为车太多了,道路阻塞,效率极低。为了提高使用的效率,能够充分利用道路,于是加了多个车道。

-
Process与Thread(程序和线程)
- 程序是静态的指令和数据集合,本身不具备运行含义。当程序运行时,会启动一个进程,进程是程序的动态执行过程,同时也是系统资源分配的基本单位。
- 一个进程中至少包含一个线程,线程是CPU调度和执行的最小单位,真正负责执行任务。线程存在于进程中,而进程是由程序启动的。而main()是主线程。
程序、进程和线程的关系可以总结为:程序是静态的,运行后成为进程,进程内部由线程来执行任务。
-
注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错局。
-
核心概念
- 线程就是独立的执行路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,比如主线程,GC线程
- main()称之为主线程,为系统的入口,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行是由调度器(cpu)安排调度的,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的.
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。
- 线程会带来额外的开销,如CPU调度时间,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
-
线程创建
有以下三种方式Thread, Runnable, Callable
-
继承Thread类
步骤:
- 自定义一个线程类继承Thread类;
- 重写run()方法,编写线程执行体;
- 创建线程对象,调用start()方法启动线程。
下面的代码演示了如何使用多线程
start()方法会调用线程的run()方法,并使线程进入就绪状态,等待 CPU 调度。- 主线程(
main方法所在的线程)会继续执行自己的逻辑 - 直接调用
run()方法不会启动一个新线程,而只是像普通方法一样执行run()中的代码。只有通过start()方法才能启动线程。
package thread.demo1;// //创建线程方式一:继承Thread类,重写run()方法,调用start开始线程
public class TestThread1 extends Thread{public void run(){for (int i = 0; i < 2000; i++) {System.out.println("线程");}}public static void main(String[] args) {TestThread1 testThread1 = new TestThread1(); // 创建一个线程对象testThread1.start(); // 调用start方法开启线程// testThread1.run(); // run()不是多线程for (int i = 0; i < 2000; i++) {System.out.println("主进程");}}
}
当你运行这段代码时,会看到 "线程" 和 "主进程" 的输出是交错的。这是因为 TestThread1 线程和主线程是并发执行的,它们共享 CPU 时间片。中间的部分结果如下:
主进程
主进程
线程
线程
线程
线程
线程
主进程
主进程
主进程
-
多线程下载图片
/*** 多线程并发网络下载器* WebDownloader实现了网络图片下载功能;downloader方法输入为目标链接地址、下载图片文件名;结果为保存对应文件到本地* Thread2类中的run方法调用downloader方法;并且通过start实现多线程并发下载图片* 结果显而易见 执行具有无序性*/
//多线程同步下载图片
import org.apache.commons.io.FileUtils;import java.io.File;
import java.io.IOException;
import java.net.URL;public class Thread2 extends Thread {private String url;private String name;public Thread2(String url, String name) {this.url = url;this.name = name;}@Overridepublic void run() {WebDownloader webDownloader = new WebDownloader();webDownloader.downloader(url, name);System.out.println("文件名" + name);}public static void main(String[] args) {Thread2 thread21 = new Thread2("https://img-blog.csdnimg.cn/2711126564c842689248e53bbf4d85e8.png", "t1.png");Thread2 thread22 = new Thread2("https://img-blog.csdnimg.cn/4b97a32c6d9f4caa8617099065d02e3c.png", "t2.png");Thread2 thread23 = new Thread2("https://img-blog.csdnimg.cn/419537bd2d5e4fdfaa70fb8b8ed17663.png", "t3.png");thread21.start();thread22.start();thread23.start();}
}//下载器
class WebDownloader{//下载方法public void downloader(String url,String name){try {FileUtils.copyURLToFile(new URL(url),new File(name));} catch (IOException e) {e.printStackTrace();System.out.println("io异常,downloader方法出现问题");}}
}
-
实现Runnable接口
推荐使用Runnable对象,因为Java单继承的局限性
- 自定义一个线程类MyRunnable实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
下面的代码演示了如何使用runnable接口创建线程
-
在
main方法中,创建了一个TestThread3对象testThread3,它实现了Runnable接口。 -
通过
Thread类的构造函数new Thread(testThread3)将Runnable实现类对象传递给线程对象。 -
调用
thread.start()方法启动线程。
package thread.demo1;//创建线程方式2:实现Runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法
public class TestThread3 implements Runnable{public void run(){for (int i = 0; i < 20; i++) {System.out.println("线程");}}public static void main(String[] args) {TestThread3 testThread3 = new TestThread3(); // 创建runnable接口的实现类对象Thread thread = new Thread(testThread3); // 创建一个线程对象,通过线程对象开启线程thread.start(); // 通过线程对象开启线程// 上面的代码可以简写为下面的:// new Thread(testThread3).start();for (int i = 0; i < 20; i++) {System.out.println("主进程");}}
}
主进程
主进程
线程
线程
线程
线程
线程
主进程
主进程
主进程
-
下面的代码演示了如何使用多线程下载图片
import org.apache.commons.io.FileUtils;import java.io.File;
import java.io.IOException;
import java.net.URL;//练习Thread,实现多线程同步下载图片
public class TestThread2 implements Runnable{private String url; //网络图片地址private String name; //保存的文件名public TestThread2(String url,String name){this.url = url;this.name = name;}//下载图片线程的集合体public void run(){WebDownLoader webDownLoader = new WebDownLoader();webDownLoader.downloader(url,name);System.out.println("下载的文件名:"+name);}public static void main(String[] args) {TestThread2 t1 = new TestThread2("https://www.cup.edu.cn/images/2022-10/4d0ba385fbe44ed69c3f44ccb4b81b23.jpg", "1.jpg");TestThread2 t2 = new TestThread2("https://www.cup.edu.cn/news/images/2022-10/a0923c114feb4770bef9627d1e698f0a.jpg","2.jpg");TestThread2 t3 = new TestThread2("https://www.cup.edu.cn/images/2021-10/bcde29aad64842ac82fc8abcc32077b2.jpg","3.jpg");new Thread(t1).start();new Thread(t2).start();new Thread(t3).start();}
}//下载器
class WebDownLoader{//下载方法public void downloader(String url,String name){try {FileUtils.copyURLToFile(new URL(url),new File(name));} catch (IOException e) {e.printStackTrace();System.out.println("IO异常,downloader方法出现问题");}}
}
-
Thread和Runnable对比:
- 继承Thread类
- 子类继承THread类具备多线程能力
- 启动线程:子类对象.start();
- 不建议使用:避免OOP单继承局限性
- 实现Runnable接口
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
-
抢票
下面的代码演示了如何使用 高并发模拟买火车票
package thread.demo1;// 多个线程同时操作同一个对象
public class TestThread4 implements Runnable{private int ticketnums = 10; // 票数public void run(){while(true){if(ticketnums <= 0){break;}System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketnums--+"张票");}}public static void main(String[] args) {TestThread4 testThread4 = new TestThread4();new Thread(testThread4, "小明").start();new Thread(testThread4, "老师").start();new Thread(testThread4, "黄牛").start();}
}
在下面的结果中:两个人同时拿到第3张票,这是不对的。
出现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱,出现并发问题。
黄牛-->拿到了第9票
小明-->拿到了第10票
老师-->拿到了第10票
老师-->拿到了第8票
黄牛-->拿到了第7票
小明-->拿到了第8票
黄牛-->拿到了第5票
老师-->拿到了第6票
小明-->拿到了第4票
小明-->拿到了第3票
黄牛-->拿到了第3票
老师-->拿到了第3票
小明-->拿到了第2票
黄牛-->拿到了第2票
老师-->拿到了第1票
-
龟兔赛跑

这段代码模拟了一个“龟兔赛跑”的场景,通过多线程来实现兔子和乌龟的跑步过程,并判断谁是比赛的胜利者
-
兔子在每跑 10 步时会暂停 10 毫秒,模拟“兔子睡觉”的场景。
-
在每一步中调用
gameOver()方法判断比赛是否结束:-
如果已经有胜利者,则终止当前线程。
-
如果当前线程跑完 100 步,则将当前线程的名字设置为胜利者。
-
package thread.demo1;public class Race implements Runnable{private static String winner; // 胜利者public void run(){for (int i = 0; i <= 100; i++) {if (Thread.currentThread().getName().equals("兔子")&&i%10==0){ // 兔子睡觉try{Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}}boolean flag = gameOver(i); // 判断比赛是否结束if (flag){ // 比赛结束就终止break;}System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");}}// 判断是否完成比赛private boolean gameOver(int steps){if (winner != null){ // 判断是否有胜利者return true;}else { // 定义胜利者if (steps==100){winner = Thread.currentThread().getName();System.out.println("winner is "+winner);return true;}}return false;}public static void main(String[] args) {Race race = new Race();new Thread(race, "兔子").start();new Thread(race, "乌龟").start();}
}
乌龟跑了96步
乌龟跑了97步
乌龟跑了98步
乌龟跑了99步
winner is 乌龟
-
实现Callable()接口
步骤:
- 实现Callable接口,需要返回值类型;
- 重写call方法,需要抛出异常;
- 创建目标对象;
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交执行:Future result1 = ser.submit(t1);
- 获取结果:boolean r1 = result1.get()
- 关闭服务:ser.shutdownNow();
callable的好处:可以定义返回值,可以抛出异常
-
下面的代码演示了如何使用多线程下载图片
package lesson1;
//多线程同步下载图片
import org.apache.commons.io.FileUtils;import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;public class TestCallable implements Callable<Boolean> {private String url;private String name;public TestCallable(String url, String name) {this.url = url;this.name = name;}@Overridepublic Boolean call() {WebDownloader webDownloader = new WebDownloader();webDownloader.downloader(url, name);System.out.println("文件名" + name);return true;}public static void main(String[] args) {TestCallable t1 = new TestCallable("https://img-blog.csdnimg.cn/2711126564c842689248e53bbf4d85e8.png", "t1.png");TestCallable t2 = new TestCallable("https://img-blog.csdnimg.cn/4b97a32c6d9f4caa8617099065d02e3c.png", "t2.png");TestCallable t3 = new TestCallable("https://img-blog.csdnimg.cn/419537bd2d5e4fdfaa70fb8b8ed17663.png", "t3.png");// 创建执行服务ExecutorService ser = Executors.newFixedThreadPool(3);// 提交执行Future<Boolean> result1 = ser.submit(t1);Future<Boolean> result2 = ser.submit(t2);Future<Boolean> result3 = ser.submit(t3);// 获取结果boolean rs1 = result1.get();boolean rs2 = result1.get();boolean rs3 = result1.get();// 关闭服务ser.shutdownNow();}
}//下载器
class WebDownloader{//下载方法public void downloader(String url,String name){try {FileUtils.copyURLToFile(new URL(url),new File(name));} catch (IOException e) {e.printStackTrace();System.out.println("io异常,downloader方法出现问题");}}
}
-
静态代理
静态代理是一种设计模式,用于在不改变原有对象(被代理对象)的基础上,通过创建一个代理对象来间接调用被代理对象的方法。代理对象和被代理对象都实现了同一个接口或继承了同一个父类,代理对象在调用被代理对象的方法时,可以在方法调用前后添加额外的逻辑,从而实现对被代理对象行为的增强或扩展。
在静态代理中,代理类和被代理类的关系是在编译时就已经确定的,代理类需要手动编写,不能动态生成。这种方式的优点是简单直观,易于理解和实现,但缺点是灵活性较差,如果被代理类的方法过多或接口频繁变化,代理类也需要相应地进行修改,增加了维护成本。
-
下面的代码演示了如何使用静态代理,这段代码通过一个简单的例子展示了静态代理的设计模式。代理类 WeddingCompany 在调用被代理类 You 的核心方法时,添加了额外的逻辑(布置现场和收尾款),从而扩展了被代理类的行为。
-
接口
Marry:-
定义了一个方法
HappyMarry(),表示结婚的行为。这个接口是被代理类和代理类之间的桥梁,确保它们具有相同的方法签名。
-
-
被代理类
You:-
实现了
Marry接口,具体实现了结婚的行为(HappyMarry()方法),打印出 "结婚!"。
-
-
代理类
WeddingCompany:-
也实现了
Marry接口,但它的主要职责是代理You类的行为。 -
在
HappyMarry()方法中,代理类不仅调用了被代理类的HappyMarry()方法,还在调用前后分别执行了额外的逻辑:-
before()方法:在结婚之前布置现场。 -
after()方法:在结婚之后收尾款。
-
-
代理类通过构造函数接收一个
Marry类型的对象(被代理对象),并将其保存为成员变量target。
-
package thread.demo1;public class StaticProxy {public static void main(String[] args) {WeddingCompany weddingCompany = new WeddingCompany(new You());weddingCompany.HappyMarry();}
}interface Marry{void HappyMarry();
}class You implements Marry{public void HappyMarry(){System.out.println("结婚!");}
}class WeddingCompany implements Marry{private Marry target;public WeddingCompany(Marry target){this.target = target;}@Overridepublic void HappyMarry() {before();this.target.HappyMarry();after();}private void after() {System.out.println("结婚之后,收尾款");}private void before() {System.out.println("结婚之前,布置现场");}
}
结婚之前,布置现场
结婚!
结婚之后,收尾款
静态代理的核心在于不改变被代理类代码的前提下,通过代理类来扩展被代理类的行为。代理类和被代理类实现了相同的接口或继承了相同的父类,代理类在调用被代理类的方法时,可以在方法调用前后添加额外的逻辑,从而实现对被代理类行为的增强或扩展。
在这个例子中:
-
被代理类
You只负责核心的结婚行为(HappyMarry())。 -
代理类
WeddingCompany负责在结婚前后添加额外的服务(布置现场和收尾款)。 -
代理类通过接口与被代理类解耦,使得代理类可以在不修改被代理类代码的情况下,扩展其行为。
-
静态代理模式总结:
- 真实对象和代理对象都要实现同一个接口;
- 代理对象要代理真实角色。
好处:
- 代理对象可以做很多真实对象做不了的事情;
- 真实对象专注做自己的事情。
-
Lambda表达式
为什么要使用lambda表达式
- 避免匿名内部类定义过多
- 使代码简洁。去掉无意义代码,只留下核心逻辑
-
- 函数式接口:只包含一个抽象方法的接口
- 我们可以使用lambda表达式创建函数式接口的对象,lambda表达式是用于简化函数式接口
-
下面的方法使用了Lambda 表达式
-
Lambda 表达式:
() -> { System.out.println("I like lambda!"); }是一个 Lambda 表达式,它实现了LambdaAction接口的lambda()方法。 -
接口类型:Lambda 表达式必须有一个明确的接口类型(这里是
LambdaAction),并且该接口必须是函数式接口(即只有一个抽象方法)。 -
方法调用:通过
like.lambda()调用了 Lambda 表达式中的逻辑。
package thread.demo1;public class TestLambda1 {//3.静态内部类static class Like2 implements ILike{@Overridepublic void lambda() {System.out.println("I like lambda2");}}public static void main(String[] args) {ILike like = new Like();like.lambda();like = new Like2();like.lambda();// 4.局部内部类class Like3 implements ILike{@Overridepublic void lambda() {System.out.println("i like lambda3");}}like = new Like3();like.lambda();//5.匿名内部类like = new Like() {@Overridepublic void lambda() {System.out.println("i like lambda4");}};like.lambda();//6.lambda简化like = ()->{System.out.println("i like lambda6");};like.lambda();}
}// 1. 定义函数接口
interface ILike{void lambda();
}// 2. 实现类
class Like implements ILike{@Overridepublic void lambda() {System.out.println("I like lambda");}
}
I like lambda
I like lambda2
i like lambda3
i like lambda4
i like lambda6
简化
package thread.demo1;public class TestLambda2 {public static void main(String[] args) {ILove love = null;// 1. 含参love = (int a)-> {System.out.println("1");};// 2. 简化括号love = a -> {System.out.println("1");};// 3.一行可以简化花括号love = a -> System.out.println("1");love.love(520);}
}interface ILove{void love(int a);
}
-
线程状态



-
线程停止 stop()
这段代码演示了如何通过一个标志位来控制线程的停止。
-
Teststop类:实现了Runnable接口,用于定义线程的运行逻辑。其中包含一个布尔类型的标志位flag,用于控制线程的运行状态。 -
stop()方法:通过将flag设置为false,使得线程的while循环条件不再满足,从而结束线程。
package thread.state;public class Teststop implements Runnable{// 1. 设置一个标志位private boolean flag = true;public void run(){int i = 0;while (flag){System.out.println("run>>>Thread"+i++);}}// 2. 设置一个公开的方法停止线程,转换标志位public void stop(){this.flag = false;}public static void main(String[] args) {Teststop teststop = new Teststop();new Thread(teststop).start();for (int i = 0; i < 1000; i++) {if (i==900){teststop.stop(); // 调用stop方法切换System.out.println("线程停止了");}}}
}
-
线程休眠 sleep
Thread.sleep() 方法的主要特点和用途:
-
sleep(时间):指定当前线程阻塞的毫秒数,使线程暂停执行指定的时间。 -
异常:
sleep方法可能会抛出InterruptedException,表示线程在睡眠期间被中断。 -
线程状态:睡眠时间结束后,线程会从阻塞状态进入就绪状态,等待 CPU 调度。
-
应用场景:
sleep常用于模拟网络延迟、倒计时等场景。 -
锁的状态:
sleep不会释放线程持有的锁,即使在睡眠期间,线程仍然保持对锁的占用
-
这段代码通过 Thread.sleep() 方法模拟了一个倒计时功能,从 10 开始,每隔一秒打印一个数字,直到倒计时结束。
tenDown() 方法:
-
使用
Thread.sleep(1000)使当前线程暂停 1 秒。 -
每次暂停后,打印当前数字并递减(
num--)。 -
当数字减到 0 或以下时,退出循环,倒计时结束。
package thread.state;// 模拟倒计时
public class TestSleep {public static void tenDown() throws InterruptedException{int num = 10;while(true){Thread.sleep(1000);System.out.println(num--);if (num<=0){break;}}}public static void main(String[] args) {try{tenDown();} catch (InterruptedException e){e.printStackTrace();}}
}
这段代码通过 Thread.sleep() 方法实现了一个简单的倒计时功能,同时打印当前系统时间
- 打印当前系统时间:使用
System.currentTimeMillis()获取当前时间的毫秒值,并通过Date类将其转换为日期对象。使用SimpleDateFormat格式化时间,以HH:mm:ss的格式打印当前时间。 - 倒计时逻辑:在
while (true)循环中,线程每隔 1 秒暂停一次(通过Thread.sleep(1000))。每次暂停后,打印当前时间,并更新时间对象starttime为当前系统时间。
package thread.state;import java.text.SimpleDateFormat;
import java.util.Date;public class TestSleep3 { // 倒计时public static void main(String[] args) {//打印当前系统时间Date starttime = new Date(System.currentTimeMillis());//获取系统当前时间while (true){try {Thread.sleep(1000);System.out.println(new SimpleDateFormat("HH:mm:ss").format(starttime));starttime = new Date(System.currentTimeMillis());//更新当前时间} catch (InterruptedException e) {e.printStackTrace();}}}
}
-
线程礼让 yield()
- 线程礼让的作用:用于让当前正在执行的线程暂停,但不会阻塞线程。它的作用是将线程从运行状态(Running)转变为就绪状态(Runnable),让出 CPU 时间片,以便其他线程可以运行。
-
线程礼让的结果:调用
Thread.yield()后,线程不会进入阻塞状态,而是回到就绪状态,等待 CPU 重新调度。线程礼让不一定会成功,因为线程调度器会根据当前的线程优先级和其他线程的状态来决定是否切换线程。 -
线程礼让的不确定性:线程礼让是否成功取决于 CPU 调度器的行为,因此不能保证每次调用
Thread.yield()都会导致线程切换。
-
这段代码通过 Thread.yield() 方法展示了线程礼让的行为和效果。
-
调用
Thread.yield()方法,让当前线程暂停并回到就绪状态,等待 CPU 重新调度。
package thread.state;public class Testyield {public static void main(String[] args) {MyYield myYield = new MyYield();new Thread(myYield, "a").start();new Thread(myYield, "b").start();}
}class MyYield implements Runnable{public void run(){System.out.println(Thread.currentThread().getName()+"开始运行");Thread.yield(); // 礼让System.out.println(Thread.currentThread().getName()+"开始运行");}
}
礼让成功:
a线程开始执行
b线程开始执行
b线程停止执行
a线程停止执行
-
线程插队join()
- join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
- 可以想象成插队
-
这段代码通过 Thread.join() 方法展示了线程插队(等待)的行为。
-
当主线程循环到第 200 次时,调用
thread.join()方法,主线程会等待thread线程执行完毕后才继续执行。
package thread.state;public class TestJoin implements Runnable{public void run(){for (int i = 0; i < 100; i++) {System.out.println("vip来了");}}public static void main(String[] args) throws InterruptedException{// 启动我们的线程TestJoin testJoin = new TestJoin();Thread thread = new Thread(testJoin);thread.start();for (int i = 0; i < 1000; i++) {if(i==200){thread.join(); // 插队}System.out.println("main"+i);}}
}
1
线程状态观测
- NEW 尚未启动的线程处于此状态。
- RUNNABLE 在Java虚拟机中执行的线程处于此状态。
- BLOCKED 被阻塞等待监视器锁定的线程处于此状态。
- WAITING 正在等待另一个线程执行特定动作的线程处于此状态。
- TIMED_WAITING 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
- TERMINATED 已退出的线程处于此状态。
-
这段代码通过 Thread.getState() 方法展示了线程在不同阶段的状态变化。
-
在主线程中,通过
thread.getState()获取并打印线程的当前状态。线程状态的观察分为三个阶段:-
线程初始化后(未启动):打印初始状态。
-
线程启动后:打印启动后的状态。
-
线程运行过程中:每隔 100 毫秒检查并打印线程状态,直到线程结束。
-
package thread.state;public class TestState {public static void main(String[] args) throws InterruptedException{Thread thread = new Thread(()->{for (int i = 0; i < 5; i++) {try {Thread.sleep(1000);}catch (InterruptedException e){e.printStackTrace();}}System.out.println("///");});// 观察状态Thread.State state = thread.getState();System.out.println(state);// 观察启动后的thread.start();state = thread.getState();System.out.println(state);while (state != Thread.State.TERMINATED){ // 只要线程不停止,就一直输出Thread.sleep(100);state = thread.getState(); // 更新线程状态System.out.println(state); // 输出状态}}
}
NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING...TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
RUNNABLE
///
TERMINATED
-
线程优先级 priority
线程优先级用数字表示,范围1~10,优先级越高,越可能被CPU调用
- Thread.MAX_PRIORITY=10
- Thread.NORM_PRIORITY=5
- Thread.MIN_PRIORITY=1
-
获得优先级的方法:getPriority()
改变优先级的方法:setPriority(int xxx)
-
注意:
- 优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度;(先走性能低的会导致性能倒置)
- 优先级的设定建议在start()调度之前。
-
这段代码展示了如何设置和获取线程的优先级,并观察不同优先级线程的行为。
package thread.state;public class TestPriority {public static void main(String[] args) {// 主线程默认优先级 // main的优先级为5System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());MyPriority myPriority = new MyPriority();Thread t1 = new Thread(myPriority);Thread t2 = new Thread(myPriority);Thread t3 = new Thread(myPriority);Thread t4 = new Thread(myPriority);// 设置优先级再启动t1.start(); // 默认为1t2.setPriority(1);t2.start();t3.setPriority(4);t3.start();t4.setPriority(Thread.MAX_PRIORITY);t4.start();}
}class MyPriority implements Runnable{public void run(){System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());}
}
main--->5
Thread-1--->1
Thread-0--->5
Thread-2--->4
Thread-3--->10
-
守护线程 daemon
线程分为用户线程和守护线程
守护线程与用户线程的区别:
-
用户线程:默认情况下,线程是用户线程。用户线程是程序的主要线程,它们的生命周期由程序控制。程序会等待所有用户线程执行完毕后才会退出。
-
守护线程:通过
setDaemon(true)设置为守护线程。守护线程的生命周期依赖于用户线程。当所有用户线程执行完毕后,守护线程会自动终止。守护线程通常用于为用户线程提供服务,例如垃圾回收线程。
守护线程的行为:守护线程的逻辑是无限循环,但程序不会因为守护线程的无限循环而无法退出。当所有用户线程执行完毕后,守护线程会被自动终止,程序也会退出。
线程的生命周期:用户线程会正常运行,直到完成所有任务。守护线程会持续运行,但会在程序退出时自动终止。
守护线程如:后台记录操作日志,监控日志,垃圾回收等待……
-
这段代码通过 Thread.setDaemon() 方法展示了守护线程(Daemon Thread)和用户线程(User Thread)的区别。
-
创建了一个线程
thread,并将其设置为守护线程(通过thread.setDaemon(true))。守护线程的逻辑是通过God类实现的,线程会无限循环打印"上帝保护你"。 -
创建了一个用户线程(默认为用户线程),逻辑通过
You类实现。用户线程会循环 36500 次,打印"你一生都开心的活着"。
package thread.state;public class TestDaemon {public static void main(String[] args) {God god = new God();You you = new You();Thread thread = new Thread(god);thread.setDaemon(true); // 默认是false表示用户线程,正常线程都是用户线程thread.start();new Thread(you).start();}
}// 上帝
class God implements Runnable{@Overridepublic void run(){while(true){System.out.println("上帝保护你");}}
}// 你
class You implements Runnable{@Overridepublic void run() {for (int i = 0; i <= 36500; i++) {System.out.println("你一生都开心的活着");}}
}
-
线程同步(重点)
介绍
并发:同一个对象被多个线程同时操作
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
-
实现同步需要队列和锁
线程同步形成条件:队列+锁(synchronized)
- 优点:确保安全
- 缺点:挂起;性能问题(上下文切换 调度 优先级倒置)
-
三大不安全案例
这段代码模拟了一个多线程环境下的售票场景,展示了线程不安全问题。
-
多个线程共享同一个
BuyTicket实例,同时访问和修改ticketsNums。 -
buy()方法中对ticketsNums的操作没有加锁保护,导致多个线程可能同时修改票数,从而出现票数重复或错误的情况。
package thread.state;public class UnSafeBuyTicket {public static void main(String[] args) {BuyTicket station = new BuyTicket();new Thread(station,"小明").start();new Thread(station,"小红").start();new Thread(station,"黄牛党").start();}
}class BuyTicket implements Runnable{private int ticketsNums = 10; //票boolean flag = true;@Overridepublic void run() {while(flag){ // 买票try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}buy();}}private void buy(){if (ticketsNums<=0){ // 判断是否有票flag = false;return;}//买票System.out.println(Thread.currentThread().getName()+"拿到"+ticketsNums--);}
}
输出结果可能会出现票数重复的情况,多个线程同时访问和修改共享资源 ticketsNums,导致线程不安全问题。
黄牛党拿到5
小明拿到5
小红拿到4
小明拿到4
这段代码模拟了一个银行取钱的场景,展示了多线程环境下可能出现的线程不安全问题。
-
由于两个线程同时操作同一个账户,可能会出现账户余额为负数的情况(例如
-50),这是典型的线程不安全问题。
package thread.state;public class UnSafeBank {public static void main(String[] args) {//一个账户Account account=new Account(100,"结婚基金");//两个人取同一个账户里的钱Withdraw man=new Withdraw(account,100,"男方");Withdraw woman=new Withdraw(account,50,"女方");man.start();woman.start();}
}class Account{ //账户int money;//余额String name;//账户名public Account(int money, String name) {this.money = money;this.name = name;}
}class Withdraw extends Thread{ //银行:模拟取钱操作Account account;//账户int withdrawMoney;//要取的钱int nowMoney;//取到人手里的钱public Withdraw(Account account,int withdrawMoney,String name){//name:线程名super(name);this.account=account;this.withdrawMoney=withdrawMoney;}@Overridepublic void run() { //取钱if(account.money-withdrawMoney<0) {System.out.println("账户余额不足,取钱失败");return;}//用sleep放大多线程可能出现的问题try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}account.money-=withdrawMoney;nowMoney+=withdrawMoney;//这里的this就是Thread.currentThread()System.out.println(this.getName()+"取完"+account.name+"账户里的钱后,现在手里的钱为"+nowMoney);System.out.println(account.name+"的账户余额为"+account.money);}
}
由于两个线程同时操作同一个账户,可能会出现账户余额为负数的情况(例如 -50),这是典型的线程不安全问题。
女方取完结婚基金账户里的钱后,现在手里的钱为50
男方取完结婚基金账户里的钱后,现在手里的钱为100
结婚基金的账户余额为-50
结婚基金的账户余额为-50
这段代码通过多线程向一个共享的 ArrayList 中添加元素,展示了多线程环境下可能出现的线程不安全问题。
-
数据丢失:多个线程可能同时尝试将元素添加到同一个位置,导致某些元素被覆盖或丢失。
package thread.state;import java.util.ArrayList;
import java.util.List;//(这个集合的例子举的不好)
public class UnsafeList {public static void main(String[] args) throws InterruptedException {List<String> list=new ArrayList<String>();for (int i = 0; i < 100; i++) {new Thread(()->{ list.add(Thread.currentThread().getName());}).start();}Thread.sleep(200); // sleep是为了让主线程的System.out.println(list.size()); //在其他线程都运行完再输出(这个集合的例子举的不好)// 打印出来不一定是100,可能小于00,因为多个线程并发时,可能同时执行list.add(),将两个线程添加到同一内存空间}
}
-
同步方法
每个对象对应一把锁
线程若想调用synchronized方法/块,必须获得synchronized所修饰对象的锁,否则线程阻塞;synchronized方法/块一旦执行就独占该锁,直到执行完才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
-
synchronized的三种应用方式
synchronized关键字最主要有以下3种应用方式,下面分别介绍
- 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
- 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
- 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
-
我们以方法举例可以有如下的几种情况:
修饰实例方法, 这里默认的锁就是当前类的实例对象
public synchronized void t0(){// 方法体}
修饰静态方法 , 这里默认的锁就是当前类的class对象
public synchronized static void t0(){// 方法体}
修饰代码块,锁一个对象
public void t2() {synchronized (o) {// 方法体}}
锁一个对象的class
public void t3() {synchronized (o.getClass()) {// 方法体}}
-
总结:
有两种锁,对象锁和Class锁。
- Class锁允许所有的Class相同的对象都拿到资源,
- 对象锁就单纯的只有这个对象才能获取到锁。
静态都是锁类的 class。其他的时候我们都是习惯于单纯的锁对象。
-
synchronized用法
synchronized方法:用synchronized修饰方法
eg:public synchronized void method(){}
synchronized块:synchronized(Obj){}
-
案例
不安全的买票:锁 buyTicket
-
使用
synchronized修饰buy(),确保同一时间只有一个线程可以执行该方法。
package thread.state;public class UnSafeBuyTicket {public static void main(String[] args) {BuyTicket station = new BuyTicket();new Thread(station,"小明").start();new Thread(station,"小红").start();new Thread(station,"黄牛党").start();}
}class BuyTicket implements Runnable{private int ticketsNums = 10; //票boolean flag = true;@Overridepublic void run() {while(flag){ // 买票try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}buy();}}private synchronized void buy(){if (ticketsNums<=0){ // 判断是否有票flag = false;return;}//买票System.out.println(Thread.currentThread().getName()+"拿到"+ticketsNums--);}
}
不安全的银行:锁 account
-
使用
synchronized (account)同步代码块对共享资源(Account对象)进行保护。
package thread.state;public class UnSafeBank {public static void main(String[] args) {//一个账户Account account=new Account(100,"结婚基金");//两个人取同一个账户里的钱Withdraw man=new Withdraw(account,100,"男方");Withdraw woman=new Withdraw(account,50,"女方");man.start();woman.start();}
}class Account{ //账户int money;//余额String name;//账户名public Account(int money, String name) {this.money = money;this.name = name;}
}class Withdraw extends Thread{ //银行:模拟取钱操作Account account;//账户int withdrawMoney;//要取的钱int nowMoney;//取到人手里的钱public Withdraw(Account account,int withdrawMoney,String name){//name:线程名super(name);this.account=account;this.withdrawMoney=withdrawMoney;}@Overridepublic void run() { //取钱synchronized (account){if(account.money-withdrawMoney<0) {System.out.println("账户余额不足,取钱失败");return;}//用sleep放大多线程可能出现的问题try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}account.money-=withdrawMoney;nowMoney+=withdrawMoney;//这里的this就是Thread.currentThread()System.out.println(this.getName()+"取完"+account.name+"账户里的钱后,现在手里的钱为"+nowMoney);System.out.println(account.name+"的账户余额为"+account.money);}}
}
不安全的集合:锁 list
-
使用
synchronized (list)同步代码块保护了对list的操作,确保每次只有一个线程可以执行list.add()。
package thread.state;import java.util.ArrayList;
import java.util.List;//(这个集合的例子举的不好)
public class UnsafeList {public static void main(String[] args) throws InterruptedException {List<String> list=new ArrayList<String>();for (int i = 0; i < 100; i++) {new Thread(()->{synchronized (list){list.add(Thread.currentThread().getName());}}).start();}Thread.sleep(200); // sleep是为了让主线程的System.out.println(list.size()); //在其他线程都运行完再输出(这个集合的例子举的不好)// 打印出来不一定是100,可能小于00,因为多个线程并发时,可能同时执行list.add(),将两个线程添加到同一内存空间}
}
-
JUC
JUC 是 Java Util Concurrency 的缩写,它是 Java 标准库中的一个包,位于 java.util.concurrent 包下。JUC 包提供了大量用于并发编程的工具类和数据结构,帮助开发者更高效地编写多线程程序。
JUC提供的CopyOnWriteArrayList,其本身就是安全的,和上面的效果一样
package thread.state;import java.util.concurrent.CopyOnWriteArrayList;public class TestJUC {public static void main(String[] args) {CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();for (int i = 0; i <10000; i++) {new Thread(()->{list.add(Thread.currentThread().getName());}).start();}try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(list.size());}
}
-
死锁
死锁产生的四个必要条件(缺一不可)
- 互斥条件
- 请求与保持条件
- 不剥夺条件
- 循环等待条件
以下程序会死锁:
-
灰姑娘先获取了口红的锁,然后尝试获取镜子的锁。白雪公主先获取了镜子的锁,然后尝试获取口红的锁。如果灰姑娘在获取口红后暂停(通过
Thread.sleep(1000)),而白雪公主在此期间获取了镜子,那么灰姑娘会等待白雪公主释放镜子,而白雪公主会等待灰姑娘释放口红。这种互相等待对方释放资源的状态就是死锁。
package thread.state;//死锁,多个线程互相抱着对方需要的资源,然后形成僵持
public class DeadLock {public static void main(String[] args) {MakeUp g1 = new MakeUp(0, "灰姑凉");MakeUp g2 = new MakeUp(1, "白雪公主");g1.start();g2.start();}
}//口红
class Lipstick {}//镜子
class Mirror {}class MakeUp extends Thread {static Lipstick lipstick = new Lipstick();static Mirror mirrror = new Mirror();int choice;//选择String girlName;//选择化妆品的人MakeUp(int choice, String girlName) {this.choice = choice;this.girlName = girlName;}@Overridepublic void run() {//化妆try {makeup();} catch (InterruptedException e) {throw new RuntimeException(e);}}//化妆,互相持有对方的锁,就是需要拿到对方的资源private void makeup() throws InterruptedException {if (choice == 0) {synchronized (lipstick) {//获得口红的锁System.out.println(this.girlName + "获得口红的锁");Thread.sleep(1000);synchronized (mirrror) { //一秒钟后想获得镜子System.out.println(this.girlName + "获得镜子的锁");}}} else {synchronized (mirrror) { //获得镜子的锁System.out.println(this.girlName + "获得镜子的锁");Thread.sleep(1000);synchronized (lipstick) { //一秒钟后想获得口红System.out.println(this.girlName + "获得口红的锁");}}}}
}
灰姑凉获得口红的锁
白雪公主获得镜子的锁
// (然后陷入僵持...)
如何解决这个问题呢,上面的情况是两人各握着两个锁,所以解决方案是,把另一个想要的锁放在外面
private void makeup() throws InterruptedException {if (choice == 0) {synchronized (lipstick) {//获得口红的锁System.out.println(this.girlName + "获得口红的锁");Thread.sleep(1000);}synchronized (mirrror) { //一秒钟后想获得镜子System.out.println(this.girlName + "获得镜子的锁");}} else {synchronized (mirrror) { //获得镜子的锁System.out.println(this.girlName + "获得镜子的锁");Thread.sleep(1000);}synchronized (lipstick) { //一秒钟后想获得口红System.out.println(this.girlName + "获得口红的锁");}}}
-
Lock锁
Lock锁的书写格式:
class A{private final ReentrantLock lock = new ReenTrantLock();public void m(){lock.lock();try{//保证线程安全的代码;} finally {lock.unlock();//如果同步代码有异常,要将unlock()写入finally语句块}}
}
这段代码展示了如何使用 ReentrantLock 来实现线程安全的售票逻辑。
-
使用
lock.lock()获取锁,确保同一时间只有一个线程可以执行售票逻辑。 -
使用
lock.unlock()释放锁,确保其他线程可以获取锁。
package thread.state;import java.util.concurrent.locks.ReentrantLock;//测试Lock锁
public class TestLock {public static void main(String[] args) {Lock testLock = new Lock();new Thread(testLock).start();new Thread(testLock).start();new Thread(testLock).start();}
}class Lock implements Runnable {int tickerNums = 10;//定义Lock锁private final ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true) {//加锁try {lock.lock();if (tickerNums <= 0) {break;}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(tickerNums--);} catch (Exception e) {e.printStackTrace();} finally {//解锁lock.unlock();}}}
}
-
synchroized与lock对比
- lock是显示锁(需要手动开启和关闭),synchroized是隐式锁(出了作用域自动释放)
- lock只有代码块锁,而synchroized有代码块锁和方法锁
- 使用lock锁,JVM将花费较少时间来调度线程,性能更好;而且有更好的扩展性
- 使用顺序:lock>同步代码块>同步方法
-
线程通信问题
生产者和消费者问题
生产者和消费者问题是并发编程中的一个经典问题,用于描述一组生产者线程和一组消费者线程之间的协作关系。生产者线程负责生成数据并将其放入共享缓冲区,而消费者线程则从共享缓冲区中取出数据进行消费。这个过程需要解决的核心问题是:如何在生产者和消费者之间高效、安全地传递数据,同时避免线程之间的竞争条件和资源浪费。
在生产者和消费者问题中,共享缓冲区是一个有限容量的队列,生产者线程会在缓冲区未满时向其中添加数据,而消费者线程则会在缓冲区非空时从中取出数据。为了避免生产者线程向已满的缓冲区添加数据或消费者线程从空的缓冲区中取数据,需要引入同步机制来协调生产者和消费者的行为。通常,这可以通过使用锁和条件变量来实现,例如在 Java 中可以使用 synchronized 块和 Object 的 wait() 和 notify() 方法,或者使用 java.util.concurrent 包中的高级并发工具,如 BlockingQueue。
当缓冲区满时,生产者线程需要等待,直到消费者线程消费了数据,缓冲区有了空闲空间;而当缓冲区空时,消费者线程需要等待,直到生产者线程生产了数据。这种等待和通知机制确保了生产者和消费者线程之间的协作是有序的,并且避免了资源的竞争和浪费。
在实际应用中,生产者和消费者问题广泛存在于多线程和多进程的并发系统中,例如在消息队列、任务调度系统、数据流处理等领域。通过合理设计生产者和消费者之间的同步机制,可以提高系统的并发性能和资源利用率,同时保证数据的一致性和线程安全。
-
线程通信问题解决方式
管程法
这段代码实现了一个经典的生产者-消费者模型,使用了 Java 的内置同步机制(synchronized、wait() 和 notifyAll())来协调生产者和消费者之间的协作。
-
在
push()方法中,如果缓冲区已满(count == products.length),生产者线程会调用wait(),释放锁并进入等待状态,直到被消费者线程唤醒。 -
在
pop()方法中,如果缓冲区为空(count <= 0),消费者线程会调用wait(),释放锁并进入等待状态,直到被生产者线程唤醒。
package thread.state;public class TestPC {public static void main(String[] args) {SynContainer synContainer = new SynContainer();new Producer(synContainer).start();new Consumer(synContainer).start();}
}//生产者
class Producer extends Thread {//容缓冲区SynContainer container;public Producer(SynContainer container) {this.container = container;}//生产@Overridepublic void run() {for (int i = 0; i < 10; i++) {container.push(new Product(i));System.out.println("生产了" + i + "件产品");}}
}//消费者
class Consumer extends Thread {//容缓冲区SynContainer container;public Consumer(SynContainer container) {this.container = container;}//消费@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("消费了-->" + container.pop().id + "件产品");}}
}//产品
class Product {int id;//产品编号public Product(int id) {this.id = id;}
}class SynContainer{Product[] products = new Product[10]; // 容器// 容器计数器int count = 0;//生产者放入产品public synchronized void push(Product product) {// 如果容器满了,需要等待消费者消费/* 如果是if的话,假如消费者1消费了最后一个,这是index变成0此时释放锁被消费者2拿到而不是生产者拿到,这时消费者的wait是在if里所以它就直接去消费index-1下标越界,如果是while就会再去判断一下index得值是不是变成0了 */while (count == products.length) {//通知消费者消费,等待生产try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//如果没有满,需要丢入产品products[count] = product;count++;//通知消费者消费this.notifyAll();}//消费者消费产品public synchronized Product pop() {// 判断是否能消费while (count <= 0) {//等待生产者生产try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 如果可以消费count--;Product product = products[count];// 吃完了 通知生产者生产this.notifyAll();return product;}}
-
信号灯法
这段代码通过一个简单的生产者-消费者模型(演员和观众)展示了如何使用 Java 的同步机制(synchronized、wait() 和 notifyAll())来协调两个线程之间的交互。演员(生产者)负责表演节目,观众(消费者)负责观看节目。它们通过共享的 TV 对象进行通信。
标志位的作用:flag 用于控制当前状态,确保演员和观众的行为互斥。演员和观众通过 flag 来判断当前是否是自己的“工作时间”,如果不是,则调用 wait() 进入等待状态。
-
在
play()方法中:-
如果
flag = false,表示当前是观众观看时间,演员需要等待。 -
演员表演后,调用
notifyAll()唤醒观众,并切换标志位flag。
-
-
在
watch()方法中:-
如果
flag = true,表示当前是演员表演时间,观众需要等待。 -
观众观看后,调用
notifyAll()唤醒演员,并切换标志位flag。
-
package thread.state;public class TestPC2 {public static void main(String[] args) {TV tv = new TV();new Actor(tv).start();new Watcher(tv).start();}}//生产者-->演员
class Actor extends Thread {TV tv;public Actor(TV tv) {this.tv = tv;}@Overridepublic void run() {for (int i = 0; i < 20; i++) {if (i % 2 == 0) {this.tv.play("movies1 plays");} else {this.tv.play("movies2 plays");}}}
}//消费者-->观众
class Watcher extends Thread {TV tv;public Watcher(TV tv) {this.tv = tv;}@Overridepublic void run() {for (int i = 0; i < 20; i++) {tv.watch();}}
}//产品-->节目
class TV {//演员表演的时候,观众等待 True//观众观看的时候,演员等待 FalseString voice; //表演的节目boolean flag = true;//表演public synchronized void play(String voice) {if (!flag) { //这里利用标志位显示公用资源的有无try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("演员表演了:" + voice);//通知观众观看this.notifyAll(); //通知唤醒this.voice = voice;this.flag = !this.flag;}//观看public synchronized void watch() {if (flag) { //这里利用标志位显示公用资源的有无try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("观众观看了:" + voice);//通知演员表演this.notifyAll();this.flag = !this.flag;}
}
-
线程池
线程池是一种用于管理和复用线程的机制,它通过预先创建一定数量的线程并将其放入池中,避免了频繁创建和销毁线程的开销,从而提高了程序的性能和响应速度。线程池的核心思想是将线程的生命周期管理与任务的执行解耦,使得线程可以被重复利用,同时通过合理的线程数量控制,避免了系统资源的过度消耗。
线程池的工作原理可以概括为:当一个任务被提交到线程池时,线程池会根据当前的线程状态和配置规则,决定是直接分配一个线程来执行这个任务,还是将任务放入等待队列中。如果线程池中的线程数量尚未达到最大值,线程池会创建一个新的线程来处理任务;如果线程数量已经达到上限,任务会被放入等待队列中,直到有线程可用。当线程完成任务后,它会从等待队列中获取下一个任务继续执行,或者进入空闲状态等待新任务。
在实际应用中,线程池广泛用于各种需要并发处理的场景,如 Web 服务器、任务调度系统、数据处理框架等。通过使用线程池,开发者可以更高效地管理线程资源,同时避免了手动创建和管理线程的复杂性。
-
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 便于线程管理
-
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
这段代码展示了如何使用 Java 的 ExecutorService 来创建和管理线程池,并提交任务执行。
-
创建线程池:使用
Executors.newFixedThreadPool(10)创建了一个固定大小的线程池,线程池中最多可以有 10 个线程同时运行。 -
提交任务:通过
service.execute(new MyThread())提交了 4 个任务到线程池中。每个任务由MyThread类实现,MyThread实现了Runnable接口,并在run()方法中打印当前线程的名称。 -
关闭线程池:调用
service.shutdown()方法关闭线程池,这会尝试平滑关闭线程池,等待所有已提交的任务完成后再关闭。
package thread.state;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class TestPool {public static void main(String[] args) {// 1. 创建服务,创建线程池ExecutorService service = Executors.newFixedThreadPool(10); // newFixedThreadPool(线程池大小)// 执行service.execute(new MyThread());service.execute(new MyThread());service.execute(new MyThread());service.execute(new MyThread());// 2. 关闭连接service.shutdown();}
}class MyThread implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}
}
-
🎉 感谢您的支持! 🎉
如果你觉得我的内容对你有帮助,或者让你眼前一亮,请不要吝啬你的点赞👍、关注👀 和收藏⭐️ 哦!
-
点赞 是对我最大的鼓励,让我知道你在乎我的努力。
-
关注 让我们成为朋友,我会持续分享更多有趣、有用的内容。
-
收藏 方便你随时回顾,也让我知道这些内容对你有价值。
你的每一个小动作,都是我继续前行的动力!一起进步,一起成长,感谢有你!😊
#感谢支持 #点赞关注收藏 #一起成长
相关文章:
Java零基础入门笔记:多线程
前言 本笔记是学习狂神的java教程,建议配合视频,学习体验更佳。 【狂神说Java】Java零基础学习视频通俗易懂_哔哩哔哩_bilibili 第1-2章:Java零基础入门笔记:(1-2)入门(简介、基础知识)-CSDN博客 第3章…...
Django 中,Form 和 ModelForm的用法和区别
在 Django 中,Form 和 ModelForm 是用于处理表单数据的两种主要方式。它们的主要区别在于是否与模型(Model)直接关联。以下是它们的用法、区别以及高级用法的详细说明: 一、Form 的使用 1. 基本用法 Form 是一个独立的表单类,不与任何模型直接关联。适用于需要手动定义字…...
tcp udp区别
TCP(传输控制协议) 和 UDP(用户数据报协议) 是两种常用的传输层协议,它们在数据传输方式、可靠性和应用场景等方面有显著区别。以下是它们的主要区别: 1. 连接方式 TCP:面向连接的协议。通信前需…...
数据类设计_图片类设计之1_矩阵类设计(前端架构基础)
前言 学的东西多了,要想办法用出来.C和C是偏向底层的语言,直接与数据打交道.尝试做一些和数据方面相关的内容 引入 图形在底层是怎么表示的,用C来表示 认识图片 图片是个风景,动物,还是其他内容,人是可以看出来的.那么计算机是怎么看懂的呢?在有自主意识的人工智能被设计出来…...
C++:入门详解(关于C与C++基本差别)
目录 一.C的第一个程序 二.命名空间(namespace) 1.命名空间的定义与使用: (1)命名空间里可以定义变量,函数,结构体等多种类型 (2)命名空间调用(…...
GC安全点导致停顿时间过长的案例
GC安全点导致停顿时间过长的案例 前言安全点的概念案例分析解决方法如有需要收藏的看官,顺便也用发财的小手点点赞哈,如有错漏,也欢迎各位在评论区评论! 前言 前段时间在使用G1垃圾收集时,因服务读写压力过大…...
linux下 jq 截取json文件信息
背景:通过‘登录名‘ 获取该对象的其他个人信息如名字。 环境准备:麒麟操作系统V10 jq安装包 jq安装包获取方式:yum install jq 或 使用附件中的rpm 或 git自行下载 https://github.com/stedolan/jq/releases/download/ 实现过程介绍&am…...
git lfs使用方法指南【在github保存100M以上大文件】
为了在 GitHub 仓库中存储超过 100MB 的大文件并避免推送失败,使用 Git LFS(Large File Storage) 是最佳解决方案。以下是详细步骤: 一、安装 Git LFS 下载并安装 Git LFS: 访问 Git LFS 官网 下载对应系统的安装包。或…...
躲藏博弈:概率论与博弈论视角下的最优策略选择
躲藏博弈:概率论与博弈论视角下的最优策略选择 1. 问题引入 想象这样一个场景:你在厕所里藏了一部手机,一周过去了,它仍未被发现。现在你面临一个决策: 选项A:继续将手机留在原处选项B:将手机…...
类加载器加载过程
今天我们就来深入了解一下Java中的类加载器以及它的加载过程。 一、什么是类加载器? 在Java中,类加载器(Class Loader)是一个非常重要的概念。它负责将类的字节码文件(.class文件)加载到Java虚拟机&#x…...
Python中dump、dumps和load、loads的异同
Python中dump、dumps和load、loads的异同 Python中dump、dumps和load、loads的异同 1. json.dump()和json.dumps() 1.1 json.dump()1.1 json.dumps() 2. json.load()和json.loads() 2.1 json.load()2.2. json.loads() 3. 总结对比4. 区分5. 完整代码 1. json.dump()和json.dum…...
Spring Boot整合ArangoDB教程
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 一、环境准备 JDK 17Maven 3.8Spring Boot 3.2ArangoDB 3.11(本地安装或Docker运行) Docker启动ArangoDB docker run -d --name ar…...
vue3框架的响应式依赖追踪机制
当存在一个响应式变量于视图中发生改变时会更新当前组件的所以视图显示,但是没有视图中不写这个响应式变量就就算修改该变量也不会修改视图,这是为什么?我们能否可以理解宽泛的理解为vue组件的更新就是视图的更新,单当视图中不存在…...
软件工程:软件需求之需求分析方法
目录 前言 需求分析方法 工具和方法 具体分析方法 对运行环境的影响 编辑 前言 本文重点介绍开展软件需求分析的方法。 需求分析方法 工具和方法 软件需求可以维护在ALM系统中,譬如:doors,codeBeamer等,JIRA适合互联网行…...
【网络编程】WSAAsyncSelect 模型
十、基于I/O模型的网络开发 接着上次的博客继续分享:select模型 10.8 异步选择模型WSAAsyncSelect 10.8.1 基本概念 WSAAsyncSelect模型是Windows socket的一个异步I/O 模型,利用这个模型,应用程序 可在一个套接字上接收以Windows 消息为基…...
视觉-语言模型-出发点CLIP--(精读论文)
阅读建议:配合这个源码分析阅读效果更加 研究背景和目的 介绍当前计算机视觉系统依赖固定类别标签训练的局限性,以及自然语言监督作为一种有潜力替代方式的研究现状。强调论文旨在探索从自然语言监督中学习可迁移视觉模型,实现零样本学习&a…...
docker本地部署RagFlow
1.安装 克隆仓库 git clone https://github.com/infiniflow/ragflow.git构建预建的Docker映像并启动服务器 cd ragflow/docker chmod x ./entrypoint.sh docker compose -f docker-compose.yml -p ragflow up -d修改ragflow/docker/.env文件 #RAGFLOW_IMAGEinfiniflow/ragfl…...
机器学习数学基础:44.多元线性回归
一、文字内容详解 1. 多重共线性的判断——皮尔逊相关系数 皮尔逊相关系数用于衡量自变量间的线性相关程度,取值范围为 ([-1, 1]): 绝对值越接近 (1),变量间线性相关性越强;越接近 (0),相关性越弱。在多重共线性判断…...
GetWindowLongPtr函数分析
第一部分: #ifdef UNICODE FUNCLOG2(LOG_GENERAL, LONG_PTR, APIENTRY, GetWindowLongPtrW, HWND, hwnd, int, nIndex) #else FUNCLOG2(LOG_GENERAL, LONG_PTR, APIENTRY, GetWindowLongPtrA, HWND, hwnd, int, nIndex) #endif // UNICODE LONG_PTR APIENTRY GetWin…...
大语言模型(LLM)和嵌入模型的统一调用接口
ChatModelFactory、EmbeddingModelFactory 讲解代码:import os from dotenv import load_dotenv, find_dotenv_ load_dotenv(find_dotenv())from langchain_openai import ChatOpenAI, OpenAIEmbeddings, AzureChatOpenAI, AzureOpenAIEmbeddingsclass ChatModelF…...
大白话html语义化标签优势与应用场景
大白话html语义化标签优势与应用场景 大白话解释 语义化标签就是那些名字能让人一看就大概知道它是用来做什么的标签。以前我们经常用<div>来做各种布局,但是<div>本身没有什么实际的含义,就像一个没有名字的盒子。而语义化标签就像是有名…...
Scala:在哪里写类的属性?类的属性必须私有吗?类的必须初始化吗?
哪里写类的属性 直接在类体中定义属性 class Circle {private var _radius: Double 0.0def radius: Double _radiusdef radius_(newRadius: Double): Unit {_radius newRadius}def area: Double scala.math.Pi * _radius * _radius } 可以在类体内部直接定义属性。例如&am…...
Android源码编译命令详解
一、引言 先看下面几条指令,相信编译过Android源码的人都再熟悉不过的。 source setenv.sh lunch make -j8记得最初刚接触Android时,同事告诉我用上面的指令就可以编译Android源码,指令虽短但过几天就记不全或者忘记顺序,每次编…...
任务11:路由器配置与静态路由配置
目录 一、概念 二、路由器配置 三、配置静态路由CSDN 原创主页:不羁https://blog.csdn.net/2303_76492156?typeblog 一、概念 1、路由器的作用:通过路由表进行数据的转发。 2、交换机的作用:通过学习和识别 MAC 地址,依据 M…...
Unity之如何实现哔哩哔哩直播弹幕游戏
前言 什么是直播间互动? 当我们使用哔哩哔哩进行直播或者观看视频时,我们可以通过接入哔哩哔哩提供的 直播&互动玩法SDK,让直播和视频可以与Unity3D游戏客户端或者游戏服务器进行互动。 环境要求 Unity 2020.x或更高版本 依赖库:Newtonsoft Json Unity Package 在P…...
Python实例:PyMuPDF实现PDF翻译,英文翻译为中文,并按段落创建中文PDF
基于PyMuPDF与百度翻译的PDF翻译处理系统开发:中文乱码解决方案与自动化排版实践 一 、功能预览:将英文翻译为中文后创建的PDF 二、完整代码 from reportlab.lib.pagesizes import letter from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle...
LeeCode题库第四十六题
46.全排列 项目场景: 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1: 输入:nums [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2&am…...
LangChain4j开发RAG入门示例
本文将详细介绍如何基于Java语言,使用Langchain4j开源框架、Milvus向量数据、阿里Qwen大模型,开发一个RAG入门级简单示例。本示例虽然简单,但涉及到多个知识点,包括:Milvus初始化、Embedding模型、文档切片、Springboo…...
快速从C过度C++(一):namespace,C++的输入和输出,缺省参数,函数重载
📝前言: 本文章适合有一定C语言编程基础的读者浏览,主要介绍从C语言到C过度,我们首先要掌握的一些基础知识,以便于我们快速进入C的学习,为后面的学习打下基础。 这篇文章的主要内容有: 1&#x…...
课程《Deep Learning Specialization》
在coursera上,Deep Learning Specialization 课程内容如下图所示:...
