请问,多个线程可以读一个变量,只有一个线程可以对这个变量进行写,到底要不要加锁?:要加锁!我们需要保证的是该变量在多线程环境下是线程安全的。典型的read
要加锁!我们需要保证的是该变量在多线程环境下是线程安全的。
典型的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关键字后,会强制操作后同步工作内存和主内存,保证其它线程立刻看到。
3Java中该变量用volatile约束,因Java每个线程都会将用到的变量copy到现成工作栈中,而修改了内存中变量的值,还需要将此值由线程工作栈写入到内存对应区域,因为这些数据读写操作要多个步骤完成,因此不具有原子性,故需要指定volatile关键字修饰变量,让线程对此变量的操作都直接操作内存,而不是有中间的对于线程栈的拷贝动作。
4如果仅仅是你描述的,那不需要加锁。
如果需要每次读取最新值,注意加volatile。
如果需要及时拿到最新值,注意使用线程通知手段。
5看你的需求了,如果你不怕丢数据,或者读到过期的数据,就不用加锁,如果你对数据的要求很严格的话,那还是要加锁的
6这跟多少个线程读有啥关系,就算是一个线程读,也是读写并发呀。至于要不要加锁,看业务是不是需要严格的数据一致性或者合法性。
7原则上要加锁。个别简单场景可以不加,比如可以确定读写不会冲突,或者单个简单控制变量考虑volatile。如果是大项目且性能优化指标不是纳秒级的,建议加上锁,免得埋坑。
8要看变量类型,如果是原子级的,哪就没问题,不用锁
9如果脏数据对结果无影响,当然可以不加锁。反之就加锁了。
10原则上不用,但是(敲重点):如果这个项目只是你一人的,并且你永远对它内部逻辑非常清楚,不会因为修改增加逻辑去多个线程写这个变量就不用加锁,反之这如果是个团队项目,并且项目逻辑扩展会越来越复杂,并且团队间项目交接交叉频繁,文档不清,新上手的人不清楚逻辑就会可能对这个变量扩展写逻辑,这种情况就要用锁,这是要解决项目维护和扩展问题,现在n多项目都存在多次交接和扩展后混乱问题。