JAVA 多线程并发

article/2025/9/12 10:49:25

1.1.1. JAVA 并发知识库

在这里插入图片描述

1.1.2. JAVA 线程实现/创建方式

1.1.2.1. 继承 Thread 类
Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过 Thread 类的 start()实例方法。start()方法是一个 native 方法,它将启动一个新线程,并执行 run()方法。

public class MyThread extends Thread { public void run() { System.out.println("MyThread.run()"); } 
} 
MyThread myThread1 = new MyThread(); 
myThread1.start(); 

1.1.2.2. 实现 Runnable 接口
如果自己的类已经 extends 另一个类,就无法直接 extends Thread,此时,可以实现一个

Runnable 接口。
public class MyThread extends OtherClass implements Runnable { public void run() { System.out.println("MyThread.run()"); } 
}
//启动 MyThread,需要首先实例化一个 Thread,并传入自己的 MyThread 实例:
MyThread myThread = new MyThread(); 
Thread thread = new Thread(myThread); 
thread.start(); 
//事实上,当传入一个 Runnable target 参数给 Thread 后,Thread 的 run()方法就会调用
target.run()
public void run() { if (target != null) { target.run(); } 
}

1.1.2.3. ExecutorService、Callable、Future 有返回值线程
有返回值的任务必须实现 Callable 接口,类似的,无返回值的任务必须 Runnable 接口。执行Callable 任务后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到 Callable 任务返回的 Object 了,再结合线程池接口 ExecutorService 就可以实现传说中有返回结果的多线程了。

//创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 创建多个有返回值的任务
List<Future> list = new ArrayList<Future>(); 
for (int i = 0; i < taskSize; i++) { 
Callable c = new MyCallable(i + " "); 
// 执行任务并获取 Future 对象
Future f = pool.submit(c); 
list.add(f); 
} 
// 关闭线程池
pool.shutdown(); 
// 获取所有并发任务的运行结果
for (Future f : list) { 
// 从 Future 对象上获取任务的返回值,并输出到控制台
System.out.println("res:" + f.get().toString()); 
}

1.1.2.4. 基于线程池的方式
线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销毁,是非常浪费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。

 // 创建线程池ExecutorService threadPool = Executors.newFixedThreadPool(10);while(true) {threadPool.execute(new Runnable() { // 提交多个线程任务,并执行@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " is running ..");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}});} }

1.1.3. 4 种线程池
Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是 ExecutorService。
在这里插入图片描述
1.1.3.1. newCachedThreadPool
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。
1.1.3.2. newFixedThreadPool
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。
1.1.3.3. newScheduledThreadPool
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

 ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(3); scheduledThreadPool.schedule(newRunnable(){ @Override public void run() {System.out.println("延迟三秒");}}, 3, TimeUnit.SECONDS);
scheduledThreadPool.scheduleAtFixedRate(newRunnable(){ @Override public void run() {System.out.println("延迟 1 秒后每三秒执行一次");}},1,3,TimeUnit.SECONDS);

1.1.3.4. newSingleThreadExecutor
Executors.newSingleThreadExecutor()返回一个线程池(这个线程池只有一个线程),这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去!

1.1.4. 线程生命周期(状态)

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5 种状态。尤其是当线程启动以后,它不可能一直"霸占"着 CPU 独自运行,所以 CPU 需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换。
1.1.4.1. 新建状态(NEW)
当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值。
1.1.4.2. 就绪状态(RUNNABLE):
当线程对象调用了 start()方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。
1.1.4.3. 运行状态(RUNNING):
如果处于就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状态。
1.1.4.4. 阻塞状态(BLOCKED):
阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状态。阻塞的情况分三种:

等待阻塞(o.wait->等待对列):
运行(running)的线程执行 o.wait()方法,JVM 会把该线程放入等待队列(waitting queue)中。
同步阻塞(lock->锁池)运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。
其他阻塞(sleep/join):
运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入可运行(runnable)状态。

4.1.4.5. 线程死亡(DEAD)
线程会以下面三种方式结束,结束后就是死亡状态。
正常结束

  1. run()或 call()方法执行完成,线程正常结束。
    异常结束
  2. 线程抛出一个未捕获的 Exception 或 Error。
    调用 stop
  3. 直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。
    在这里插入图片描述

1.1.5. 终止线程 4 种方式

1.1.5.1. 正常运行结束
程序运行结束,线程自动结束。
1.1.5.2. 使用退出标志退出线程
一般 run()方法执行完,线程就会正常结束,然而,常常有些线程是伺服线程。它们需要长时间的运行,只有在外部某些条件满足的情况下,才能关闭这些线程。使用一个变量来控制循环,例如:最直接的方法就是设一个 boolean 类型的标志,并通过设置这个标志为 true或 false 来控制 while循环是否退出,代码示例:

public class ThreadSafe extends Thread {public volatile boolean exit = false; public void run() { while (!exit){//do something}} 
}

定义了一个退出标志 exit,当 exit 为 true 时,while 循环退出,exit 的默认值为 false.在定义 exit时,使用了一个 Java 关键字 volatile,这个关键字的目的是使 exit 同步,也就是说在同一时刻只能由一个线程来修改 exit 的值。
1.1.5.3. Interrupt 方法结束线程
使用 interrupt()方法来中断线程有两种情况:

  1. 线程处于阻塞状态:如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时,会使线程处于阻塞状态。当调用线程的 interrupt()方法时,会抛出 InterruptException 异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态,从而让我们有机会结束这个线程的执行。通常很多人认为只要调用interrupt 方法线程就会结束,实际上是错的, 一定要先捕获InterruptedException 异常之后通过 break 来跳出循环,才能正常结束 run 方法。
  2. 线程未处于阻塞状态:使用 isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志就会置 true,和使用自定义的标志来控制循环是一样的道理。
 public class ThreadSafe extends Thread {public void run() { while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出try{Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出}catch(InterruptedException e){e.printStackTrace();break;//捕获到异常之后,执行 break 跳出循环}}} 
}

1.1.5.4. stop 方法终止线程(线程不安全)
程序中可以直接使用 thread.stop()来强行终止线程,但是 stop 方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,不安全主要是:thread.stop()调用之后,创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。因此,并不推荐使用 stop 方法来终止线程。


http://chatgpt.dhexx.cn/article/meZ5mSII.shtml

相关文章

攻防世界 web高手进阶区 10分题 TimeKeeper

前言 继续ctf的旅程 开始攻防世界web高手进阶区的10分题 本文是TimeKeeper的writeup 解题过程 进入界面 惯例源码御剑 源码没发现 御剑扫到一个Flask debug console 先正常注册登录 尝试在注册和登录界面sqli 失败 没有别的头绪 就尝试研究debug 参考Flask debug pin安全问…

【Linux时钟系统】

Linux系统时间 时间设备![在这里插入图片描述](https://img-blog.csdnimg.cn/7c50db7d72ba4ff5a0f1264629769184.png)RTC时钟系统计时设备&#xff08;吴斌个人主页&#xff09;定时通知设备(吴斌个人主页)系统启动过程中的时钟系统SMP初始化阶段Linux时钟中断低精度定时器的使…

linux时间子系统 - clocksource/timekeeper

linux时间子系统中有一个很重要的记录时间的模块就是timekeeper&#xff0c;而timekeeper需要底层clock硬件设备的支持&#xff0c;每一个设备用结构体clocksource来表示&#xff0c;注册进系统的每一个clocksource会凭借优先级最终被选择成为timekeeper的时钟源。 1. clocksou…

【Java】Timer和TimerTask详解

以下内容根据 The JavaTM Tutorial 和相关API doc翻译整理&#xff0c;以供日后参考&#xff1a; 1.概览 Timer是一种定时器工具&#xff0c;用来在一个后台线程计划执行指定任务。它可以计划执行一个任务一次或反复多次。 TimerTask一个抽象类&#xff0c;它的子类代表一个可以…

【Linux内核代码分析1】Linux时间子系统及HRTIMER实现

Linux时间子系统软件架构 &#xff08;1&#xff09;嵌入式设备需要较好的电源管理策略。传统的linux会有一个周期性的时钟&#xff0c;即便是系统无事可做的时候也要醒来&#xff0c;这样导致系统不断的从低功耗&#xff08;idle&#xff09;状态进入高功耗的状态。这样的设计…

Linux Time

1、Linux时钟框架 上图是linux时钟框架一个经典的描述。本质上linux各种时钟架构和服务是基于硬件提供的两种timer而构建的。 1、定时Timer 这类timer每个cpu都有一个独立的&#xff0c;称为local timer。这类timer的中断一般都是PPI&#xff08;Private Peripheral Interrup…

Linux时间子系统基础(三):时间的维护者:timekeeper

本系列文章的前两节讨论了用于计时的时钟源&#xff1a;clocksource&#xff0c;以及内核内部时间的一些表示方法&#xff0c;但是对于真实的用户来说&#xff0c;我们感知的是真实世界的真实时间&#xff0c;也就是所谓的墙上时间&#xff0c;clocksource只能提供一个按给定频…

攻防世界--TimeKeeper

将price和id加上引号&#xff0c;触发debug 联想到扫描目录出现的/console Flask debug pin安全问题 这种方法有点难&#xff0c;换种方法 目录穿越 可以成功读取任意文件&#xff0c;获得flag。

html 时间控件滚动选择器,TimePicker

TimePicker滚动的列表选择器&#xff0c;可用于时间选择。多个TimePicker组合起来可实现复杂的应用&#xff0c;如效果图中&#xff0c;俩个TimePicker组合&#xff0c;一个表示"分"、一个表示"秒"。 效果图&#xff1a; 如何使用xml创建 android:id"…

Linux时间子系统之时间维护层(Time Keeper)

时间维护层会收到Tick层的周期调用&#xff0c;每次调用的周期是由内核参数决定的。在此期间&#xff0c;时间维护层可以读取时钟源设备的周期数&#xff0c;从而感知时间的流逝。 目前时间维护层主要负责维护以下几种类型的时间&#xff1a; 实时时间&#xff08;CLOCK_REAL…

【Linux内核|时间子系统】Linux时间子系统(二)timekeeping简介

文章目录 1. timekeeping2. 计算墙上时间、启动时间差值3. timekeeper初始化3.1. 默认时钟源3.2. tk_setup_internals3.3. 设置时间 4. timekeeping_update4.1. tk_update_ktime_data&#xff1a;tkr_mono和tkr_raw设置4.2. update_fast_timekeeper4.3. shadow_timekeeper 1. t…

matlab 梯度下降 求偏导,通过计算图求梯度下降中各偏导的推导

通过计算图求梯度下降中各偏导的推导 Author: nex3z 2017-08-30 在 Neural Networks and Deep Learning 课程的 Logistic Regression Gradient Descent 一节以逻辑回归为例&#xff0c;介绍了使用计算图(Computation Graph)求梯度下降中各偏导的方法&#xff0c;但没有给出具体…

matlab|求导数/最值

本博文源于matlab求导数求极值求最值&#xff0c;涉及内容极限命令求导/diff求导/一元函数一阶导数&#xff0c;多阶导数&#xff0c;求参数导数/函数极值和最值/不给定区间求最值 用极限命令求导 例子&#xff1a;求函数3sinx4x^2在x0处的导数 >> syms t; limit((3*…

matlab 梯度下降 求偏导,吴恩达机器学习课程课时12梯度下降算法中参数θ0,θ1求偏导...

最近学习吴恩达的机器学习课程。 看到了线性回归的梯度下降算法。课程中将了一个非常简单的线性回归&#xff1a; 比如给出一些房子的size和对应的price&#xff0c;我们可以建立一个模型(在此模型就是线性回归)&#xff0c; 希望之后在给出任意一个房子的size&#xff0c;可以…

matlab求COPULA偏导,matlab note

Update date&#xff1a;2018-01-05 Matlab Note 1 vine-copula 1.1 xlsread() 打开xlsx里面的数据 S3CE xlsread(电表建模数据.xlsx,S3CE&#xff0c;‘G2:G52’) 1.2常见希腊发音总结 image.png 3.prob() 连乘 由于matlab中的元素是以矩阵为单位,prod(x)就是把x向量中所有元…

matlab求解多元函数的偏导数diff

本博文源于matlab求解多元函数导数。涉及求一阶/求多阶/求向量偏导数/求隐函数导数 多元函数的偏导数 diff(f(x,y,z),变量名)例子 求x^2lny根号z的偏导数 >> syms x >> syms y >> syms z >> du_dx diff(x^2log(y)sqrt(z),x)du_dx 2*x>> du_dz…

matlab实现隐函数求偏导数(impldiff函数)

目录 总述函数说明应用举例例1例2 函数实现 总述 由前面给出的算法&#xff0c;可以编写出如下函数来求解隐函数的偏导数。 函数说明 function dy impldiff(f, x, y, n) %impldiff %隐函数求导 % 调用格式&#xff1a; % f1 impldiff(f, x, y, n) % 其中&#xff1a;f…

matlab偏导数方程,利用Matlab求解不同类型的偏微分方程

来源:新浪了凡春秋的博客 在科学技术各领域中,有很多问题都可以归结为偏微分方程问题。在物理专业的力学、热学、电学、光学、近代物理课程中都可遇见偏微分方程。偏微分方程,再加上边界条件、初始条件构成的数学模型,只有在很特殊情况下才可求得解析解。随着计算机技术的发…

【MATLAB】求偏导数

目录 1、示例1 2、示例2 3、MATLAB求偏导数的应用 1、示例1 syms x y; z=(1+x*y)^y; zx=diff (z,’x’)

MATLAB求解偏导数

MATLAB具有多元函数求解偏导数的功能。 例&#xff1a; 1.函数关于x的二阶偏导数 syms x y >> z x^4y^4-4*x^2*y^2; >> zxx diff(z,x,2)zxx 12*x^2 - 8*y^2 2.函数关于y的二阶偏导数 zyy diff(z,y,2)zyy 12*y^2 - 8*x^2 3.函数二阶混合偏导数 zxy diff(dif…