人文艺术 > 请问,多个线程可以读一个变量,只有一个线程可以对这个变量进行

请问,多个线程可以读一个变量,只有一个线程可以对这个变量进行

2020-07-21 07:23阅读(77)

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

多线程的神奇之处就在于你永远不知道哪个线程的代码先得到执行,因此多个线程的共享变量就必须要加锁操作,原因如下:

所谓变量,就是可以被修改的,如果一个线程正在读取,而另一个线程正在对其进行修改,那么读取的哪个线程就遭殃了,因为它对读取的值不可预期,如果是一段发射火箭的代码,那么到底是该先启动倒计时还是先启动点火?如果没有加锁控制预期,那么火箭的架子还没撤开,火就被点了。

5

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

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

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

6

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

7

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

8

读写需要多条指令时需要加锁。

读未完成期间不能写,写未完成期间不能读。

9

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

10

多读一写,我赞同峰子777的做法,原因:从可见性来说,用volatile修饰能保证其他线程读到的是最新值