多线程(线程安全)

简介: 多线程(线程安全)

线程安全的根本原因: 线程之间的抢占式执行, 随机调度


线程安全问题示例

最简单的, 两个线程对同一个变量各增加 5000 次, 最终输出该变量, 结果并不是我们理想的 增加了 10000 次

class Counter {
    public int count;
    
    public void add() {
        count++;
    }
}

public class Main{
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) counter.add();
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) counter.add();
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.count);
    }
}

运行结果:

可以看出, 多次运行的结果都不一样


线程不安全的原因

一般来说, 线程不安全有以下几种原因:

  1. [根本原因] 抢占式执行, 随机调度
  2. 代码结构: 多个线程同时修改同一个变量
  3. 原子性, 如果修改操作是原子的, 一般不会有线程安全问题, 如果是非原子的,多线程环境中出 bug 的概率就很高
  4. 内存可见性
  5. 指令重排序 (本质是编译器的优化出 bug 了)

对于上述代码而言, 线程不安全是因为 ++ 操作不是原子的,以及线程之间的调度顺序问题

++ 操作本质上要分成三步

  1. 把内存中的值读取到 CPU 的寄存器中 – load
  2. 把 CPU 寄存器的数值进行 +1 运算 – add
  3. 把得到的结果写回到内存中 – save

如果每个线程中的这三步都能顺序执行, 中间不被其他线程所影响, 自然没有线程安全问题 (也就和单线程没有区别了)


如果是当前线程的三个步骤中间穿插着其他线程的一些操作, 就有线程安全问题了 (这里只给出两例, 还记得 大明湖畔(MySQL) 的 的夏雨荷(脏读)嘛, 是不是很像 …)


如何解决线程安全问题?

加锁!!! – synchronized, 其实就是把一堆非原子的操作, 通过加锁变成一个原子操作

synchronized 使用方法:

  1. 修饰方法
    1.1修饰普通方法 (修饰的是 this)
    1.2 修饰静态方法 (修饰的是类对象 )
  2. 修饰代码块 (显示/手动指定锁对象)

sychronized 对 “对象” 加锁

修饰普通方法, 谁调用该方法, 就把谁加锁

修饰静态方法, 对该静态方法所在的类对象加锁

修饰代码块, 对指定的对象加锁

如果多个线程针对同一个对象加锁, 就会产生锁竞争/锁冲突, 其中一个线程获取到锁, 正常执行, 其他线程就进入阻塞状态, 等待锁资源被释放, 再重新竞争锁 (这里的操作本质上是把并行变成了串行,顺序执行就不会有线程安全问题了)

对示例代码加锁解决线程安全问题

针对 add() 方法加锁 (其实是对调用 add() 方法的对象(实例) 加锁)

synchronized public void add() {
   count++;
}

针对代码块加锁 (对this对象加锁, 其实还是对调用该方法的对象(实例) 加锁)

public void add() {
    synchronized (this) {
        count++;
    }
}

Java 标准库中的线程安全类

内置 synchronized 加锁的类, 相对来说安全

还有些类不涉及"修改"操作


目录
相关文章
|
2月前
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
64 1
|
4天前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
12天前
|
Java Spring
spring多线程实现+合理设置最大线程数和核心线程数
本文介绍了手动设置线程池时的最大线程数和核心线程数配置方法,建议根据CPU核数及程序类型(CPU密集型或IO密集型)来合理设定。对于IO密集型,核心线程数设为CPU核数的两倍;CPU密集型则设为CPU核数加一。此外,还讨论了`maxPoolSize`、`keepAliveTime`、`allowCoreThreadTimeout`和`queueCapacity`等参数的设置策略,以确保线程池高效稳定运行。
72 10
spring多线程实现+合理设置最大线程数和核心线程数
|
20天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
35 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
6天前
|
Python
5-5|python开启多线程入口必须在main,从python线程(而不是main线程)启动pyQt线程有什么坏处?...
5-5|python开启多线程入口必须在main,从python线程(而不是main线程)启动pyQt线程有什么坏处?...
|
22天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
38 10
|
3天前
|
NoSQL 网络协议 Unix
1)Redis 属于单线程还是多线程?不同版本之间有什么区别?
1)Redis 属于单线程还是多线程?不同版本之间有什么区别?
8 0
|
4天前
|
Java
COMATE插件实现使用线程池高级并发模型简化多线程编程
本文介绍了COMATE插件的使用,该插件通过线程池实现高级并发模型,简化了多线程编程的过程,并提供了生成结果和代码参考。
|
29天前
|
存储 Ubuntu Linux
C语言 多线程编程(1) 初识线程和条件变量
本文档详细介绍了多线程的概念、相关命令及线程的操作方法。首先解释了线程的定义及其与进程的关系,接着对比了线程与进程的区别。随后介绍了如何在 Linux 系统中使用 `pidstat`、`top` 和 `ps` 命令查看线程信息。文档还探讨了多进程和多线程模式各自的优缺点及适用场景,并详细讲解了如何使用 POSIX 线程库创建、退出、等待和取消线程。此外,还介绍了线程分离的概念和方法,并提供了多个示例代码帮助理解。最后,深入探讨了线程间的通讯机制、互斥锁和条件变量的使用,通过具体示例展示了如何实现生产者与消费者的同步模型。