【JavaEE初阶】多线程上部

devtools/2024/11/15 23:05:11/

文章目录

  • 本篇目标:
  • 一、认识线程(Thread)
    • 1.概念:
    • 2.创建线程
  • 二、Thread 类及常见方法
    • 2.1 Thread 的常见构造方法
    • 2.2 Thread 的几个常见属性
    • 2.3 启动⼀个线程 - start()
    • 2.4 中断⼀个线程
    • 2.5 等待⼀个线程 - join()
    • 2.6 获取当前线程引用
    • 2.7 休眠当前线程 - sleep()
  • 三、线程的状态
    • 3.1 观察线程的所有状态
  • 四、 多线程带来的的风险-线程安全 (重点)
    • 4.1 线程安全的概念
    • 4.2 线程不安全的原因
  • 五、synchronized 关键字 - 监视器锁 monitor lock
    • 5.1 synchronized 的特性
    • 5.2 synchronized 使用示例
  • 总结


本篇目标:

  • 认识多线程
  • 掌握多线程程序的编写
  • 掌握多线程的状态
  • 掌握什么是线程不安全及解决思路
  • 掌握 synchronized关键字

提示:以下是本篇文章正文内容

一、认识线程(Thread)

1.概念:

1.1 线程是什么?

线程: ⼀个线程就是⼀个 “执行流”. 每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 “同时” 执行着多份代码.

1.2 为什么要有线程?

首先, “并发编程” 成为 “刚需”.

  • 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU资源.
  • 有些任务场景需要 “等待 IO”, 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程

其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量.

  • 创建线程比创建进程更快.
  • 销毁线程比销毁进程更快.
  • 调度线程比调度进程更快

最后, 线程虽然比进程轻量, 但是人们还不满足, 于是又有了 “线程池”(ThreadPool) 和 “协程”(Coroutine)

1.3 进程和线程的区别

  • 进程是包含线程的. 每个进程至少有⼀个线程存在,即主线程。
  • 进程和进程之间不共享内存空间. 同⼀个进程的线程之间共享同⼀个内存空间
  • 进程是系统分配资源的最小单位,线程是系统调度的最小单位。
  • ⼀个进程挂了⼀般不会影响到其他进程. 但是⼀个线程挂了, 可能把同进程内的其他线程⼀起带走(整个进程崩溃).

1.4 Java的线程和操作系统线程的关系

  • 线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了⼀些 API 供用户使用(例如 Linux 的 pthread 库).
  • Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进⼀步的抽象和封装

2.创建线程

方法一:继承 Thread 类

1.继承 Thread 来创建⼀个线程类.

java">class MyThread extends Thread {@Overridepublic void run() {System.out.println("这⾥是线程运⾏的代码");}
}

2.创建 MyThread 类的实例

java">MyThread t = new MyThread();

3.调用 start 方法启动线程

java">t.start();		// 线程开始运⾏

方法二 实现 Runnable 接口

1.实现 Runnable 接口

java">class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("这⾥是线程运⾏的代码");}
}

2.创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.

java">Thread t = new Thread(new MyRunnable());

3.调用 start 方法

java">t.start();		// 线程开始运⾏

对比上面两种方法:
• 继承 Thread 类, 直接使用 this 就表示当前线程对象的引用.
• 实现 Runnable 接口, this 表示的是 MyRunnable 的引用. 需要使用Thread.currentThread()

其他变形:

  • 匿名内部类创建 Thread 子类对象
java">// 使⽤匿名类创建 Thread ⼦类对象
Thread t1 = new Thread() {@Overridepublic void run() {System.out.println("使⽤匿名类创建 Thread ⼦类对象");}
};
  • 匿名内部类创建 Runnable 子类对象
java">// 使⽤匿名类创建 Runnable ⼦类对象
Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("使⽤匿名类创建 Runnable ⼦类对象");}
});
  • lambda 表达式创建 Runnable 子类对象
java">// 使⽤ lambda 表达式创建 Runnable ⼦类对象
Thread t3 = new Thread(() -> System.out.println("使⽤匿名类创建 Thread ⼦类对象"));
Thread t4 = new Thread(() -> {System.out.println("使⽤匿名类创建 Thread 子类对象");
});

二、Thread 类及常见方法

Thread 类是 JVM 用来管理线程的⼀个类,换句话说,每个线程都有⼀个唯⼀的 Thread 对象与之关联。

2.1 Thread 的常见构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用Runnable 对象创建线程对象,并命名
java">Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

2.2 Thread 的几个常见属性

属性获取方法
IDgetld0)
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted( )
  • ID 是线程的唯⼀标识,不同线程不会重复
  • 名称是各种调试工具用到
  • 状态表示线程当前所处的⼀个情况,下面我们会进⼀步说明
  • 优先级高的线程理论上来说更容易被调度到
  • 关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有非后台线程结束后,才会结束运行。
  • 是否存活,即简单的理解,为 run 方法是否运行结束了
  • 线程的中断问题,下面我们进⼀步说明

2.3 启动⼀个线程 - start()

调用 start 方法, 才真的在操作系统的底层创建出⼀个线程。

2.4 中断⼀个线程

目前常见的有以下两种方式:

  1. 通过共享的标记来进行沟通
  2. 调用 interrupt() 方法来通知

2.5 等待⼀个线程 - join()

有时,我们需要等待⼀个线程完成它的工作后,才能进行自己的下⼀步工作。

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等millis毫秒
public void join(long millis, int nanos)同理,但可以更高精度

2.6 获取当前线程引用

方法说明
public static Thread currentTkread()返回当前线程对象的引用

2.7 休眠当前线程 - sleep()

方法
public static void sleep(long millis) throws InterruptedException
public static void sleep(long millis, int nanos)throws InterruptedException

三、线程的状态

3.1 观察线程的所有状态

线程的状态是⼀个枚举类型 Thread.State

  • NEW: 安排了工作, 还未开始行动
  • RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
  • BLOCKED: 这几个都表示排队等着其他事情
  • WAITING: 这几个都表示排队等着其他事情
  • TIMED_WAITING: 这几个都表示排队等着其他事情
  • TERMINATED: 工作完成了

四、 多线程带来的的风险-线程安全 (重点)

4.1 线程安全的概念

如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。

4.2 线程不安全的原因

  • **线程调度是随机的 **,这是线程安全问题的 罪魁祸首, 随机调度使⼀个程序在多线程环境下, 执行顺序存在很多的变数,程序猿必须保证在任意执行顺序下 , 代码都能正常工作。
  • 修改共享数据,多个线程修改同⼀个变量。
  • 原子性
  • 可见性
  • 指令重排序

【拓展】:Java 内存模型 (JMM): Java虚拟机规范中定义了Java内存模型
目的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到⼀致的并发效果。

  • 线程之间的共享变量存在 主内存 (Main Memory).
  • 每⼀个线程都有自己的 “工作内存” (Working Memory) .
  • 当线程要读取⼀个共享变量的时候, 会先把变量从主内存拷贝到工作内存, 再从工作内存读取数据.
  • 当线程要修改⼀个共享变量的时候, 也会先修改工作内存中的副本, 再同步回主内存.

由于每个线程有自己的工作内存, 这些工作内存中的内容相当于同⼀个共享变量的 “副本”. 此时修改线程1 的工作内存中的值, 线程2 的工作内存不⼀定会及时变化。

此时引柚柚们可能会有这些问题:

  • 为啥要整这么多内存?
  • 为啥要这么麻烦的拷来拷去?
  1. 为啥整这么多内存?
    实际并没有这么多 “内存”. 这只是 Java 规范中的⼀个术语, 是属于 “抽象” 的叫法.
    所谓的 “主内存” 才是真正硬件角度的 “内存”. 而所谓的 “工作内存”, 则是指 CPU 的寄存器和高速缓存。

  2. 为啥要这么麻烦的拷来拷去?
    因为 CPU 访问自身寄存器的速度以及高速缓存的速度, 远远超过访问内存的速度(快了 3 - 4 个数量级,也就是几千倍, 上万倍).

那么接下来问题又来了, 既然访问寄存器速度这么快, 还要内存干啥??
答案就是⼀个字: 贵
在这里插入图片描述

五、synchronized 关键字 - 监视器锁 monitor lock

5.1 synchronized 的特性

1) 互斥
synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到同⼀个对象 synchronized 就会阻塞等待.

  • 进入 synchronized 修饰的代码块, 相当于 加锁
  • 退出 synchronized 修饰的代码块, 相当于 解锁
    注意:
  • 上⼀个线程解锁之后, 下⼀个线程并不是立即就能获取到锁. 而是要靠操作系统来 “唤醒”. 这也就是操作系统线程调度的⼀部分工作.
  • 假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁, 此时 B 和 C都在阻塞队列中排队等待. 但是当 A 释放锁之后, 虽然 B 比 C 先来的, 但是 B 不⼀定就能获取到锁,而是和 C 重新竞争, 并不遵守先来后到的规则.
    2) 可重入
    synchronized 同步块对同⼀条线程来说是可重入的,不会出现自己把自己锁死的问题。

5.2 synchronized 使用示例

synchronized 本质上要修改指定对象的 “对象头”. 从使用角度来看, synchronized 也势必要搭配⼀个具体的对象来使用。

1) 修饰代码块: 明确指定锁哪个对象。
锁任意对象

java">public class SynchronizedDemo {private Object locker = new Object();public void method() {synchronized (locker) {}}
}

锁当前对象

java">public class SynchronizedDemo {public void method() {synchronized (this) {}}
}

2) 直接修饰普通方法: 锁的 SynchronizedDemo 对象

java">public class SynchronizedDemo {public synchronized void methond() {}
}

3) 修饰静态方法: 锁的 SynchronizedDemo 类的对象

java">public class SynchronizedDemo {public synchronized static void method() {}
}

我们重点要理解,synchronized 锁的是什么. 两个线程竞争同一把锁, 才会产生阻塞等待

总结

多线程几乎是面试必问题,柚柚们一定要好好理解喔!!!
在这里插入图片描述


http://www.ppmy.cn/devtools/134279.html

相关文章

caozha-comment(原生PHP评论系统)

caozha-comment,一个功能强大的评论系统,采用原生PHP编写,不依赖任何框架,特点:易上手,零门槛,界面清爽极简,极便于二次开发。 可以自动适配电脑、平板和手机等不同客户端。 其他版…

基于OpenFOAM和深度学习驱动的流体力学计算与应用

在深度学习与流体力学深度融合的背景下,科研边界不断拓展,创新成果层出不穷。从物理模型融合到复杂流动模拟,从数据驱动研究到流场智能分析,深度学习正以前所未有的力量重塑流体力学领域。近期在Nature和Science杂志上发表的深度学…

使用Aria2实现离线下载

最近有需要BT下载,但有的资源很冷门,速度很慢,总不能一直开着电脑下载,于是想到部署个离线下载。想起之前用雨云服务器拿来部署兰空图床感觉效果不错,发现内存剩的还挺多,所以继续压榨一下😏 提…

经典文献阅读之--DROID-SLAM(完美的深度学习slam框架)

0. 简介 深度学习和SLAM现在结合越来越紧密了,但是实际上很多时候深度学习只会作为一个block放在slam系统中。而很多深度学习slam算法,在slam这边的性能都不是太好,尤其是回环和全局优化这块。因为有一些深度学习的工作就不太适合做回环检测…

Mac解压包安装MongoDB8并设置launchd自启动

记录一下在mac上安装mongodb8过程,本机是M3芯片所以下载m芯片的安装包,intel芯片的类似操作。 首先下载安装程序包。 # M芯片下载地址 https://fastdl.mongodb.org/osx/mongodb-macos-arm64-8.0.3.tgz # intel芯片下载地址 https://fastdl.mongodb.org…

Rust 数据类型

Rust 数据类型 Rust 是一种系统编程语言,以其内存安全性、速度和并发性而闻名。Rust 的设计理念是“零成本抽象”,这意味着它提供了高级语言的便利性,同时保持了接近低级语言的性能。Rust 的数据类型系统是其核心特性之一,它包括了几种不同的类型,用于处理各种编程场景。…

Kafka 学习笔记

文章目录 1、背景 1、背景 Kafka是由Apache软件基金会开发的一个开源流处理平台,由Scala和Java编写。Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者在网站中的所有动作流数据。 发布订阅(Publish-Subscribe)消息…

力扣617:合并二叉树

给你两棵二叉树: root1 和 root2 。 想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠&#…