线程安全是我们追求的目标,其核心在于对共享和可变状态的访问和操作进行管理和控制,即原子性和可见性的实现。JMM保证内存可见性,实现线程间的隐式通信,而原子性通常由锁来实现。synchronized是常见的重量级锁,而关于锁已存在不少的优化。
一、同步的必要性####
- 多线程安全
当多个线程访问某个类时,该类可以表现出正确的行为,这个类就是线程安全的。其关键在于对状态访问操作的管理,尤其是共享的和可变的。 - 同步的内容
方法内部的变量,由线程所私有,存在于Java虚拟机栈中的局部变量表中,栈帧的入栈和出栈对应于方法的调用和执行,不存在线程共享的问题;
多个对象同样不存在线程安全问题,因为Java对象分配存在Java堆中,具有不同的内存空间;
发生多线程安全问题的是类的实例变量;
二、synchronized关键字####
- synchronized方法与synchronized(this)
synchronized方法与synchronized(this)拥有的是相同的对象锁;
A线程可以同步调用synchronized方法或者synchronized(this),B线程可以异步调用非此对象锁锁住的方法和代码块; - synchronized(Obeject)
多个线程同时执行synchronized(o)代码块时呈现同步效果;
多个线程同时执行o对象中的synchrinized方法呈现同步效果;
多个线程同时执行o对象方法里面的synchronized(this)呈现同步效果;
三、有关的小内容####
- 可重入锁
线程获得对象锁后,可以再次请求获得对象锁; - 可重入锁的继承性
子类可以通过"可重入锁"(子类的同步方法或者同步this)调用父类的同步方法; - 同步的非继承性
子类的非同步重写方法不能继承父类的同步性; - 锁与异常的关系
一但出现异常,所持有的锁会自动释放; - 对象锁与class锁
synchronized修饰静态方法,则是给其类加锁;synchronized修饰非静态方法,是给其类的对象加锁; - 锁对象的改变
只要对象不变,即使对象的属性不变,运行结果依然是同步的。因为String常量池的存在,String通常并不适合作为锁对象;
四、原理解释####
- 具有原子性,间接实现内存可见性
synchronized在获得锁对象后,优先将主内存中的最新值刷新到工作内存中;synchronized在释放锁对象前,优先将工作内存中的最新值刷新到主内存中;(工作内存是JMM抽象出的概念,实则为缓存、缓冲区、寄存器和硬件优化的综合结果)
五、锁优化####
-
自旋锁
抢夺锁失败后,不是直接进入阻塞状态,而是占用CPU时间,执行一段毫无意义的循环,称为自旋,等待锁释放。自旋的时间必然存在一个限度,不然造成性能的损失; -
适应性自旋锁
相比于自旋锁,适应性自旋锁的自旋时间,由在同一个锁对象的上一个自旋锁的自旋时间和锁对象状态共同决定。一般来说,上一个自旋锁成功,则虚拟机会自动增加下一个自旋锁的时间,成功的可能性会提高,反之亦然; -
锁消除
锁消除依据的逃逸分析的数据支持,若JVM检测到无数据共享发生,则会取消同步锁。通常在于使用Java内置的API时,如StringBuffer,从其源码可以看出并不存在变量溢出情况,将其内部锁进行消除; -
锁粗化
将多个连续的加锁和解锁操作,扩展为一个更大范围的锁,减少性能损失,如同一个对象的多次add方法,可以扩展为更大范围的锁; -
轻量级锁和偏向锁
轻量级锁在无竞争的条件下使用CAS操作去消除同步使用的互斥量;
偏向锁在无竞争的条件下消除整个同步,即对于第一个获取锁的线程,在接下来环节中锁未释放,则无需再进行同步操作;具体参见锁优化
网友评论