前面说过,Java对象都有与之关联的一个内部锁和监视器
内部锁是一种排它锁,可以保障原子性、可见性、有序性
从Java语言层面上说,内部锁使用synchronized关键字实现
synchronized能够修饰方法,静态方法和实例方法均可以,也能够修饰一段代码({} 包裹)
synchronized修饰的方法被叫作同步方法
- 修饰的静态方法叫作同步静态方法
- 修饰的实例方法叫作同步实例方法
- synchronized修饰的代码块(或者一整个方法)就是曾经说过的临界区
synchronized关键字同步机制的使用,须要借助于锁对象
synchronized关键字修饰静态方法,锁对象隐含的是该类的class实例对象;修饰的实例方法隐含的是该对象自己(this)
对于同步代码段,则须要显式的指定锁对象
示例
注意:
对于锁对象,应该声明为final的
由于若是一旦锁对象发生了变化,那么极可能使用的将不是同一个锁对象,也就失去了同步的意义了,更甚一步,一般声明为private final
如上代码示例,借助于synchronized关键字,就能够实现原子性、可见性、有序性,因此对于该临界区内的代码,必然不会出现线程安全问题
可是这是一种排他锁,也就是对临界区的处理串行化,因此势必影响性能
锁泄漏
对于synchronized来讲,这是一种内部锁,对于锁的申请和释放,都是借助于底层实现的,换句话说你只须要使用synchronized关键字便可
底层JVM会帮助咱们实现锁的获取与锁的释放,即便出现问题,也会释放锁,因此synchronized的内部锁不存在锁泄露问题
对于锁泄漏,有时候多是同一个线程持续操做,因为锁的可重入性,因此并不会发现问题,可是对于高并发,这就极可能爆发出来问题了
调度
Java虚拟机会给每一个内部锁分配一个入口集 Entry Set,用于记录等待得到内部锁的线程
多个线程竞争时,只会有一个线程得到锁,其余线程获取失败,会进入BLOCKED等待状态,位于入口集的等待区中
锁释放后,会随机的唤醒一个线程,Java虚拟机内部对于内部锁是非公平的,也仅仅支持非公平调度,唤醒的线程可能会跟其余的线程竞争,因此他并不必定能够竞选成功,可能会被再次置入等待状态
这个过程跟前面介绍的监视器的过程是同样的
锁对象的确认
前面提到
synchronized修饰的同步实例方法,锁对象为当前对象自己this;静态方法锁对象为该类型对应的xxx.class对象实例;
这都是隐式的,如何确认?其实很简单
能够定义另外的方法显式的声明锁对象为该对象this或者xxx.class对象实例,对其中一个线程进行sleep,观察显式方法对锁的获取状况,就能够佐证这一结论。
若是是不一样的锁的话,将不会收到任何影响,若是是同一个锁就须要进行等待。
同步继承性
synchronized关键字修饰的方法能够进行同步,对于同步方法的继承性是什么样子的?
好比父类中
public synchronized void service();
子类中
@override
public void service();
对于子类中的方法调用,并不会具备同步的特性,因此,一个方法是否具备同步的特性,在于这个方法自己是否有synchronized修饰
同步代码块
synchronized便可以修饰方法,也能够修饰代码块
为何还要用同步代码块?直接加到方法上多省事儿?
synchronized同步保障了原子性、可见性、有序性,这个内部锁机制是排他的,换言之,至关于部分串行
串行天然能够解决多线程安全问题,若是整个项目所有都是synchronized的方法,那么确定不会有线程安全问题,可是为何不这么作?还不是由于性能问题,多核CPU放在那里,难道就只是摆设嘛
既然是至关于串行,很显然,串行化的代码越多,那么效率必然将会越低,因此但愿减小非必要的串行化,留给多核机器以及编译器CPU更多的优化空间
因此同步代码块顺势而出
同步代码块保障了更少的“串行化”代码,那么一个方法中,同步代码块以外的代码是如何进行的?是异步的!
进入同步代码块以前会多线程并发,可是一旦执行到同步代码块,将会串行
小结
对于synchronized关键字,从应用层面上来讲是很是简单的,就只有代码中的三种样式,可是底层的原理是很复杂的,涉及到JMM以及原子性、可见性、有序性的概念
因此想要学习synchronized,务必要理解这些概念
对于多线程编程来讲,synchronized更大程度上来讲,更至关因而一个语法糖,底层的机制所有被封装了,若是理解了底层的概念,语法糖的东西,就没什么理解难度
原子性、可见性、有序性是问题根源,JMM是问题解决方案,编译器、JVM底层负责实现,synchronized只是一个关键字而已,可是synchronized倒是彻底表明了底层的一切
为何说synchronized关键字修饰的方法(代码段)是线程安全的?那是由于底层的原子性、可见性、有序性的保障。
Java中任何一个对象都有与之关联的内部锁和监视器,因此任何的一个对象均可以用来做为锁对象
因此,借助于synchronized关键字和锁对象,进行合理的安排,你必定能够编写出来正确的并发程序(自身的安排组织不当怪不得synchronized)