线程安全与锁优化
线程安全定义
当一个对象被多线程调用的时候,不用在乎线程之间的交替执行,也不需要额外的同步操作,同时也不需要调用方协调操作,得到的结果都是正确的,那么可以说这个对象是线程安全的。
java语言中的线程安全
不可变
被final 修饰的字段,如果没有初始值,那么必须在构造器阶段对他进行赋值。且之后的程序运行将无法改变final字段值,属于线程安全。
例如:java.lang.Integer 里面的value字段
绝对线程安全
绝对的线程安全,是那种 任何方法都需要同步操作封装好,调用者不需要任何的同步操作。
相对线程安全
类似Vector、HashTable。绝大多数是线程安全的,但是在有些情况下会表现出线程不安全。
例如:两个线程,一个线程在对Vector进行删除操作,另一个线程对Vector进行循环查询操作。
这里很有可能会抛出异常,因为查询操作可能会反问到无效的下标。
线程兼容
指对象本身并不安全,但是可以通过同步操作使其线程安全。例如HashTable。
线程独立
不管是否采取了同步措施,都不能在并发的情况下使用。
同步实现的方
1.使用synchronized关键字,被包围的代码段,编译后,会在代码段前后加上monitorenter、monitorexit 指令。
2.使用juc包内的ReentrantLock,显示加锁,并在finally里面添加解锁。
ReentrantLock的好处:
1.等待可中断,(长时间的等待,会考虑直接中断去干别的事情。)
2.公平锁:可以按各线程申请锁的时间来分配锁。
3.可以指定多个条件。
悲观锁和乐观锁
悲观锁就是如synchronized关键字,如果没有申请的到锁就会进入阻塞状态将线程挂起。
乐观锁会使用CAS(compare and swap),会得到现有值,预期旧值,预期新值。如果预期旧值和现有值不想等则放弃本次设置。循环设置,直到成功。
CAS (仅仅是一个指令)需要硬件指令的支持,在UnSafe 类里面可以找到 cas的方法。
锁优化
自旋锁与自适应自旋
为什么需要自旋锁?在没有自旋锁的时候,线程没有申请到锁,会直接阻塞,等待唤醒的信号。
但是这唤醒的过程可能涉及到了操作系统内核态的操作。对性能的损耗非常大。因此引入了自选锁的概念,就是没有申请到锁,会进行空自旋。这种自旋锁适合短时间的自旋,如果是长时间的自选会对资源造成不必要的浪费。
之后演变成了自适应自旋,他会根据之前获取锁的时间和锁的拥有者的状态来判断。如果都是短时间就获取了锁,则下一次也是会进行自旋,如果上几次监控的结果是需要较长时间才能获取锁,则会直接进行挂起,不浪费资源。
锁消除
会对没有必要的锁进行消除。
锁粗化
如果检测到有很多一小段是获取同一个锁的,就会将锁粗化到一个锁包含这些一小段的代码。
轻量级锁
轻量级锁本意是针对没有过多线程竞争的情况下,刚开始获取锁会使用CAS的方式进行加锁,如果成功了则会设置 mark word 里面的锁标志为00,反之则会设置为10,mark word中记录的就是重量级锁的地址。解锁同样是根据cas方式来解锁,如果解锁成功就成功了,如果失败则证明有其他线程在竞争锁,则会在解锁的同时,唤醒被挂起的线程。
偏向锁
偏向锁则是期望无竞争发生,没有发生竞争则不会进行锁设置,很大可能优化性能。
如果遇到锁,则会进行膨胀,轻量级再到重量级。