线程安全问题及解决方案(上)

简介: 线程安全问题及解决方案(上)

影响线程安全问题的因素有很多

包括但不限于:

  • 抢占式执行(主要原因)
  • 多个线程修改同一个变量
  • 修改操作,不是原子的(这里所说的原子,是指不可分割)

本篇将通过实例对上述原因进行讲解

🔎1.示例

🌻示例代码

class Counter {
    public int count;
    public void add() {
        count++;
    }
}
public class Test1 {
    public static void main(String[] args) {
        int n = 50_000;
        Counter cnt = new Counter();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < n; i++) {
                cnt.add();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < n; i++) {
                cnt.add();
            }
        });
        t1.start();
        t2.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(cnt.count);
    }
}

代码描述:

  • 上述代码启动了 t1,t2两个线程
  • 每个线程分别执行cnt.add()50000次
  • 最后打印出count的值

🌻运行结果

运行结果

运行结果

运行结果

上图表示代码的运行结果

对于上述这段代码,由于是多线程进行运行,所以其运行结果不一定是10W.

也可以通俗的理解为一个简单的BUG


🌻原因分析

那么是因为什么导致的这个BUG呢?

主要原因有3个

  • 1.抢占式执行(主要原因),这个可能不太好理解请看下图
  • 2.多个线程修改同一个变量(这里由于 t1,t2两个线程同时对count值进行修改,所以结果是不确定的)那么什么操作时安全的呢?
  • 一个线程修改同一个变量(安全)
  • 多个线程读取同一个变量(安全)
  • 多个线程修改多个不同的变量(安全)
  • 3.修改操作,不是原子的(原子性只得是不可再被分割)
    修改操作可以被划分为load(读取)add(修改)save(保存)
    那么什么是原子性的操作呢?一个简单的例子就是赋值(a = 1)

🌼抢占式执行的几种可能

load(读取),线程读取数据的值

add(修改),线程修改读取到的数据的值

save(保存),线程将修改的数据值返回

这3个步骤必须连续,结果才能是正确的

case1

case2

case3

case4

case5

注意:以上所罗列的只是部分情况,并非全部

对于上述情况,只有case4case5的执行结果是正确的


🌼模拟case

下面来模拟实现case1

case1


🌼原因分析

对于case1

(1)多个线程对同一个变量进行修改

(2)修改操作不是原子性

(3)线程之间进行了抢占式执行(操作顺序不同)

最终导致了BUG的发生


🌻解决方案

为该方法加锁

当为该操作加锁后,一个线程执行该方法时,另一个线程就需要进行等待,就不再会出现抢占式执行

Java中,synchronized{}代表锁,在括号体内部就是为该对象加锁,出了括号就代表解锁

运行结果


🌼加锁的实现方式

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

只为count++操作进行加锁,哪个对象调用的add()方法,this就指代哪个对象

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

为add()方法加锁

synchronized public static void add() {
}

为静态方法加锁

public static void add() {
        synchronized(Counter.class) {
        }
};

为静态方法加锁

需要注意的是,synchronized修饰sttatic(静态方法)的时候,是给类对象进行加锁


🔎结尾

创作不易,如果对您有帮助,希望您能点个免费的赞👍

大家有什么不太理解的,可以私信或者评论区留言,一起加油

相关文章
|
20天前
|
安全 Java 开发者
丢失的8小时去哪里了?SimpleDateFormat线程不安全,多线程初始化异常解决方案
丢失的8小时去哪里了?SimpleDateFormat线程不安全,多线程初始化异常解决方案
44 0
|
20天前
|
数据处理
多线程与并发编程【线程对象锁、死锁及解决方案、线程并发协作、生产者与消费者模式】(四)-全面详解(学习总结---从入门到深化)
多线程与并发编程【线程对象锁、死锁及解决方案、线程并发协作、生产者与消费者模式】(四)-全面详解(学习总结---从入门到深化)
47 1
|
9月前
|
Java 测试技术 数据处理
Java多线程父线程向子线程传值解决方案 2
Java多线程父线程向子线程传值解决方案
95 0
|
9月前
|
Java
Java多线程父线程向子线程传值解决方案 1
Java多线程父线程向子线程传值解决方案
248 0
|
8天前
|
算法 Java
Java多线程基础-13:一文阐明死锁的成因及解决方案
死锁是指多个线程相互等待对方释放资源而造成的一种僵局,导致程序无法正常结束。发生死锁需满足四个条件:互斥、请求与保持、不可抢占和循环等待。避免死锁的方法包括设定加锁顺序、使用银行家算法、设置超时机制、检测与恢复死锁以及减少共享资源。面试中可能会问及死锁的概念、避免策略以及实际经验。
17 1
|
14天前
|
监控 网络协议 iOS开发
程序退到后台的时候,所有线程被挂起,系统回收所有的socket资源问题及解决方案
程序退到后台的时候,所有线程被挂起,系统回收所有的socket资源问题及解决方案
31 0
|
14天前
|
存储 网络协议 iOS开发
connect永远阻塞线程及解决方案
connect永远阻塞线程及解决方案
18 0
|
20天前
|
Java
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
31 1
|
20天前
|
存储 缓存 安全
【Java多线程】线程安全问题与解决方案
【Java多线程】线程安全问题与解决方案
26 1
|
20天前
|
存储 消息中间件 Java
Java多线程实战-异步操作日志记录解决方案(AOP+注解+多线程)
Java多线程实战-异步操作日志记录解决方案(AOP+注解+多线程)