Python微信订餐小程序课程视频

https://edu.csdn.net/course/detail/36074

Python实战量化交易理财系统

https://edu.csdn.net/course/detail/35475
线程安全是指某个方法或某段代码,在多线程中能够正确的执行,不会出现数据不一致或数据污染的情况,我们把这样的程序称之为线程安全的,反之则为非线程安全的。在 Java 中,解决线程安全问题有以下 3 种手段:

  1. 使用线程安全类,比如 AtomicInteger。
  2. 加锁排队执行
    1. 使用 synchronized 加锁。
    2. 使用 ReentrantLock 加锁。
  3. 使用线程本地变量 ThreadLocal。

接下来我们逐个来看它们的实现。

线程安全问题演示

我们创建一个变量 number 等于 0,之后创建线程 1,执行 100 万次 ++ 操作,同时再创建线程 2 执行 100 万次 – 操作,等线程 1 和线程 2 都执行完之后,打印 number 变量的值,如果打印的结果为 0,则说明是线程安全的,否则则为非线程安全的,示例代码如下:

public class ThreadSafeTest {// 全局变量private static int number = 0;// 循环次数(100W)private static final int COUNT = 1\_000\_000;public static void main(String[] args) throws InterruptedException {// 线程1:执行 100W 次 ++ 操作Thread t1 = new Thread(() -> {for (int i = 0; i < COUNT; i++) {number++;}});t1.start();// 线程2:执行 100W 次 -- 操作Thread t2 = new Thread(() -> {for (int i = 0; i < COUNT; i++) {number--;}});t2.start();// 等待线程 1 和线程 2,执行完,打印 number 最终的结果t1.join();t2.join();System.out.println("number 最终结果:" + number);}
}

以上程序的执行结果如下图所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/98b89b9acac783454f55b813aa4354fc.png#clientId=u94b6bb1a-9208-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=237&id=u66380cb5&margin=[object Object]&name=image.png&originHeight=473&originWidth=2133&originalType=binary&ratio=1&rotation=0&showTitle=false&size=62571&status=done&style=none&taskId=uf63710cb-0dab-41ca-a408-967d57e04cb&title=&width=1066.5)
从上述执行结果可以看出,number 变量最终的结果并不是 0,和预期的正确结果不相符,这就是多线程中的线程安全问题。

解决线程安全问题

1.原子类AtomicInteger

AtomicInteger 是线程安全的类,使用它可以将 ++ 操作和 – 操作,变成一个原子性操作,这样就能解决非线程安全的问题了,如下代码所示:

import java.util.concurrent.atomic.AtomicInteger;public class AtomicIntegerExample {// 创建 AtomicIntegerprivate static AtomicInteger number = new AtomicInteger(0);// 循环次数private static final int COUNT = 1\_000\_000;public static void main(String[] args) throws InterruptedException {// 线程1:执行 100W 次 ++ 操作Thread t1 = new Thread(() -> {for (int i = 0; i < COUNT; i++) {// ++ 操作number.incrementAndGet();}});t1.start();// 线程2:执行 100W 次 -- 操作Thread t2 = new Thread(() -> {for (int i = 0; i < COUNT; i++) {// -- 操作number.decrementAndGet();}});t2.start();// 等待线程 1 和线程 2,执行完,打印 number 最终的结果t1.join();t2.join();System.out.println("最终结果:" + number.get());}
}

以上程序的执行结果如下图所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/eac02de048d40a6bb19415d09dca665e.png#clientId=uc9b5b0b8-16f6-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=238&id=u26d96190&margin=[object Object]&name=image.png&originHeight=475&originWidth=1998&originalType=binary&ratio=1&rotation=0&showTitle=false&size=58030&status=done&style=none&taskId=ub7550b03-3b88-4f1f-8828-2eac206cc6c&title=&width=999)

2.加锁排队执行

Java 中有两种锁:synchronized 同步锁和 ReentrantLock 可重入锁。

2.1 同步锁synchronized

synchronized 是 JVM 层面实现的自动加锁和自动释放锁的同步锁,它的实现代码如下:

public class SynchronizedExample {// 全局变量private static int number = 0;// 循环次数(100W)private static final int COUNT = 1\_000\_000;public static void main(String[] args) throws InterruptedException {// 线程1:执行 100W 次 ++ 操作Thread t1 = new Thread(() -> {for (int i = 0; i < COUNT; i++) {// 加锁排队执行synchronized (SynchronizedExample.class) {number++;}}});t1.start();// 线程2:执行 100W 次 -- 操作Thread t2 = new Thread(() -> {for (int i = 0; i < COUNT; i++) {// 加锁排队执行synchronized (SynchronizedExample.class) {number--;}}});t2.start();// 等待线程 1 和线程 2,执行完,打印 number 最终的结果t1.join();t2.join();System.out.println("number 最终结果:" + number);}
}

以上程序的执行结果如下图所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/878949ab633deea3e681ac6aba61a563.png#clientId=uaa1c8ab9-eb34-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=235&id=uf922960d&margin=[object Object]&name=image.png&originHeight=470&originWidth=2002&originalType=binary&ratio=1&rotation=0&showTitle=false&size=60072&status=done&style=none&taskId=uf776a51b-e562-4a41-aea3-ac023887929&title=&width=1001)

2.2 可重入锁ReentrantLock

ReentrantLock 可重入锁需要程序员自己加锁和释放锁,它的实现代码如下:

import java.util.concurrent.locks.ReentrantLock;/*** 使用 ReentrantLock 解决非线程安全问题*/
public class ReentrantLockExample {// 全局变量private static int number = 0;// 循环次数(100W)private static final int COUNT = 1\_000\_000;// 创建 ReentrantLockprivate static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) throws InterruptedException {// 线程1:执行 100W 次 ++ 操作Thread t1 = new Thread(() -> {for (int i = 0; i < COUNT; i++) {lock.lock();    // 手动加锁number++;       // ++ 操作lock.unlock();  // 手动释放锁}});t1.start();// 线程2:执行 100W 次 -- 操作Thread t2 = new Thread(() -> {for (int i = 0; i < COUNT; i++) {lock.lock();    // 手动加锁number--;       // -- 操作lock.unlock();  // 手动释放锁}});t2.start();// 等待线程 1 和线程 2,执行完,打印 number 最终的结果t1.join();t2.join();System.out.println("number 最终结果:" + number);}
}

以上程序的执行结果如下图所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/d9d62308a188ee38080fb4920b381485.png#clientId=uaa1c8ab9-eb34-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=233&id=u59ecafbf&margin=[object Object]&name=image.png&originHeight=465&originWidth=1931&originalType=binary&ratio=1&rotation=0&showTitle=false&size=58888&status=done&style=none&taskId=u4c520a91-e5f1-4028-a55a-e2523875035&title=&width=965.5)

3.线程本地变量ThreadLocal

使用 ThreadLocal 线程本地变量也可以解决线程安全问题,它是给每个线程独自创建了一份属于自己的私有变量,不同的线程操作的是不同的变量,所以也不会存在非线程安全的问题,它的实现代码如下:

public class ThreadSafeExample {// 创建 ThreadLocal(设置每个线程中的初始值为 0)private static ThreadLocal threadLocal = ThreadLocal.withInitial(() -> 0);// 全局变量private static int number = 0;// 循环次数(100W)private static final int COUNT = 1\_000\_000;public static void main(String[] args) throws InterruptedException {// 线程1:执行 100W 次 ++ 操作Thread t1 = new Thread(() -> {try {for (int i = 0; i < COUNT; i++) {// ++ 操作threadLocal.set(threadLocal.get() + 1);}// 将 ThreadLocal 中的值进行累加number += threadLocal.get();} finally {threadLocal.remove(); // 清除资源,防止内存溢出}});t1.start();// 线程2:执行 100W 次 -- 操作Thread t2 = new Thread(() -> {try {for (int i = 0; i < COUNT; i++) {// -- 操作threadLocal.set(threadLocal.get() - 1);}// 将 ThreadLocal 中的值进行累加number += threadLocal.get();} finally {threadLocal.remove(); // 清除资源,防止内存溢出}});t2.start();// 等待线程 1 和线程 2,执行完,打印 number 最终的结果t1.join();t2.join();System.out.println("最终结果:" + number);}
}

以上程序的执行结果如下图所示:
![image.png](https://img-blog.csdnimg.cn/img_convert/9fcefdc60b493a2b0ebc89c0e3a87e15.png#clientId=u1cda0e9f-319a-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=239&id=u9a104fac&margin=[object Object]&name=image.png&originHeight=478&originWidth=1922&originalType=binary&ratio=1&rotation=0&showTitle=false&size=56925&status=done&style=none&taskId=u1f4e7158-55bb-4792-92da-e5e26328e6f&title=&width=961)

总结

在 Java 中,解决线程安全问题的手段有 3 种:1.使用线程安全的类,如 AtomicInteger 类;2.使用锁 synchronized 或 ReentrantLock 加锁排队执行;3.使用线程本地变量 ThreadLocal 来处理。

是非审之于己,毁誉听之于人,得失安之于数。

公众号:Java面试真题解析

面试合集:https://gitee.com/mydb/interview

面试突击37:线程安全问题的解决方案有哪些?相关推荐

  1. SimpleDateFormat类的线程安全问题和解决方案

    摘要:我们就一起看下在高并发下SimpleDateFormat类为何会出现安全问题,以及如何解决SimpleDateFormat类的安全问题. 本文分享自华为云社区<SimpleDateForm ...

  2. java多线程安全解决方案_《Java多线程编程核心技术(第2版)》 —1.2.8 实例变量共享造成的非线程安全问题与解决方案...

    1.2.8 实例变量共享造成的非线程安全问题与解决方案 自定义线程类中的实例变量针对其他线程可以有共享与不共享之分,这在多个线程之间交互时是很重要的技术点. 1.不共享数据的情况 不共享数据的情况如图 ...

  3. 线程安全问题及其解决方案

    一:线程安全问题的原因: 1.线程是抢占式执行的,即具有随机性 2.多线程的情况下,线程同时去修改同一个数据或者有的线程在修改数据,有的线程在读取数据 3.一条语句对应多个指令,这些指令操作并不是原子 ...

  4. spring单例模式与线程安全问题的解决方案

    一句话总结:1:spring容器创建对象的方式是单例的 2:spring单例模式的安全问题是使用ThreadLocal解决的 单例模式的意思就是只有一个实例.单例模式确保某一个类只有一个实例,而且自行 ...

  5. java中SimpleDateFormat线程安全问题及解决方案

    最近看到一篇文章提到了SimpleDateFormat这个类.说这个类在单线程程序中没问题,但是在多线程环境下会线程安全的问题. 出于兴趣对这个问题进行了查证.网上有很多关于这个问题的文章,也解析了其 ...

  6. SimpleDateFormat 的线程安全问题与解决方案

    SimpleDateFormat 的线程安全问题 SimpleDateFormat 是一个以国别敏感的方式格式化和分析数据的具体类. 它允许格式化 (date -> text).语法分析 (te ...

  7. Java进阶:ArrayList线程安全问题和解决方案

    文章目录 问题介绍 异常分析 问题解决 问题介绍 ArrayList 是线程不安全的集合类,当多线程环境下,并发对同一个ArrayList执行add,可能会抛出java.util.Concurrent ...

  8. 面试突击:线程池有几种创建方式?推荐使用哪种?

    在 Java 语言中,并发编程都是通过创建线程池来实现的,而线程池的创建方式也有很多种,每种线程池的创建方式都对应了不同的使用场景,总体来说线程池的创建可以分为以下两类: 通过 ThreadPoolE ...

  9. 面试突击第一季完结:共 91 篇!

    感谢各位读者的支持与阅读,面试突击系列第一季到这里就要和大家说再见了. 希望所写内容对大家有帮助,也祝你们找到满意的工作. 青山不改,细水长流,我们下一季再见! 91:MD5 加密安全吗? 90:过滤 ...

最新文章

  1. [FZSZOJ 1029] 观察者加强版
  2. php dao类设计,DAO数据访问对象设计 - GoFrame官网 - 类似PHP-Laravel, Java-SpringBoot的Go企业级开发框架...
  3. 好久没弄数学了,一本书上出现了,应该是指代了什么意思,问下.
  4. Heart Rate Estimate
  5. Dictionary集合
  6. [ Typescript 手册] JavaScript `Array` 在 Typescript 中的接口
  7. linux进程挂起的原因6,linux – 如何找出ssh进程挂起的原因?
  8. h3c无线控制器常用命令(wx)
  9. 基于simulink的模糊PID控制器设计与实现
  10. 毫米和像素怎么换算_像素换算(像素和毫米换算器)
  11. figma学习记录 快捷键 工具了解(1)
  12. Xcode8注释快捷键不能使用
  13. HarmonyOS:Preferences的封装使用与避坑
  14. 消费者人群画像 python_一步一步教你分析消费者大数据
  15. 中科院计算机网络信息中心是一种怎样的存在?
  16. Java服务,CPU100%问题如何快速定位?
  17. 分享!快速申请抖音企业蓝V认证的方案
  18. 电脑磁盘占用100%解决办法
  19. 那些长期喝咖啡的人,现在都怎么样了?
  20. 达梦数据库基础2-数据库实例(Linux)

热门文章

  1. crm2011下载报表
  2. 在ubuntu10.10, 打印机MFC-7340安装
  3. 502粘到手上变硬了怎么办_502粘住手指了怎么办
  4. pg数据库的备份和恢复以及sql脚本错误的解决方法
  5. 【Android M】预制的 Google GMS包
  6. CSS之优先级(6大类)、权重(4等级)、从高到低优先顺序
  7. gba测试软件,用手机玩GBA游戏 掌机模拟App《Delta》展开测试
  8. 关于模拟器上出现Installation error: INSTALL_FAILED_MISSING_SHARED_LIBRARY
  9. 生物制药企业如何安全供电保生产
  10. 数据分析经典书籍大全