volatile synchronized

volatile和synchronized到底啥区别?
1)volatile 与 synchronized 在处理哪些问题是相对等价的?
2)为什么说 volatile 是 synchronized 弱同步的方式?
3)volatile 除了可见性问题,还能解决什么问题?
4)二者我要如何选择使用?

都听过【天上一天,地下一年】,
假设 CPU 执行一条普通指令需要一天,那么 CPU 读写内存就得等待一年的时间。

受【木桶原理】的限制,在CPU眼里,程序的整体性能都被内存的办事效率拉低了,
为了解决这个短板,
硬件同学也使用了我们做软件常用的提速策略——使用缓存Cache

Java 内存模型(JMM)
CPU 增加了缓存均衡了与内存的速度差异,这一增加还是好几层。

此时内存的短板不再那么明显。但随之却带来很多问题

看上图,每个核都有自己的一级缓存(L1 Cache),
有的架构里面还有所有核共用的二级缓存(L2 Cache)。
使用缓存之后,当线程要访问共享变量时,如果 L1 中存在该共享变量,就不会再逐级访问直至主内存了。
所以,通过这种方式,就补上了访问内存慢的短板

具体来说,线程读/写共享变量的步骤是这样:
1.从主内存复制共享变量到自己的工作内存
2.在工作内存中对变量进行处理
3.处理完后,将变量值更新回主内存

假设现在主内存中有共享变量 X, 其初始值为 0
线程1先访问变量 X, 套用上面的步骤就是这样:
L1 和 L2 中都没有发现变量 X,直到在主内存中找到
拷贝变量 X 到 L1 和 L2 中
在 L1 中将 X 的值修改为1,并逐层写回到主内存中
此时,在线程 1 眼中,X 的值是这样的:

接下来,线程 2 同样按照上面的步骤访问变量 X

1.L1 中没有发现变量 X
2.L2 中发现了变量X
3.从L2中拷贝变量到L1中
4.在L1中将X 的值修改为2,并逐层写回到主内存中
此时,线程 2 眼中,X 的值是这样的:

结合刚刚的两次操作,当线程1再访问变量x,我们看看有什么问题

此刻,如果线程 1 再次将 x=1回写,就会覆盖线程2 x=2 的结果,
同样的共享变量,线程拿到的结果却不一样(线程1眼中x=1;线程2眼中x=2),
这就是共享变量内存不可见的问题

先来说说最熟悉的 synchronized 关键字
synchronized 关键字解决共享变量内存可见性问题
1)【进入】synchronized 块的内存语义是把在 synchronized 块内使用的变量从线程的工作内存中清除,
从主内存中读取
2)【退出】synchronized 块的内存语义事把在 synchronized 块内对共享变量的修改刷新到主内存中

volatile
当一个变量被声明为 volatile 时:
1)线程在【读取】共享变量时,会先清空本地内存变量值,再从主内存获取最新值
2)线程在【写入】共享变量时,不会把值缓存在寄存器或其他地方(就是刚刚说的所谓的「工作内存」),
而是会把值刷新回主内存

有种换汤不换药的感觉,你看的一点都没错

当使用 synchronized 或 volatile 后,多线程操作共享变量的步骤就变成了这样

简单点来说就是不再参考 L1 和 L2 中共享变量的值,而是直接访问主内存

public class ThreadSafeInteger {
    // 共享变量 value
    private int value;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

使用volatile改造:
public class ThreadSafeInteger {
    // 共享变量 value
    private volatile int value;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

使用synchronized改造:
public class ThreadSafeInteger {
    // 共享变量 value
    private int value;

    public synchronized int getValue() {
        return value;
    }

    public synchronized void setValue(int value) {
        this.value = value;
    }
}

这两个结果是完全相同,在解决【当前】共享变量数据可见性的问题上,二者算是等同的

@Slf4j
public class VisibilityIssue {

    private static final int TOTAL = 10000;
    // 即便像下面这样加了 volatile 关键字修饰不会解决问题,因为并没有解决原子性问题
    private volatile int count;

    public static void main(String[] args) {
        VisibilityIssue visibilityIssue = new VisibilityIssue();
        Thread thread1 = new Thread(() -> visibilityIssue.add10KCount());
        Thread thread2 = new Thread(() -> visibilityIssue.add10KCount());
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            log.error(e.getMessage());
        }
        log.info("count 值为:{}", visibilityIssue.count);
    }

    private void add10KCount() {
        int start = 0;
        while (start++ < TOTAL) {
            this.count++;
        }
    }
}
运行代码,你会发现,count的值始终是处于1w和2w之间的

在方法add10KCount上添加synchronized  count 结果就是 2w

通过 volatile 和 synchronized 关键字以同样形式修饰,怎么有的可以带来相同结果,有的却不能呢?
二者的不同:
count++ 程序代码是一行,但是翻译成 CPU 指令确是三行
synchronized 是独占锁/排他锁(就是有你没我的意思),
同时只能有一个线程调用 add10KCount 方法,
其他调用线程会被阻塞。
所以三行 CPU 指令都是同一个线程执行完之后别的线程才能继续执行,
这就是通常说说的 原子性 (线程执行多条指令不被中断)

volatile 是非阻塞算法(也就是不排他),
当遇到三行 CPU 指令自然就不能保证别的线程不插足了,
这就是通常所说的,volatile 能保证内存可见性,但是不能保证原子性

什么时候才能用volatile关键字呢?
如果写入变量值不依赖变量当前值,那么就可以用 volatile
如果写入变量值不依赖变量当前值,那么就可以用 volatile
如果写入变量值不依赖变量当前值,那么就可以用 volatile

上面 count++ ,是获取-计算-写入三步操作,也就是依赖当前值的,所以不能靠volatile 解决问题

synchronized 是排他的,线程排队就要有切换,这个切换就好比上面的例子,要完成切换,
还得记准线程上一次的操作,很累CPU大脑,这就是通常说的上下文切换会带来很大开销

volatile 就不一样了,它是非阻塞的方式,所以在解决共享变量可见性问题的时候,
volatile 就是 synchronized 的弱同步体现了


参考:https://www.toutiao.com/i6801088222123262477/?tt_from=weixin&utm_campaign=client_share&wxshare_count=1&timestamp=1615033196&app=news_article&utm_source=weixin&utm_medium=toutiao_ios&use_new_style=1&req_id=202103062019560102120810880E51A5A5&share_token=62E1F3A6-12DD-410A-9902-0E33005788F4&group_id=6801088222123262477 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值