线程
线程
此系列笔记来源于
BiliBili韩顺平老师的Java基础课
进程
1、进程是指运行中的程序,操作系统会为其分配内存空间。
2、进程是程序的一次执行过程,或是正在运行的一个程序,是动态过程:有它自身的产生、存在和消亡的过程
线程
1、线程由进程创建,是进程的一个实体
2、一个进程可以拥有多个线程
1、单线程:
同一个时刻,只允许执行一个线程
2、多线程:
同一个时刻,可以执行多个线程
3、并发:
同一个时刻,多个任务交替执行,造成一种 ”貌似同时“ 的错觉。简单地说,单核cpu实现的多任务就是并发
4、并行:
同一个时刻,多个任务同时执行。多核cpu可以实现并行
//返回到Java虚拟机的可用的处理器数量
Runtime runtime = Runtime.getRuntime();
System.out.print(runtime.availableProcessors());
线程基本使用
示意图

方式一:继承 Thread类
方式二:实现 Runnable接口
继承 Thread类
说明
1、当一个类继承了Thread类,就可以作为一个线程来使用
2、我们会重写 run 方法,写上自己的业务代码
3、run 是 Thread类实现了 Runnable接口的 run方法
//基本使用
public class Yra {
public static void main(String[] args) {
wuhu wh = new wuhu();
wh.start(); //启动线程
// wh.run(); //run方法只是一个普通方法,并未真正的开启一个新的线程
}
}
class wuhu extends Thread {
@Override
//java中实现真正的多线程是start中的start0()方法,run()方法只是一个普通的方法
public void run() { //重写run方法
int cnt = 0;
while (true) {
System.out.println(++ cnt);
//异常捕获 快捷键:ctrl + alt + t
try {
Thread.sleep(1000); //休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (cnt == 3) {
System.out.println("结束");
break;
}
}
}
}
PS:
1、可以用 JConsole 监控线程执行情况
2、java中实现真正的多线程是start中的start0()方法,run()方法只是一个普通的方法
3、start()方法调用 start0()方法后,该线程不一定会立马执行,只是将线程变成了可运行状态,具体什么时候执行,取决于CPU,由CPU统一调度
4、start0() 是本地方法,由JVM调用,底层是C/C++实现
实现 Runnable接口
说明
1、java是单继承的,当一个类可能已经继承了某个弗雷,这时再用继承Thread类方法来创建线程显然不可能
public class Yra {
public static void main(String[] args) {
wuhu a = new wuhu();
//新建一个Thread对象,把实现了Runnable接口的a放入Thread,a不能直接调用start()方法
Thread thread = new Thread(a);
thread.start();
}
}
class wuhu implements Runnable {
@Override
public void run() { //重写run方法
int cnt = 0;
while (true) {
System.out.println(++ cnt);
//异常捕获 快捷键:ctrl + alt + t
try {
Thread.sleep(1000); //休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (cnt == 3) {
System.out.println("结束");
break;
}
}
}
}
PS:
1、这里底层用到了【静态 】代理模式(设计模式)
线程终止
在线程类里面加个控制线程内容是否执行的变量和set该变量的方法,main方法中调用该方法来终止线程
如loop变量 和 setLoop方法,在main方法中调用setLoop方法来改变loop的值,以终止while循环
线程常用方法
第一组:
1、setName //设置线程名称
2、getName //返回该线程名称
3、start //使该线程开始执行,JVM底层调用该线程的 start0方法
4、run //调用线程对象的 run方法
5、setPriority //更改线程的优先级
6、getPriority //获取线程的优先级
7、sleep //在指定的毫秒数内让当前正在执行的线程休眠
8、interrupt //中断线程
PS:
1、start底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新的线程
2、线程优先级:
pulic static final int MAX_PRIORITY = 10;
pulic static final int MIN_PRIORITY = 1;
pulic static final int NORM_PRIORITY = 5;
3、interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠的线程(唤醒)
4、sleep:线程的静态方法,使当前线程休眠
try {
Thread.sleep(1000); //休眠1秒
} catch (InterruptedException e) {
//当获取到中断异常后,即调用了该对象的interrupt方法,就为直接中断休眠,可以加上业务逻辑代码
}
第二组:
1、yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
2、join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
用户线程和守护线程
1、用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
2、 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。常见的守护线程:垃圾回收机制
xxx.setDaemon(true); //将子线程设为守护线程
线程的生命周期
1、可以用Thread.State查看线程的状态
2、线程可以处于一下状态之一
3、线程的状态转换图
线程同步机制 Synchronized
介绍
1、在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
2、也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。
同步具体方法
//1、同步代码块
synchronized(对象) { //得到该对象的锁,才能操作同步代码
//需要被同步代码
}
//2、synchronized还可以放在方法声明中,表示整个方法为同步方法
public synchronized void m(String m) {
//需要被同步的代码
}
如买票,如何多个线程卖票最后剩余的票不会小于0呢?
可以给sell方法加个synchronized(保证每次只有一个线程买票),然后在sell方法开始判断票的数量。
public class Yra {
public static void main(String[] args) {
Shop a = new Shop();
new Thread(a).start();
new Thread(a).start();
new Thread(a).start();
}
}
class Shop implements Runnable {
private int rest = 10;
private static boolean loop = true;
public synchronized void sell() {
if (rest <= 0) {
loop = false;
return;
} else {
System.out.println(Thread.currentThread().getName() + "还剩" + --rest + "张票");
}
}
@Override
public void run() {
while (loop) {
sell();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
同步原理
各线程争夺对象上的锁,抢到锁后执行相关代码,执行完后把锁还回去。如果第一次t1先抢到,t1还回去后仍然会去抢锁
互斥锁
介绍
1、java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
2、每个对象都对应于一个可称为“互斥锁” 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
3、关键字synchronized 来与对象的互斥锁联系。
4、同步的局限性:导致程序的执行效率要降低
5、同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个独享)
public synchronized void sell() {} 是一个同步方法,这时锁在 this对象上
6、同步方法(静态的)的锁为当前类本身
7、也可以在代码块上写 synchronized,同步代码块,互斥锁还是在 this对象上
public synchronized void sell() {
synchronized (this) { //如果是静态方法,那么this需要换成类名.class
if (rest <= 0) {
loop = false;
return;
} else {
System.out.println(Thread.currentThread().getName() + "还剩" + --rest + "张票");
}
}
}
PS:
1、同步方法如果没有使用static修饰:默认锁对象为this
2、如果同步方法使用static修饰,默认锁对象:当前类.class
3、实现的落地步骤:
(1)需要先分析上锁的代码
(2)选择同步代码块或同步方法
(3)要求多个线程的锁对象为同一个即可
线程的死锁
介绍
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程时一定要避免死锁的发生
例:
if (flag) {
synchronized(o1) {
synchronized(o2) {
}
}
} else {
synchronized(o2) {
synchronized(o1) {
}
}
}
释放锁
下面操作会释放锁
1、当前线程的同步方法、代码块执行结束
2、当前在同步代码块、方法中遇到break、return
3、当前线程在同步代码块、方法中出现了未处理的 Error 或 Exception,导致异常结束
4、当前线程在同步代码块、方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
下面操作不会释放锁
1、线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
2、线程执行同步代码块或同步方法时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁
提示:应景连通管避免使用 suspend() 和 resume() 来控制线程,方法不再推荐使用;——
定义了一个Shot线程,给每个坦克加个射击方法,调用这个方法就可以触发一个线程来发射子弹,给我们的面板也添加一个线程,用来不断刷新重绘使得导弹能有移动的效果
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器