(1)一个人只要自己不放弃自己,整个世界也不会放弃你.
(2)天生我才必有大用
(3)不能忍受学习之苦就一定要忍受生活之苦,这是多么痛苦而深刻的领悟.
(4)做难事必有所得
(5)精神乃真正的刀锋
(6)战胜对手有两次,第一次在内心中.
(7)好好活就是做有意义的事情.
(8)亡羊补牢,为时未晚
(9)科技领域,没有捷径与投机取巧。
(10)有实力,一年365天都是应聘的旺季,没实力,天天都是应聘的淡季。
(11)基础不牢,地动天摇
(12)写博客初心:成长自己,辅助他人。当某一天离开人世,希望博客中的思想还能帮人指引方向.
(13)编写实属不易,若喜欢或者对你有帮助记得点赞+关注或者收藏哦~
synchronized关键字辨析
文章目录
1.synchronized的实现原理
1.1使用monitorenter和monitorexit指令实现的
(1)monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束和异常处。
(2)每个monitorenter必须有对应的monitorexit与之配对。
(3)任何对象都有一个monitor与之关联
1.2非线程安全与线程安全案例
(1)加锁解决线程安全问题
- 方法上加锁
- 代码块加锁
/**
* 类说明:简单的程序会有线程安全问题吗?
*/
public class SimplOper {
//计数器
private volatile long count = 0;
/**
* 非线程安全方法
*/
public void incCount1() {
count = count + 1;//count++;
}
/**
* 线程安全方法
*/
public synchronized void incCount2() {
count = count + 1;//count++;
}
/**
* 线程安全代码块
*/
public void incCount3() {
synchronized (this) {
count = count + 1;//count++;
}
}
//进行累加的线程
private static class Count extends Thread {
private SimplOper simplOper;
public Count(SimplOper simplOper) {
this.simplOper = simplOper;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
//simplOper.incCount1();//加10000
//simplOper.incCount2();//加10000
simplOper.incCount3();//加10000
}
}
}
public static void main(String[] args) throws InterruptedException {
SimplOper simplOper = new SimplOper();
//启动两个线程
Count count1 = new Count(simplOper);
Count count2 = new Count(simplOper);
count1.start();
count2.start();
Thread.sleep(50);
System.out.println(simplOper.count);//=20000?
}
}
(2)java文件在编译为class文件的过程中,会由编译器在class文件中插入monitorenter与monitorexit字节码指令,这两个指令即是synchronized关键字在底层的实现。
- 执行到monitorenter指令,表示要去获取锁,monitorenter在虚拟机里面它其实是一个对象与其相对应的,即Monitor对象。拿锁就是拿这个Monitor对象的所有权,哪个线程拿到了这个对象的所有权,就表示它进入了这个锁,执行相关业务逻辑。
(3)在同步代码块上加synchronized锁
(4)在方法上面加synchronized锁
- flags中会加上ACC_SYNCHRONIZED标记,表示是一个同步方法,它的底层实现还是通过monitorenter与monitorexit指令实现的。
1.3锁的存放位置
(1)锁放在Java的对象头里面。
(2)当在Java代码里面,new Object();或者是new User();每一个对象在内存里面都要存放数据,除了这些数据之外,还有对象头。
(3)对象头里面包含当前对象一些比较关键的信息,比如GC年龄,hashcode码,MarkWord
(4)对象头里面还包含对象属于哪一个类,这个类上面有哪些相关的属性,由对象头的另一个类进行保存,虚拟机要知道这个对象属于哪个类的时候,通过对象头区域就可以找到这个对象属于哪个类,即属于哪个类的实例,即类型指针ClassPoint,用ClassPoint标明这个对象是哪个类。
(5)对象头包含两个部分
- MarkWord:对象
- ClassPoint:对象指针
对象头会随着程序的运行,发生一些变化
2.为了提升synchronized的性能做的相关优化
(1)在任一时刻,只有一个线程可以拿到锁,其他线程要被阻塞住,意味这个线程会被操作系统挂起,一旦挂起会发生两次上下文切换。
(2)发生上下文操作是比较昂贵的操作,因为CPU需要花5000-20000个指令周期来执行上下文的切换操作。
2.1轻量级锁
(1)通过CAS操作来加锁和解锁
(2)在轻量级锁的概念中出现了自旋锁的概念。就是在轻量级锁里面,推测可能前一个拿到锁的线程释放锁的过程可能很快,没有必要去进行线程上下文切换的操作,在解锁与拿锁操作上面通过自旋,使用CAS操作加锁与解锁,尝试一下拿锁,拿不到就再尝试多次拿锁,由此来避免加锁解锁过程中的上下文切换。
(3)CAS大部分情况下是处在空转的情况,是消耗CPU的,不停的自旋检测CPU是有消耗的,(但是比上下文切换的消耗要小得多)。
(4)如果加锁的操作是一个很重的操作,比如要等待第三方接口的调用,比如要访问后台服务器,拿到数据,这个时候自旋就很消耗CPU,会导致手机发热。
2.2适应性自旋锁
(1)太多的线程去自旋,会占用CPU资源,自旋的次数一定要做控制,早期JDK中定义为10次,JDK1.6以后,自旋的次数不再是一个固定的值,而是由虚拟机自行进行判断,即由虚拟机动态调整。
(2)现在自旋的时间大概是一个线程上下文切换所需要的时间。
(3)引入轻量级锁最终的目的是避免引入重量级锁时产生上下文切换。如果自旋的时间已经超过了上下文切换的时间,那么使用适应性自旋锁取代重量级锁的操作已经没有必要了。
2.3偏向锁
(1)总是同一个线程获得同一把锁。
(2)轻量级锁里面,不管是什么情况,会将CAS的操作也去掉,测试一下拥有这把锁的是不是当前线程自己,是自己,就直接拿来用,这种锁就称为偏向锁。
(3)偏向锁就是锁的获得者总是偏向于第一个拿到这把锁的线程。
(4)偏向锁的好处就是减少CAS操作来加锁与解锁,只做一次CAS操作,后续不再做CAS操作。
大多数情况下,锁不仅不存在于多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低由此引入了偏向锁。无竞争时不需要进行CAS操作来加锁和解锁。
(1)加锁可能存在多线程竞争的情况,此时一旦出现两个线程或者多个线程,如果线程处在偏向锁的状态,但是第二个线程出现了,它也需要拿这把锁,此时偏向锁开始撤销,开始进行锁的升级,升级为轻量级锁。
(2)将偏向锁撤销,替换成轻量级锁的数据,会出现Stop the World,STW现象,停止世界,因此偏向锁的撤销工作是一个比较消耗资源的工作。
2.4synchronized在实现的时候,JDK所做的优化有哪些?
(1)引入了偏向锁的机制
(2)引入了轻量级锁的机制
(3)锁会根据竞争的不同情况,一点点由偏向锁膨胀为轻量级锁,再由轻量级锁膨胀为重量级锁。
(1)偏向锁适应的场景是当前线程没有竞争,而且总是由同一个线程获得同一个锁的时候,synchronized关键字会走偏向锁模式。
(2)一旦发生了竞争的情况,偏向锁就会撤销,晋升为轻量级锁,轻量级锁在获取锁的时候,它使用了一个自旋的CAS操作,CAS操作是比较耗费CPU资源的。自旋的次数太多,反而会导致性能下降,于是轻量级锁就会膨胀为重量级锁。
(3)引入阻塞状态,将线程阻塞起来,将CPU释放出来做更多有效的事情,这是synchronized关键字实现的一个例子。
(4)回答synchronized关键做了哪些优化的时候,要说到3点,偏向锁、轻量级锁、自适应的自旋锁。
(5)synchronized在实现的时候都是基于monitorenter和monitorexit指令实现的。进入同步块就意味着执行monitorenter指令,拿到Monitor对象的所有权。
(6)早期使用的是阻塞的方式实现,对于一些轻量级的类似于同步块的操作,这种方式比较重,所以说,synchronized又做了一些相关的优化。
(7)在原有的重量级的基础之上,引入了偏向锁和轻量级锁这两个状态,如果当前synchronized加锁的情况下面,偏向锁对象没有竞争,同一个线程获取锁的时候,进入偏向锁。
(8)一旦产生了竞争, 升级膨胀为轻量级锁,如果轻量级锁的自旋超过一定的次数,又会膨胀为一个重量级锁。
(9)一旦引入了加锁,如果底层实现没有进行优化,就会很重,于是Java开发者就想到了提升虚拟机synchronized的性能,于是底层实现就引入了偏向锁、轻量级锁来避免重量级加锁情况下无谓的上下文切换。
2.5synchronized锁与Lock显示锁应该用哪个好?
如果没有特殊的需求,即业务里面要求可中断拿锁,要尝试拿锁,尽可能的使用synchronized关键字,尽量少用显示锁Lock,除了完成业务有特殊的需求再应用显示锁Lock.
3.打赏鼓励
感谢您的细心阅读,您的鼓励是我写作的不竭动力!!!