教育培训 > 请问,多个线程可以读一个变量,只有一个线程可以对这个变量进行写,到底要不要加锁?

请问,多个线程可以读一个变量,只有一个线程可以对这个变量进行写,到底要不要加锁?

2020-11-17 01:04阅读(59)

请问,多个线程可以读一个变量,只有一个线程可以对这个变量进行写,到底要不要加锁?:要加锁!我们需要保证的是该变量在多线程环境下是线程安全的。典型的read

1

要加锁!我们需要保证的是该变量在多线程环境下是线程安全的。

典型的read write lock场景,Java中比较经典的ReentrantReadWriteLock。

另外多说一句,当你不确定要不要加锁的时候,就是你需要加锁的时候。

然后你参考MySQL数据库下Innodb存储引擎默认级别是RR,防止脏读,即当我们update一条数据的时候,是加了行锁的,其他的读线程在等待着,所以你需不需要加锁?

不加锁会出现什么问题?

显然会出现脏读的问题!

这也是面试中常见的一种问题,以该题为突破口,带你进入多线程的世界。

其实加锁也很简单,Java中对该变量进行volatile修饰即可!

不了解volatile的同学需要学习了,本质上是为了保证变量操作时候的内存可见性

那怎么保证内存可见性呢?禁止指令重排序、内存屏障。

重点看下图hotspot虚拟机的实现:

当我们对一个变量i进行volatile修饰后,Java在字节码层面是对这个变量进行了ACC_COLATILE标记。当我们的JVM虚拟机看见ACC_COLATILE这个字节码指令后就会对这个变量加一个lock指令(cpu层面的),还是一个的概念。


有的人说考虑程序的吞吐量,在保证最终一致性的前提下可以不加锁,那我的理解就是你不是在搞事情就是在埋坑,埋一个还不容易被发现的坑。这种坑测试测不出来,线上出问题了还复现不出来,所以为了安全还是加锁吧~

2

先说结论:不必要

  • 如果不需要可见性,什么都不需要加
  • 如果需要保证可见性,则需要加volatile关键字。这里可以加锁,但是没必要,对性能有影响

下面简单解释下原因:

加锁是因为操作不是原子性的,以i++这个操作来解释,看下面两张图。

i++这个操作需要

  • 先将i的值从内存中读出来
  • 然后加1
  • 最后写回去

看上面第二张图,能很清楚的理解流程吧?

加锁就是保证上面的三步是一个原子操作。

回到问题,这里只有一个线程写,实际没有竞争,所以没必要加锁。

但是,看第一张图,因为有主内存和本地内存的存在

  • 线程先写入本地内存
  • 然后刷入主内存
  • 其它内存同步主内存到工作内存
  • 然后从工作内存中读取

一个线程写入后,不能保证其它线程立即看到,这就是可见性问题。

加了volatile关键字后,会强制操作后同步工作内存和主内存,保证其它线程立刻看到。

3

Java中该变量用volatile约束,因Java每个线程都会将用到的变量copy到现成工作栈中,而修改了内存中变量的值,还需要将此值由线程工作栈写入到内存对应区域,因为这些数据读写操作要多个步骤完成,因此不具有原子性,故需要指定volatile关键字修饰变量,让线程对此变量的操作都直接操作内存,而不是有中间的对于线程栈的拷贝动作。

4

如果仅仅是你描述的,那不需要加锁。

如果需要每次读取最新值,注意加volatile。

如果需要及时拿到最新值,注意使用线程通知手段。

5

看你的需求了,如果你不怕丢数据,或者读到过期的数据,就不用加锁,如果你对数据的要求很严格的话,那还是要加锁的

6

这跟多少个线程读有啥关系,就算是一个线程读,也是读写并发呀。至于要不要加锁,看业务是不是需要严格的数据一致性或者合法性。

7

原则上要加锁。个别简单场景可以不加,比如可以确定读写不会冲突,或者单个简单控制变量考虑volatile。如果是大项目且性能优化指标不是纳秒级的,建议加上锁,免得埋坑。

8

要看变量类型,如果是原子级的,哪就没问题,不用锁

9

如果脏数据对结果无影响,当然可以不加锁。反之就加锁了。

10

原则上不用,但是(敲重点):如果这个项目只是你一人的,并且你永远对它内部逻辑非常清楚,不会因为修改增加逻辑去多个线程写这个变量就不用加锁,反之这如果是个团队项目,并且项目逻辑扩展会越来越复杂,并且团队间项目交接交叉频繁,文档不清,新上手的人不清楚逻辑就会可能对这个变量扩展写逻辑,这种情况就要用锁,这是要解决项目维护和扩展问题,现在n多项目都存在多次交接和扩展后混乱问题。

相关问答推荐