0
点赞
收藏
分享

微信扫一扫

线程安全产生的原因以及解决方案

清冷的蓝天天 2022-03-26 阅读 61
java-ee

这里写自定义目录标题


引言

🍊线程安全产生的原因

🍊什么是线程安全

在操作系统中,因为线程的调度是随机的(抢占式执行),正是因为这中随机性,才会让代码中产生很多bug 如果认为是因为这样的线程调度才导致代码产生了bug,则认为线程是不安全的, 如果这样的调度,并没有让代码产生bug,我们则认为线程是安全的
这里的安全指代的是代码中有没有产生bug,与我们平常认为的安全是两种截然不同的概念,我们所熟知的安全是由黑客造成的,他们会不会侵入你的电脑💻,攻击你的计算机,这是我门不能够制止的,我们所要做的就是让代码不会产生bug.

🍊线程安全实例

栗子 🌰 :使用两个线程,对同一个整型变量进行自增操作,每个线程自增五万次,看最后的结果,代码如下

class Counter{
    int count = 0;
     public void increase(){
        count++;
    }
}
public class Demo1 {
    private static Counter counter = new Counter();
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        Thread thread2 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(counter.count);
    }
}

运行结果如下

  1. 在这里插入图片描述
  2. 在这里插入图片描述
  3. 在这里插入图片描述
    从 ⬆️ 面的运行结果,我们不难发现,每次的运行结果都是不同的值都是在50000~100000之间的值,这是为什么呢?

在此,我们需要了解一下count++,是如何在CPU上运行的?

请添加图片描述

请添加图片描述

请添加图片描述

请添加图片描述

请添加图片描述

🍊产生线程不安全的原因总结

  1. 线程是抢占式的,线程之间的调度充满随机性.[线程不安全的万恶之源,而且这是无法避免的,程序猿无可奈何😮‍💨]
  2. 多个线程对同一个变量进行修改~~(多个线程对不同的变量进行修改是没问题的,多个线程对同一个变量进行读操作也是没问题的),这是可以控制的,通过对代码的结构进行修改,使不同线程操作不同的变量
  3. 针对变量的操作不是原子性的,这就是上面的load->add->save等指令,这些操作是绑定的,原子性的,把多个指令看成一个.这需要加锁才能解决
  4. 内存可见性(编译器优化)
    这是很难理解的,我们需要举一个具体的🌰

请添加图片描述

对于这种情况我们可以使用synchronizedvolatile关键字解决,我们在此先不做详解介绍,一会在分析

  1. 指令重排序

请添加图片描述
系统不按照我们所给的命令执行程序,而是为了提高效率,改变指令的执行顺序,这样就会产生bug,这也是编译器认为程序猿瞎b写代码的案列(编译器优化)

🍉线程不安全的解决方案(加锁)

我们主要对第2,3条原因进行干涉,这就需要加锁的方法

🍉加锁的概念

加锁

那么,讲到这里就会有人问,那么,上了锁之后,并发编程不就是很鸡肋了吗?
这是不对的!!!

🍉加锁的好处与意义

🍉怎么进行加锁

🍉 synchronized关键字

1. 使用关键字synchronized****(一定要记住怎么读和拼写!!!)

使用🌰

class Counter{
    int count = 0;
    synchronized public void increase(){
        count++;
    }
}
public class Demo1 {
    private static Counter counter = new Counter();
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        Thread thread2 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(counter.count);
    }
}

运行结果

请添加图片描述

  1. 如何使用synchronized
    synchronized本质操作就是修改了Object对象中的对象头里的一个标记
synchronized public void increase(){
        count++;
    }
class Counter{
    int count = 0;
     public void increase(){
         synchronized(this){
             count++;
         }
    }
}
synchronized public static void func(){}

相当于

public static void func(){
         synchronized (Counter.class){
             
         }
     }

以上就是synchronized的使用方法!!!(单词一定要会读,会拼)

🍉volatile关键字

class Counter{
    volatile int count = 0;
      public void increase(){
             count++;
    }
     public static void func(){
         synchronized (Counter.class){

         }
     }
}
public class Demo1 {
    private static Counter counter = new Counter();
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        Thread thread2 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(counter.count);
    }
}

请添加图片描述
从上面的代码中,我们得知,volatile不能保证线程的安全性.

🍉synchronized与volatile的区别

那么,有人会问,既然synchronized的功能更全,直接无脑用就好了?
❌ 这是不对的!!!

所以说具体问题具体分析,需要知道你需要干什么,在选择相应的关键字来加锁

举报

相关推荐

0 条评论