Java多线程知识汇总(二)

目录

  • 一、Java多线程
  • 1、进程与线程
  • 2、并行与并发
  • 二、线程的礼让
  • 三、线程的优先级
  • 四、守护线程
  • 五、线程的阻塞
  •  六、线程的打断
  •  七、线程的相关方法总结
  • 同步锁
  • 线程安全
  • synchronized
  • 线程通信
  • wait+notify

一、Java多线程

1、进程与线程

进程

  • 当一个程序被运行,就开启了一个进程, 比如启动了qq,word
  • 程序由指令和数据组成,指令要运行,数据要加载,指令被cpu加载运行,数据被加载到内存,指令运行时可由cpu调度硬盘、网络等设备

线程

  • 一个进程内可分为多个线程
  • 一个线程就是一个指令流,cpu调度的最小单位,由cpu一条一条执行指令

2、并行与并发

并发:单核cpu运行多线程时,时间片进行很快的切换。线程轮流执行cpu

并行:多核cpu运行 多线程时,真正的在同一时刻运行

线程的并行与并发
线程的并行与并发

二、线程的礼让

        yield()方法会让运行中的线程切换到就绪状态,重新争抢cpu的时间片,争抢时是否获取到时间片看cpu的分配。

示例代码:

// 方法的定义
public static native void yield();Runnable r1 = () -> {int count = 0;for (;;){log.info("---- 1>" + count++);}
};
Runnable r2 = () -> {int count = 0;for (;;){Thread.yield(); //礼让log.info("            ---- 2>" + count++);}
};
Thread t1 = new Thread(r1,"t1");
Thread t2 = new Thread(r2,"t2");
t1.start();
t2.start();

运行结果:

11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129504
11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129505
11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129506
11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129507
11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129508
11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129509
11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129510
11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129511
11:49:15.796 [t1] INFO thread.TestYield - ---- 1>129512
11:49:15.798 [t2] INFO thread.TestYield -             ---- 2>293
11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129513
11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129514
11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129515
11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129516
11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129517
11:49:15.798 [t1] INFO thread.TestYield - ---- 1>129518

        如上结果所示,t2线程每次执行时进行了yield(),线程1执行的机会明显比线程2要多。

三、线程的优先级

线程内部用1~10的数来调整线程的优先级,默认的线程优先级为NORM_PRIORITY:5

​ cpu比较忙时,优先级高的线程获取更多的时间片

​ cpu比较闲时,优先级设置基本没用

 public final static int MIN_PRIORITY = 1;public final static int NORM_PRIORITY = 5;public final static int MAX_PRIORITY = 10;// 方法的定义public final void setPriority(int newPriority) {}

cpu比较忙时

Runnable r1 = () -> {int count = 0;for (;;){log.info("---- 1>" + count++);}
};
Runnable r2 = () -> {int count = 0;for (;;){log.info("            ---- 2>" + count++);}
};
Thread t1 = new Thread(r1,"t1");
Thread t2 = new Thread(r2,"t2");
t1.setPriority(Thread.NORM_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();// 可能的运行结果
11:59:00.696 [t1] INFO thread.TestYieldPriority - ---- 1>44102
11:59:00.696 [t2] INFO thread.TestYieldPriority -             ---- 2>135903
11:59:00.696 [t2] INFO thread.TestYieldPriority -             ---- 2>135904
11:59:00.696 [t2] INFO thread.TestYieldPriority -             ---- 2>135905
11:59:00.696 [t2] INFO thread.TestYieldPriority -             ---- 2>135906

四、守护线程

默认情况下,java进程需要等待所有线程都运行结束,才会结束,有一种特殊线程叫守护线程,当所有的非守护线程都结束后,即使它没有执行完,也会强制结束。

默认的线程都是非守护线程。

垃圾回收线程就是典型的守护线程

// 方法的定义
public final void setDaemon(boolean on) {
}Thread thread = new Thread(() -> {while (true) {}
});
// 具体的api。设为true表示未守护线程,当主线程结束后,守护线程也结束。
// 默认是false,当主线程结束后,thread继续运行,程序不停止
thread.setDaemon(true);
thread.start();
log.info("结束");

五、线程的阻塞

线程的阻塞可以分为好多种,从操作系统层面和java层面阻塞的定义可能不同,但是广义上使得线程阻塞的方式有下面几种:

  1. BIO阻塞,即使用了阻塞式的io流
  2. sleep(long time) 让线程休眠进入阻塞状态
  3. a.join() 调用该方法的线程进入阻塞,等待a线程执行完恢复运行
  4. sychronized或ReentrantLock 造成线程未获得锁进入阻塞状态 (同步锁章节细说)
  5. 获得锁之后调用wait()方法 也会让线程进入阻塞状态 (同步锁章节细说)
  6. LockSupport.park() 让线程进入阻塞状态 (同步锁章节细说)

 六、线程的打断

// 相关方法的定义
public void interrupt() {
}
public boolean isInterrupted() {
}
public static boolean interrupted() {
}

打断标记:线程是否被打断,true表示被打断了,false表示没有

isInterrupted() 获取线程的打断标记 ,调用后不会修改线程的打断标记

interrupt()方法用于中断线程:

  1. 可以打断sleep,wait,join等显式的抛出InterruptedException方法的线程,但是打断后,线程的打断标记还是false
  2. 打断正常线程 ,线程不会真正被中断,但是线程的打断标记为true

interrupted() 获取线程的打断标记,调用后清空打断标记 即如果获取为true 调用后打断标记为false (不常用)

interrupt实例: 有个后台监控线程不停的监控,当外界打断它时,就结束运行。代码如下

@Slf4j
class TwoPhaseTerminal{// 监控线程private Thread monitor;public void start(){monitor = new Thread(() ->{// 不停的监控while (true){Thread thread = Thread.currentThread();// 判断当前线程是否被打断if (thread.isInterrupted()){log.info("当前线程被打断,结束运行");break;}try {Thread.sleep(1000);// 监控逻辑中被打断后,打断标记为truelog.info("监控");} catch (InterruptedException e) {// 睡眠时被打断时抛出异常 在该处捕获到 此时打断标记还是false// 在调用一次中断 使得中断标记为truethread.interrupt();}}});monitor.start();}public void stop(){monitor.interrupt();}
}

 七、线程的相关方法总结

主要总结Thread类中的核心方法

方法名称是否static方法说明
start()让线程启动,进入就绪状态,等待cpu分配时间片
run()重写Runnable接口的方法,线程获取到cpu时间片时执行的具体逻辑
yield()线程的礼让,使得获取到cpu时间片的线程进入就绪状态,重新争抢时间片
sleep(time)线程休眠固定时间,进入阻塞状态,休眠时间完成后重新争抢时间片,休眠可被打断
join()/join(time)调用线程对象的join方法,调用者线程进入阻塞,等待线程对象执行完或者到达指定时间才恢复,重新争抢时间片
isInterrupted()获取线程的打断标记,true:被打断,false:没有被打断。调用后不会修改打断标记
interrupt()打断线程,抛出InterruptedException异常的方法均可被打断,但是打断后不会修改打断标记,正常执行的线程被打断后会修改打断标记
interrupted()获取线程的打断标记。调用后会清空打断标记
stop()停止线程运行 不推荐
suspend()挂起线程 不推荐
resume()恢复线程运行 不推荐
currentThread()获取当前线程

Object中与线程相关方法

方法名称方法说明
wait()/wait(long timeout)获取到锁的线程进入阻塞状态
notify()随机唤醒被wait()的一个线程
notifyAll();唤醒被wait()的所有线程,重新争抢时间片

同步锁

线程安全

  • 一个程序运行多个线程本身是没有问题的
  • 问题有可能出现在多个线程访问共享资源

    • 多个线程都是读共享资源也是没有问题的
    • 当多个线程读写共享资源时,如果发生指令交错,就会出现问题

临界区: 一段代码如果对共享资源的多线程读写操作,这段代码就被称为临界区。

注意的是 指令交错指的是 java代码在解析成字节码文件时,java代码的一行代码在字节码中可能有多行,在线程上下文切换时就有可能交错。

线程安全指的是多线程调用同一个对象的临界区的方法时,对象的属性值一定不会发生错误,这就是保证了线程安全。

如下面不安全的代码:

// 对象的成员变量
private static int count = 0;public static void main(String[] args) throws InterruptedException {// t1线程对变量+5000次Thread t1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {count++;}});// t2线程对变量-5000次Thread t2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {count--;}});t1.start();t2.start();// 让t1 t2都执行完t1.join();t2.join();System.out.println(count);
}// 运行结果 
-1399

上面的代码 两个线程,一个+5000次,一个-5000次,如果线程安全,count的值应该还是0。

但是运行很多次,每次的结果不同,且都不是0,所以是线程不安全的。

线程安全的类一定所有的操作都线程安全吗?

开发中经常会说到一些线程安全的类,如ConcurrentHashMap,线程安全指的是类里每一个独立的方法是线程安全的,但是方法的组合就不一定是线程安全的

成员变量和静态变量是否线程安全?

  • 如果没有多线程共享,则线程安全
  • 如果存在多线程共享

    • 多线程只有读操作,则线程安全
    • 多线程存在写操作,写操作的代码又是临界区,则线程不安全

局部变量是否线程安全?

  • 局部变量是线程安全的
  • 局部变量引用的对象未必是线程安全的

    • 如果该对象没有逃离该方法的作用范围,则线程安全
    • 如果该对象逃离了该方法的作用范围,比如:方法的返回值,需要考虑线程安全

synchronized

同步锁也叫对象锁,是锁在对象上的,不同的对象就是不同的锁。

该关键字是用于保证线程安全的,是阻塞式的解决方案。

让同一个时刻最多只有一个线程能持有对象锁,其他线程在想获取这个对象锁就会被阻塞,不用担心上下文切换的问题。

注意: 不要理解为一个线程加了锁 ,进入 synchronized代码块中就会一直执行下去。如果时间片切换了,也会执行其他线程,再切换回来会紧接着执行,只是不会执行到有竞争锁的资源,因为当前线程还未释放锁。

当一个线程执行完synchronized的代码块后 会唤醒正在等待的线程

synchronized实际上使用对象锁保证临界区的原子性 临界区的代码是不可分割的 不会因为线程切换所打断

基本使用:

// 加在方法上 实际是对this对象加锁
private synchronized void a() {
}// 同步代码块,锁对象可以是任意的,加在this上 和a()方法作用相同
private void b(){synchronized (this){}
}// 加在静态方法上 实际是对类对象加锁
private synchronized static void c() {}// 同步代码块 实际是对类对象加锁 和c()方法作用相同
private void d(){synchronized (TestSynchronized.class){}
}// 上述b方法对应的字节码源码 其中monitorenter就是加锁的地方0 aload_01 dup2 astore_13 monitorenter4 aload_15 monitorexit6 goto 14 (+8)9 astore_2
10 aload_1
11 monitorexit
12 aload_2
13 athrow
14 return

线程安全的代码:

private static int count = 0;private static Object lock = new Object();private static Object lock2 = new Object();// t1线程和t2对象都是对同一对象加锁。保证了线程安全。此段代码无论执行多少次,结果都是0
public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {synchronized (lock) {count++;}}});Thread t2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {synchronized (lock) {count--;}}});t1.start();t2.start();// 让t1 t2都执行完t1.join();t2.join();System.out.println(count);
}

重点:加锁是加在对象上,一定要保证是同一对象,加锁才能生效

线程通信

wait+notify

线程间通信可以通过共享变量+wait()&notify()来实现

wait()将线程进入阻塞状态,notify()将线程唤醒

当多线程竞争访问对象的同步方法时,锁对象会关联一个底层的Monitor对象(重量级锁的实现)

如下图所示 Thread0,1先竞争到锁执行了代码后,2,3,4,5线程同时来执行临界区的代码,开始竞争锁。

  1. Thread-0先获取到对象的锁,关联到monitor的owner,同步代码块内调用了锁对象的wait()方法,调用后会进入waitSet等待,Thread-1同样如此,此时Thread-0的状态为Waitting
  2. Thread2、3、4、5同时竞争,2获取到锁后,关联了monitor的owner,3、4、5只能进入EntryList中等待,此时2线程状态为 Runnable,3、4、5状态为Blocked
  3. 2执行后,唤醒entryList中的线程,3、4、5进行竞争锁,获取到的线程即会关联monitor的owner
  4. 3、4、5线程在执行过程中,调用了锁对象的notify()或notifyAll()时,会唤醒waitSet的线程,唤醒的线程进入entryList等待重新竞争锁

注意:

  1. Blocked状态和Waitting状态都是阻塞状态
  2. Blocked线程会在owner线程释放锁时唤醒
  3. wait和notify使用场景是必须要有同步,且必须获得对象的锁才能调用,使用锁对象去调用,否则会抛异常
  • wait() 释放锁 进入 waitSet 可传入时间,如果指定时间内未被唤醒 则自动唤醒
  • notify()随机唤醒一个waitSet里的线程
  • notifyAll()唤醒waitSet中所有的线程
static final Object lock = new Object();
new Thread(() -> {synchronized (lock) {log.info("开始执行");try {// 同步代码内部才能调用lock.wait();} catch (InterruptedException e) {e.printStackTrace();}log.info("继续执行核心逻辑");}
}, "t1").start();new Thread(() -> {synchronized (lock) {log.info("开始执行");try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}log.info("继续执行核心逻辑");}
}, "t2").start();try {Thread.sleep(2000);
} catch (InterruptedException e) {e.printStackTrace();
}
log.info("开始唤醒");synchronized (lock) {// 同步代码内部才能调用lock.notifyAll();
}
// 执行结果
14:29:47.138 [t1] INFO TestWaitNotify - 开始执行
14:29:47.141 [t2] INFO TestWaitNotify - 开始执行
14:29:49.136 [main] INFO TestWaitNotify - 开始唤醒
14:29:49.136 [t2] INFO TestWaitNotify - 继续执行核心逻辑
14:29:49.136 [t1] INFO TestWaitNotify - 继续执行核心逻辑

本文转自:万字图解Java多线程 - 个人文章 - SegmentFault 思否

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/241984.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SQL性能分析

SQL性能分析 1、SQL执行频率 ​ MySQL 客户端连接成功后&#xff0c;通过 show [session|global] status 命令可以提供服务器状态信 息。通过如下指令&#xff0c;可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次&#xff1a; -- session 是查看当前会话 ; …

《游戏-02_2D-开发》

基于《游戏-01_2D-开发》&#xff0c; 继续制作游戏&#xff1a; 首先给人物添加一个2D重力效果 在编辑的项目设置中&#xff0c; 可以看出unity默认给的2D重力数值是-9.81&#xff0c;模拟现实社会中的重力效果 下方可以设置帧率 而Gravity Scale代表 这个数值会 * 重力 还…

erlang (OS 操作模块)学习笔记

cmd: env: 返回所有环境变量的列表。 每个环境变量都表示为元组 {VarName&#xff0c;Value}&#xff0c;其中 VarName 是 变量和 Value 其值。 例: {VarName&#xff0c;Value} {"ERLANG_HOME","C:\\Program Files\\erl-24.3.4.2\\bin\\erl-24.3.4.2"}…

Linux多线程——互斥锁

本质Gitee仓库:互斥锁、锁封装 文章目录 1. 线程互斥2. 互斥锁2.1 锁的初始化与释放2.2 加锁与解锁 3. 锁的原理4. 锁的封装5. 线程安全与可重入函数 1. 线程互斥 一个共享资源在被多个线程并发访问的时候&#xff0c;可能会出现一个线程正在访问&#xff0c;而另一个线程又来…

Elasticsearch 分布式架构剖析及扩展性优化

1. 背景 Elasticsearch 是一个实时的分布式搜索分析引擎&#xff0c;简称 ES。一个集群由多个节点组成&#xff0c;节点的角色可以根据用户的使用场景自由配置&#xff0c;集群可以以节点为单位自由扩缩容&#xff0c;数据以索引、分片的形式散列在各个节点上。本文介绍 ES 分布…

交叉编译工具 aarch64-linux-gnu-gcc 的介绍与安装

AArch64 是随 ARMv8 ISA 一起引入的 64 位架构&#xff0c;用于执行 A64 指令的计算机。而且在 AArch64 状态下执行的代码只能使用 A64 指令集。&#xff0c;而不能执行 A32 或 T32 指令。但是&#xff0c;与 AArch32 中不同&#xff0c;在64位状态下&#xff0c;指令可以访问 …

离线数据仓库-关于增量和全量

数据同步策略 数据仓库同步策略概述一、数据的全量同步二、数据的增量同步三、数据同步策略的选择 数据仓库同步策略概述 应用系统所产生的业务数据是数据仓库的重要数据来源&#xff0c;我们需要每日定时从业务数据库中抽取数据&#xff0c;传输到数据仓库中&#xff0c;之后…

十八周周报

文章目录 摘要文献阅读3D reconstruction of human bodies from single-view and multi-view images: A systematic review简介研究方法搜索策略选择标准搜索结果 三维重建方法单个视图中使用的技术基于参数化人体模型的回归基于非参数人体模型的回归 多个视图中使用的技术基于…

傲空间私有部署Windows指南

推荐阅读 智能化校园&#xff1a;深入探讨云端管理系统设计与实现&#xff08;一&#xff09; 智能化校园&#xff1a;深入探讨云端管理系统设计与实现&#xff08;二&#xff09; 安装 docker 请下载对应的 Docker&#xff0c;安装完成后启动。 Docker Desktop for Windows…

【论文笔记】Fully Sparse 3D Panoptic Occupancy Prediction

原文链接&#xff1a;https://arxiv.org/abs/2312.17118 1. 引言 现有的3D占用预测方法建立密集的3D特征&#xff0c;没有考虑场景的稀疏性&#xff0c;因此难以满足实时要求。此外&#xff0c;这些方法仅关注语义占用&#xff0c;无法区分实例。 本文认为场景的稀疏性包含两…

使用Sqoop从Oracle数据库导入数据

在大数据领域&#xff0c;将数据从关系型数据库&#xff08;如Oracle&#xff09;导入到Hadoop生态系统是一项常见的任务。Sqoop是一个强大的工具&#xff0c;可以帮助轻松完成这项任务。本文将提供详细的指南&#xff0c;以及丰富的示例代码&#xff0c;帮助了解如何使用Sqoop…

java:流程控制

一、流程控制语句分类 顺序结构分支结构&#xff08;if&#xff0c;switch&#xff09;循环结构&#xff08;for&#xff0c;while&#xff0c;do...while&#xff09; 二、顺序结构 定义&#xff1a;顺序结构是程序中最基本的流程控制&#xff0c;没有特定的语法结构&#…

MySQL三大日志

1. redo log 1.1 特点 InnoDB存储引擎独有物理日志&#xff0c;记录在数据页上做的修改让MySQL拥有了崩溃恢复能力&#xff0c;保证事务的持久性 1.2 刷盘时机 事务提交时log buffer 空间使用大约一半时事务日志缓冲区满InnoDB 定期执行检查点Checkpoint后台刷新线程&#…

【数据结构和算法】奇偶链表

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 2.1 方法一&#xff1a;分离节点后合并 三、代码 3.1 方法一&#xff1a;分离节点后合并 四、复杂度分…

详细介绍IP 地址、网络号和主机号、ABC三类、ip地址可分配问题、子网掩码、子网划分

1、 IP 地址: 网络之间互连的协议&#xff0c;是由4个字节(32位二进制)组成的逻辑上的地址。 将32位二进制进行分组&#xff0c;分成4组&#xff0c;每组8位(1个字节)。【ip地址通常使用十进制表示】ip地址分成四组之后&#xff0c;在逻辑上&#xff0c;分成网络号和主机号 2…

phpmyadmin 创建服务器

phpmyadmin默认的服务器是localhost 访问setup&#xff0c;创建新的服务器 添加服务器信息 点击应用&#xff0c;服务器创建成功 下载配置文件config.inc.php&#xff0c;放到WWW目录下 可再次访问setup&#xff0c;发现已配置过了 访问登录页面&#xff0c;发现可选…

关闭Windows自动更新的6种方法

关闭Windows自动更新的6种方法&#xff01; 方法一&#xff1a;通过Windows设置关闭Windows自动更新 步骤1. 按WinI打开Windows设置页面。步骤2. 单击“更新和安全”>“Windows更新”&#xff0c;然后在右侧详情页中选择“暂停更新7天”选项即可在此后7天内关闭Windows更新…

Go语言基础快速上手

1、Go语言关键字 2、Go数据类型 3、特殊的操作 3.1、iota关键字 Go中没有明确意思上的enum&#xff08;枚举&#xff09;定义&#xff0c;不过可以借用iota标识符实现一组自增常亮值来实现枚举类型。 const (a iota // 0b // 1c 100 // 100d // 100 (与上一…

自然语言处理(Natural Language Processing,NLP)解密

专栏集锦&#xff0c;大佬们可以收藏以备不时之需&#xff1a; Spring Cloud 专栏&#xff1a;http://t.csdnimg.cn/WDmJ9 Python 专栏&#xff1a;http://t.csdnimg.cn/hMwPR Redis 专栏&#xff1a;http://t.csdnimg.cn/Qq0Xc TensorFlow 专栏&#xff1a;http://t.csdni…

【前后端分离与不分离的区别】

Web 应用的开发主要有两种模式&#xff1a; 前后端不分离 前后端分离 理解它们的区别有助于我们进行对应产品的测试工作。 前后端不分离 在早期&#xff0c;Web 应用开发主要采用前后端不分离的方式&#xff0c;它是以后端直接渲染模板完成响应为主的一种开发模式。以前后端不…