文章目录
菜鸟的学习笔记,欢迎大神指导批评。
1.问题的引入
假设两个人同时对银行的一个账户进行存入1元钱操作,若银行未进行一定的保障,则有可能造成两个人同时存入一元钱,而这个账户仅仅会添加一元钱,而将会丢失一元钱的情况,那么如何保证这种情况不被发生呢?
2.问题发生的原因
由于两人同时存入前,计算机读取数据时,并不会判断在读取数据后是否会发成改变,并且都将相应读到的数据存到计算机内,而仅对当前读到的数据进行保存,而忽略了多个人同时操作时发生的与时间有关的问题,故会造成读到脏数据的情况。(详见操作系统,多线程相关原理的解释)
3.解决的方法
3.1.解决方法的理论
3.1.1总线加锁
总线加锁即当操作共享数据时,将会为访问的数据进行全部加锁的方式,这种方式虽然能解决相关程序的运行问题,但是,同样也会造成系统的运行效率低下等问题。
3.1.2.使用cpu高速缓存一致性协议(Intel 提出)
思想:cpu写入数据时,发现被共享时,会通知其他拥有副本的cpu需要立即更新缓存,即之前的信息失效,其他的cpu在收到信号后,会重新到相应内存区重新获取相应数据,这也就解决多个线程访问时数据不一致的问题。而Java在使用volatile关键字时,即使用了这个设计的思想。
3.2.Java中的多线程的三大原则
3.2.1.原子性(atomic)
一个操作要么都成功,要么都失败,不能因为任何原因被中断。
3.2.2.有序性(order)
相应的操作需要按顺序执行(重排序为只要求结果一致性(重排序能优化单线程的执行效率),但在多线程中,会产生相关的错误)
Java默认提供的有序性(happens-before)
-
代码执行时顺序为写在前面的代码先发生,写在后面的后发生。
-
解锁操作必须在锁之后。
-
多线程时,volatile修饰的关键字先执行写操作,后执行读操作。
-
如果A操作先于B操作,B操作先于C操作,那么A操作必定先于C操作。
-
在线程启动时,线程的创建必定优先于线程的执行。
-
在线程中断时,线程的中断操作(interrupt)必须发生在捕获异常发生之前。
-
对象的初始化,必须发生在销毁之前。
-
线程的所有操作必须发生在线程的销毁之前。
3.3.3.可见性(visable)
任何时间看到的数据应为最新的数据,而并非为旧的数据(防止操作系统的与时间有关的错误)
3.3.Java中的解决方法(使用volatile)
在Java中,未了解决操作系统中的与时间有关的错误的问题时,为了简单起见,通常会使用volatile关键字,但本关键字也有一定的使用限制,具体如下:
3.3.1.使用volatile关键字的优点和缺点
- 一旦此变量被关键字修饰,则虚拟机则保证了执行的可见性(V)和有序性(O),但并不能保证这个变量的原子性(A)。
- 保证重排序时,指令的执行顺序不被修改
- 强制使缓存的数据立刻写入主存
- 若对关键字修饰的变量进行写操作,则会是其他cpu的缓存的数据在写入修改后立即失效。
3.3.2.volatile关键字的使用场景
- 需要进行状态量的标记时,可使用本关键字。
- 需要保证关键字修饰的语句的执行的先后顺序不发送改变,即运行一致性时,可使用本关键字。