首页 > 其他分享 >原来这就是线程安全(一)

原来这就是线程安全(一)

时间:2024-03-29 19:30:26浏览次数:19  
标签:count Thread t2 t1 安全 线程 原来 public

@TOC

一:什么是线程不安全??

先看一段代码:

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

在这里插入图片描述
上面的代码可能我们以为最终的count=100000;
但最终小于100000,这就是线程安全在搞鬼.
因为线程是随机调度 的,抢占式执行,就会使代码执行结果不可控,结果也就是随机的.
我们往往希望结果使我们所预期的,而这种不可控,随机的执行结果就称为bug.
多线程代码,引起了bug,这样的问题就是"线程安全问题",存在线程安全问题的代码,就称为"线程不安全".

二:为什么代码执行结果不是100000

count++操作其实是三个指令:
(1)把内存中的count中的数值,读到CPU寄存器中.(load)
(2)把寄存器中的值+1,(add).
(3)把寄存器上述计算后的值,写回到内存的count里.(save)
但因为不止一个线程,某个线程在执行指令的过程中,当它执行到任何一个指令的时候,由于随机调度,抢占式执行,就有可能被其他线程把它的CPU给抢占走.(操作系统把前一个线程调度走,后一个线程执行).
下面列举两个线程并发执行的时候,可能的执行指令的顺序,(不同的执行顺序,得到的结果可能存在差异)
在这里插入图片描述

在这里插入图片描述

我们知道count数据是存在内存中的,而同一个进程,不同的线程是共用一块内存资源的,也就是t1,t2线程操作的是同一块内存地址.
以下图为例:
在这里插入图片描述
1: t1线程 load操作拿到count的值,拿到了0,放到了A寄存器中;
2:t2线程 load 操作拿到count的值,拿到了0,放到了B寄存器中;
3:t1线程执行add操作,将A寄存器中的值+1,
4:t2线程执行add操作,将B寄存器中的值+1,
5:t1线程执行save 操作,将A寄存器中的值写回到内存中,内存中的值由0变为1.
6:t2线程执行save 操作,将B寄存器中的值写回到内存中,内存中的值由1变为1.

三:出现线程不安全的原因:

1:线程在系统中是随机调度,抢占式执行的,
2:当前代码中,多个线程同时修改同一个变量.

如果只有一个线程,那么修改操作没问题.
如果多个线程是读取操作,那么也没事.
如果多个线程修改不同的变量,也没事.
3:线程针对变量的修改操作不是"原子"的,而是三条指令.
但有的修改操作,是原子的,比如直接对变量进行赋值操作(CPU中就一个move指令).
4:内存可见性问题,引起的线程不安全
5:指令重排序,引起的线程不安全

四:解决线程不安全问题

线程不安全原因1:我们不能修改,因为硬件方面就是这样设计的(内核就是这样设计的 ).
原因2:不能修改,因为此时场景就是多线修改同一个变量.
原因3:可以通过"加锁"操作,将多个指令打包成一个整体.
通过"加锁",就达到了"互斥"的效果
互斥:t1线程在执行的时候,t2线程不能执行(阻塞等待),t2线程在执行的时候,t1线程不能执行(阻塞等待),互斥也称为锁竞争,锁冲突.

1:锁的操作:

(1)加锁:t1加上锁之后,t2也尝试加锁,就会阻塞等待(都是系统内核控制)(在Java中可以看到BLOCKED状态)
(2)解锁:直到t1解锁了之后,t2才有可能拿到锁(加锁成功).

2:编写代码

首先创建一个对象,使用这个对象作为锁:
在Java中可以使用任何对象作为加锁对象.
创建锁对象的意义:
锁对象的用途:有且只有一个,那就是用来区分,多个线程是否针对同一个对象(count)加锁,如果是,就会出现锁竞争/锁冲突/互斥,就会引起阻塞等待;如果不是,就不会出现锁竞争,也就不会阻塞等待.
通过设定不同的锁对象,来确定竞争关系.

public class Demo2 {
    public static int count=0;

    public static void main(String[] args) throws InterruptedException {
        //首先创建一个对象,使用这个对象作为锁
        Object locker = new Object();
        Thread t1=new Thread(()->{
            for (int i = 0; i < 5000; i++) {
              synchronized (locker){
                  count++;
              }

            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                synchronized (locker){
                    count++;
                }

            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = " +count);
    }
}

2.1 synchronized (){}

()里面写"锁对象"
{}:当进入代码块,就相当于对锁对象进行了加锁操作.
当出了代码块,就相当于对锁对象进行了解锁操作.

public class Demo2 {
    public static int count=0;

    public static void main(String[] args) throws InterruptedException {
        //首先创建一个对象,使用这个对象作为锁
        Object locker = new Object();
        Thread t1=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
              synchronized (locker){
                  count++;
              }

            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                synchronized (locker){
                    count++;
                }

            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = " +count);
    }
}

上述代码就相当于两个线程,针对同一个锁对象加锁,就会产生互斥.
t1,t2做的事情就是:判断循环条件,加锁,load ,add, save ,解锁,i++;
假设:由于是线程是并发执行的,这里假设t1线程先执行到了加锁操作,并且t1还没解锁,那么t2线程就不能获得锁对象,就不能进行加锁操作,
假设t1刚解锁,t2 就加锁了,但t1线程并不是什么代码也不执行,而是继续执行i++,循环判断条件,当t1执行到了synchronized,发现不能获得锁对象,那么t1线程只好阻塞等待了.
因此:在t1,t2两个线程中,每次count++是存在锁竞争的,会变成"串行"执行,但是执行for 循环中的条件以及i++仍然是并发执行的.

public class Demo2 {
    public static int count=0;

    public static void main(String[] args) throws InterruptedException {
        //首先创建一个对象,使用这个对象作为锁
        Object locker = new Object();
        Object locker2=new Object();
        Thread t1=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
              synchronized (locker){
                  count++;
              }

            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                synchronized (locker2){
                    count++;
                }

            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = " +count);
    }
}

上述代码就是两个线程针对不同对象加锁,就不会产生互斥,也就不会发生阻塞等待现象了.
操作系统中的加锁,解锁功能,核心还是CPU提供的指令(硬件提供了这样的能力,软件上才有对应的功能)

3:加锁的变种写法:

3.1:创建一个自定义类,并把这个类的引用作为加锁对象

class Count {
    public int count;
    public void add(){
        count++;
    }
    public int get(){
        return count;
    }
}
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Count count=new Count();
        Thread t1=new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
                synchronized (count){
                    count.add();
                }

            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
                synchronized (count){
                    count.add();
                }

            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count= " +count.get());

    }
}

3.2: synchronized (this){}

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

    }
    public int get(){
        return count;
    }

}
public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Count1 count1=new Count1();
        Thread t1=new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
                count1.add();
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
                count1.add();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = "+count1.get());
    }
}

3.3: synchronized public void add(){}

class Count1 {
    public int count;
    synchronized public void add(){
            count++;
    }
    public int get(){
        return count;
    }

}
public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Count1 count1=new Count1();
        Thread t1=new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
                count1.add();
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
                count1.add();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = "+count1.get());
    }
}

3.4:针对类对象加锁

class Count1 {
    public static int count;
   static void func(){
       synchronized (Count1.class){
           count++;
       }
   }
   public int get(){
       return count;
   }


public class Demo2 {

    }
    public static void main(String[] args) throws InterruptedException {
        Count1 count1=new Count1();
        Count1 count2=new Count1();
        Thread t1=new Thread(()->{
            for (int i = 0; i <50000 ; i++) {

            count1.func();
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i <50000 ; i++) {
           count2.func();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = "+count1.get());
    }
}

标签:count,Thread,t2,t1,安全,线程,原来,public
From: https://blog.csdn.net/2302_77978695/article/details/137120890

相关文章

  • KingbaseES行级安全策略
    什么是行级安全策略(RLS)?行安全策略就是对不同用户,不同行数据的可见性,和可修改性。是表权限的一种扩展。默认情况下表没有任何安全策略限制。这样用户根据自身对表持有的权限来操作表数据,对于查询或更新来说其中所有的行都是平等的。当在一个表上启用行安全性时,所有对该表的操......
  • URL编码:原理、应用与安全性
    在网络世界中,URL(统一资源定位符)是我们访问网页、发送请求的重要方式。然而,URL中包含的特殊字符、不安全字符以及保留字符可能会导致传输错误或安全风险。为了解决这些问题,URL编码应运而生。本文将从概念介绍、编码规则、编码与解码、常见应用场景、历史演变、安全性考虑、局......
  • 零基础自学网络安全的三个必经阶段(含学习路线图)
    一、为什么选择网络安全?这几年随着我国《国家网络空间安全战略》《网络安全法》《网络安全等级保护2.0》等一系列政策/法规/标准的持续落地,网络安全行业地位、薪资随之水涨船高。未来3-5年,是安全行业的黄金发展期,提前踏入行业,能享受行业发展红利。二、为什么说网络安全行......
  • JavaScript代码安全性提升:选择和使用JS混淆工具的指南
    引言在Web开发中,JavaScript是一种常用的脚本语言,然而,由于其代码容易被他人轻易获取和修改,为了保护JavaScript代码的安全性和版权,我们需要使用JS混淆工具。本文将介绍什么是JS混淆工具、为什么要使用以及如何选择合适的JS混淆工具,同时还会列举一些常用的JS混淆工具。 正文什......
  • TSINGSEE青犀智慧工厂视频汇聚与安全风险智能识别和预警方案
    在智慧工厂的建设中,智能视频监控方案扮演着至关重要的角色。它不仅能够实现全方位、无死角的监控,还能够通过人工智能技术,实现智能识别、预警和分析,为工厂的安全生产和高效运营提供有力保障。TSINGSEE青犀智慧工厂智能视频监控方案,主要是采用高清摄像头,通过智能分析技术,实现对工厂......
  • 夏季水域安全管理,AI智能识别算法防溺水视频监控方案
    随着夏季的到来,不少人为了一时的痛快凉爽就私自下水游泳,特别是在野外池塘,由于长期无人监管,极易发生人员溺亡事件,如何对池塘水域进行全天候无人值守智能监管,并实现发生人员闯入就立即告警?旭帆科技防溺水智能视频监管方案给出答案。1、视频监控布局在水域附近进行视频监控设备的......
  • 保障安全的散列算法 - SHA256
    引言SHA-256是由美国国家安全局(NSA)开发的SHA-2密码哈希函数之一,用于数字签名和区块链。在计算机科学和信息安全领域,SHA-256(安全哈希算法256位)是广受欢迎且常被使用的密码学散列函数。SHA-256产生一个唯一、定长的256位(32字节)散列值,不仅可以用于密码学中信息的安全......
  • 雷军之夜:小米汽车SU7发布会后的智能化探索与网络安全考量
    引言3月28日晚,小米集团创始人雷军在一场备受瞩目的发布会上,以其一贯的激情与诚意,揭开了小米汽车首款车型SU7的神秘面纱。这一夜,不仅是小米跨足汽车行业的重要里程碑,更是中国智能汽车产业向前迈进的新篇章。然而,在智能汽车不断颠覆传统出行方式的同时,围绕汽车智能化与网络安全......
  • 【计算机网络】http协议的原理与应用,https是如何保证安全传输的
    ✨✨欢迎大家来到景天科技苑✨✨......
  • cnc数据采集 ,机床数据采集,设备联网,多品牌多线程采集驱动,融合马扎克、西门子、海德汉、
    +cnccaiji采集驱动可以对不同品牌的机床信息进行管理(ip、端口、采集点位、车间、工厂、设备名称、编号)等,将采集到的数据通过mqtt或者http推送到指定地址,实现和业务系统的完全解耦。对于不了解cnc采集相关业务的公司来讲非常的友好。驱动支持系统 马扎克马扎克MAZAKCNC数据采......