访问 https://www.idwarf.cn 获取更多java内容
概念
synchronized 是 Java 中的关键字,是利用锁的机制来实现同步的。
锁机制有如下两种特性:
-
互斥性:即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问。互斥性我们也往往称为操作的原子性。
-
可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。
锁
对象锁
在 Java 中,每个对象都会有一个 monitor 对象,这个对象其实就是 Java 对象的锁,通常会被称为“内置锁”或“对象锁”。类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。
某一线程占有这个对象的时候,先询问monitor的计数器是不是0,如果是0还没有线程占有,此时这个线程可占有这个对象,并且对这个对象的monitor+1;如果不为0,表示这个线程已经被其他线程占有,这个线程进入等待状态。当线程释放占有权的时候,monitor-1;
可重入:某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
synchronized (this) {
System.out.println("第1次获取锁,这个锁是:" + this);
int index = 1;
while (i<10) {
synchronized (this) {
System.out.println("第" + (++index) + "次获取锁,这个锁是:" + this);
}
i++;
}
}
类锁
在 Java 中,针对每个类也有一个锁,可以称为“类锁”,类锁实际上是通过对象锁实现的,即类的 Class 对象锁。每个类只有一个 Class 对象,所以每个类只有一个类锁。
Synchronized用法
根据修饰对象分类:
- 修饰代码块
synchronized(this|object) {}
synchronized(类.class) {}
- 修饰方法
- 修饰非静态方法
public synchronized void methodName(){
……
}
- 修饰静态方法
public synchronized static void methodName(){
……
}
根据获取的锁分类:
- 获取对象锁:
synchronized(this|object) {}
修饰非静态方法
- 获取类锁
synchronized(类.class) {}
修饰静态方法
- 在代码块上加锁:使用javap(javap -V filename)通过反编译Synchronized所在的类可以发现,在Synchronized所使用的方法被monitorenter和monitorexit包含,注意monitorexit有两个,一个为正常结束时的出口,一个为异常结束时的出口。
- 在方法上加锁:方法上有个flag,值为ACC_SYNCHRONIZED表示此方法具有互斥性。
JDK对synchronized的优化。
- jdk1.6之前,对于最原始的synchronized关键字,锁被称之为重量级锁。
- jdk1.6之后,锁分为以下几种:
- 无锁状态,无锁。
- 偏向锁,指一段同步代码一直被同一个线程所访问,那么该线程会自动的获取锁。降低获取锁的代价。
- 轻量级锁,当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
- 重量级锁,当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没获取到锁就会进入阻塞,该锁膨胀为重量级锁。重量级会让其他申请线程阻塞,性能降低。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kMMKFucx-1594090835870)(https://www.idwarf.cn/upload/2020/07/image-323ef3c6a27a42aba6132e0209720552.png)]
偏向锁,轻量级锁都是乐观锁,重量级锁是悲观锁。
一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着,它现在认为只可能有一个线程来访问它,所以当第一个线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁。偏向第一个线程,这个线程在修改对象头成为偏向锁的时候使用CAS操作(下文提及),并将对象头中的ThreadID改成自己的ID,之后再次访问这个对象时,只需要对比ID,不需要再使用CAS在进行操作。
一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象时偏向状态,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,(偏向锁就是这个时候升级为轻量级锁的)。如果不存在使用了,则可以将对象回复成无锁状态,然后重新偏向。
轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(执行几次空循环,自旋),另一个线程就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。
注:此段解释引用于:https://blog.csdn.net/cuichunchi/article/details/88532582
锁消除
虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行削除。
如下,synchronized代码段操作本身具有原子性,不需要使用synchronized:
synchronized(this){
int i = 10;
}
CAS
Compare-and-Swap. 在sun.misc.Unsafe类中有相关的方法。其底层是用 C/C++ 实现的,所以它的方式都是被 native 关键字修饰过的。Unsafe是CAS的核心类,需要通过本地方法来访问,由于Java方法无法直接访问底层系统,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jLTP3QdD-1594090835873)(https://www.idwarf.cn/upload/2020/07/image-0398fc32c16b483d941e649c22b32ab7.png)]
CAS 的思想很简单:三个参数,一个当前内存值 V、旧的预期值 A、即将更新的值 B,当且仅当预期值 A 和内存值 V 相同时,将内存值修改为 B 并返回 true,否则什么都不做,并返回 false。