目录
1. 学习总结
- 1、sychronized
产生《并发问题》的三个根源在于“缓存可见性问题”,“原子性问题”,“指令顺序性问题”,java并发包里的《并发工具》,也都是围绕着解决这3个问题的解决方案。
解决并发问题常用的就是使用锁 synchronized ,他同时解决了上述3个问题。理论上来说关键字synchronized可以解决Java里所有的并发问题。
理清锁和共享资源之间的关系
加锁的对象是什么、锁和对象的关系
加锁的对象和资源的关系
多把锁保护同一个共享资源,会出现并发问题。一把锁保护多个资源、确实并发安全的,如下代码,属于多把锁保护同一共享资源,是不安全的(上面是对象this锁,下面是Test.class锁)
public class Test {
static Integer value;
//线程1执行此方法
public synchronized void thisLock(){
value++;
}
//线程2执行此方法
public static synchronized void classLock(){
value++;
}
}
- 2、synchronized的实现原理
为了降低使用synchronized的学习成本和出错成本,java就把synchronized的加锁和解锁操作都内置了。查看指令码时,使用sychronized的代码前后,会多出一对monitor指令。
synchronized 其实就是维护了一个monitor,然后monitorenter,monitorexit会进行加锁和解锁的操作。
wait():当前线程阻塞,并释放锁。
notify():唤醒等待队列的一个线程。
notifyAll():唤醒等待队列的所有线程。
- 3、sychronized的锁是如何存储的
锁的标记是保存在对象信息里的(即对象头中)
锁分为 轻量级锁、重量级锁、偏向锁
偏向锁:对象头存储线程ID、检查是否为当前线程,CAS替换为本线程,当有其他线程竞争时,就会升级为轻量级锁
轻量级锁,有线程来参与锁的竞争,但是锁竞争的时间很短:当发现有锁冲突,线程首先会使用“自旋(循环)”的方式循环在这里获取锁,因为使用自旋的方式非常消耗CPU,当一定时间内通过自旋的方式无法获取到锁的话,那么锁就开始升级为重量级锁了。
重量级锁:我们知道当获取锁冲突多,时间越长的时候,我们的线程肯定不能一直一致在这里循环的询问,这种方式太消耗CPU资源,而且也是没意义的。所以这个时候最好的方式就是先让线程进入队列等待,然后等前面获取锁的线程释放了锁之后再开启下一轮的锁竞争,而这种形式就是我们的重量级锁,只有当锁升级为重量级锁的时候才真正意义上的在操作系统层面进行了加锁操作。
- 4、synchronized设计模型(管程模型)
线程同步:采用什么方式获得锁、采用什么方式释放锁并通知其他线程。管程是通过管理共享变量访问过程,保证共享变量线程安全的一种机制, 这种机制是通过线程之间访问共享资源的互斥特性来达到线程安全的目的,保持互斥的方式也就是我们常说的“锁”,因为管程里线程互斥特性,并发时候同一时间能加锁成功操作共享资源,那么获取锁的这些线程是采用什么等待机制,加锁成功的线程结束后使用什么方式通知其它线程,这就是线程之间的协作,我们称为线程“同步”。
- 5、sychronized加锁的2中方式
显示加锁
隐式加锁
(1)隐式加锁,synchronized修饰对象方法,效果等于synchronized(this),this也就是Test的当前实例对象。
public class Test {
//修饰在对象方法上,隐式加锁,效果等于synchronized(this)
public synchronized void thisLock(){
}
}
(2)隐式加锁,synchronized修饰静态类方法,效果等于synchronized(Test.class)。这里需要注意Test.class是在JVM虚拟机真实存在的一个对象,JVM代码在经过编译、加载之后就会在堆里面创建一个 Test.class的对象,所以网上说锁住了Test.class对象就相当于锁住了Test的实例对象,这种说法是错误的,因为他们两个没有关系,不是一回事。
public class Test {
//修饰在类方法上,隐式加锁,效果等于synchronized(Test.class)
public static synchronized void classLock(){
}
}
3、显示加锁,在synchronized修饰代码块时会需要显示的指定一个加锁的对象,synchronized(任何对象)。
public class Test {
public void codeLock(){
//显示加锁,锁的是当前Test实例对象
synchronized(this){
//Todo
}
}
Object objA=new Object();
public void objLock(){
//显示加锁,锁的是objA对象
synchronized(objA){
//Todo
}
}
}
2. 总结
(1)sychronized解决了哪些问题:3个并发问题(可见性、原子性、)
(2)sychronized的2中加锁方式(显示和隐式加锁)
(3)sychronized加锁的对象是什么(Test.class、实例对象this、普通对象object实例)
(4)加锁的对象和共享资源的关系(多个锁保护同一共享变量会出现并发问题、一个锁保护多个资源是安全的)
(5)sychronized原理(指令码中在加锁前后加上了monitorenter和monitorexit)
(6)sychronized是如何存储锁的(存储在对象头中,分为偏向锁->轻量级锁->重量级锁)
(7)管程模式
管程模/sychronized运行原理图