synchronized关键字的相关技术点
synchronized的简介
能够保证多个线程条件下,在同一时刻只有一个线程能够执行该段代码。以达到并发安全的效果。比如常见的i++操作,如果不加锁的话可能会出现数据不一致。
synchronized的使用
Synchronized锁分为两类,一个是类锁,一个是对象锁。
synchronized的使用大概是上面的几种。
我们先从对象锁说起
对象锁,锁住的其实是实例化的对象,也就是我们new出来的实例。使用参考以下方法
上面两种锁都是锁住的是当前对象(this)。如何理解以下代码?
第1个线程和第2个线程存在锁竞争。而第1个和第3个以及第2个和第3个不存在锁竞争,即锁是无效的。
为什么呢?
第1个线程和第2个线程中的锁是SynchronizedTest1,而第三个线程的锁是SynchronizedTest2,所以第1个和第3个以及第2个和第3个不存在锁竞争。
总之一定要看清对象锁,锁住的对象是什么,如果多个线程使用的是“一把锁”那么就会存在锁竞争,如果使用的是“不同的锁”,那么锁会无效的
实例化对象不同,里面的锁也会不同,那么如何保证所有实例化的对象都拥有相同的锁?-----只能从类对象加锁
接着说类锁
类锁有两种添加方式,一种是class锁,一种是添加到静态方法中
加上类锁以后,所有实例化对象都拥有一把相同的锁。
关于锁,是否还有以下的疑惑
synchronized是“锁”?还是关键字?它到底作用在哪里?
锁是怎么作用在对象上的或者类上的?
synchronized锁到底“锁”住了什么?锁是什么?
synchronized是“锁”?还是关键字?它到底作用在哪里?
synchronized 仅仅是一个关键字,关键字的作用是给对象/类加锁,所以锁是加在对象上或者类上。
锁是怎么作用在对象上的或者类上的?
- 如何作用在对象上?
- 对象头的底层描述(未写)
对象由三部分组成:对象头、实例化数据、对齐填充。
以下是对象头中的markword
从这个图可以看出,可以通过修改对象头的标志位,让对象变成一个锁。
所以synchronized修饰对象锁的时候,其实就是修改对象的对象头的markword里的重量级锁的标志位,使得将对象标记为一个重量级锁。
2. 如何作用在类上的?
类锁是加在类上的,而类的信息保存在jvm的方法区,并且整个jvm只有一份,方法区可以被所有的线程所共享。所以在类的信息上加入锁的标示,当多线程去访问每一个该类的实例化对象时,都会存在锁竞争,进而保证并发安全。
synchronized锁到底“锁”住了什么?锁是什么?
通过以上所说:
synchronized关键字,作用在类上,是类锁,那么锁就是这个类。锁住的是该类的所有实例化对象。
synchronized关键字,作用在对象上,是对象锁。那么锁就是这个对象。锁住的是该对象。
synchronized的底层原理
synchronized作用的地方就两个:同步方法块、同步方法。
分析反编译之后的字节码,对于同步方法JVM采用的是ACC_SYNCHRONIZED标识符实现同步,对于同步代码块,JVM采用monitorenter和monitorexit来实现同步。不管是基于ACC_SYNCHRONIZED还是monitorenter、monitorexit都是基于Monitor实现的。参照monitor的原理
synchronized的性质
synchronized具有不可中断性 以及可重入性。
可重入性依赖于monitor监视器里的计数器。其实也很好理解,获取了对象锁,那么这个对象的方法都可以访问,但是下面这个种情况不行。
这个也很好理解,对象锁(一个是son,一个是father)都一样,当然不可重入。这里面其实也暗含着死锁。
缺陷
- 效率低
来源于不可中断和无法知道是否成功获取到锁。一个等待的线程如果获取不到锁就会一直等待,而不能做其他的事情。 - 不够灵活
加锁和解锁的本质上是对象锁,所以不能够适用于分布式。 - 重量级锁
重量级锁其实是相对于AQS自旋锁来说的,其重量级的原因在于,java线程是映射到操作系统的原生线程之上的,如果要阻塞一个线程或者唤醒,需要操作系统切换到内核态,因此状态切换需要花费处理器的时间,对于代码简单的同步块,状态转换消耗的时间可能比代码执行的时间长,所以说synchronized是java语言中的一个重量级的操纵。