「 代码性能优化 」作为一名 Java 程序员,你真的了解 synchronized 吗?(一)
前言
synchronized 是 Java 中的关键字,是一种同步锁,本文将详细介绍 Java 中 Synchronized 用法,感兴趣的小伙伴跟博主一起讨论下。
什么是 synchronized
前言中提到,Synchronized 是一种同步锁,是 Java 中解决并发问题的一种最常用的方法,也是最简单的一种方法。
1、Java 中锁机制的特性
1.1. 互斥性(原子性)
所谓原子性就是指一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
在 Java 中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。但是像 i++、i+=1 等操作字符就不是原子性的,它们是分成读取、计算、赋值几步操
作,原值在这些步骤还没完成时就可能已经被赋值了,那么最后赋值写入的数据就是脏数据,无法保证原子性。被 synchronized 修饰的类或对象的所有操作都是原子的,因为在执行操作之前必须先获得类或对象
的锁,直到执行完才能释放,这中间的过程无法被中断(除了已经废弃的 stop()方法),即保证了原子性。
注意!synchronized 经常会和 volatile 作比较,它们俩特性上最大的区别就在于原子性,volatile 不具备原子性。
1.2. 可见性
synchronized 和 volatile 都具有可见性,其中 synchronized 对一个类或对象加锁时,一个线程如果要访问该类或对象必须先获得它的锁,而这个锁的状态对于其他任何线程都是可见的,并且在释放
锁之前会将对变量的修改刷新到主存当中,保证资源变量的可见性,如果某个线程占用了该锁,其他线程就必须在锁池中等待锁的释放。而 volatile 的实现类似,被 volatile 修饰的变量,每当值需要修改时
都会立即更新主存,主存是共享的,所有线程可见,所以确保了其他线程读取到的变量永远是最新值,保证可见性。
1.3. 有序性
有序性指程序执行的顺序按照代码先后执行。
synchronized 和 volatile 都具有有序性,Java 允许编译器和处理器对指令进行重排,但是指令重排并不会影响单线程的顺序,它影响的是多线程并发执行的顺序性。synchronized 保证了每个时刻都
只有一个线程访问同步代码块,也就确定了线程执行同步代码块是分先后顺序的,保证了有序性。
1.4. 重入性
synchronized 和 ReentrantLock 都是可重入锁。当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属
于重入锁。通俗一点讲就是说一个线程拥有了锁仍然还可以重复申请锁。
2、Java 中锁的分类
2.1. 对象锁
在 Java 中,每个对象都会有一个 monitor 对象,这个对象其实就是 Java 对象的锁,通常会被称为“内置锁”或“对象锁”。类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。
2.2. 类锁
在 Java 中,针对每个类也有一个锁,可以称为“类锁”,类锁实际上是通过对象锁实现的,即类的 Class 对象锁。每个类只有一个 Class 对象,所以每个类只有一个类锁。
如何使用 synchronized
synchronized 的用法可以从两个维度上面分类:
1. 根据修饰对象分类
1.1 修饰代码块
synchronized(this|object) {}
synchronized(类.class) {}
1.2 修饰方法
修饰非静态方法
修饰静态方法
2. 根据获取的锁分类
2.1 获取对象锁
synchronized(this|object) {}
修饰非静态方法
2.2 获取类锁
synchronized(类.class) {}
修饰静态方法
synchronized 的锁膨胀
锁解决了数据的安全性,但是同样带来了性能的下降,hotspot 虚拟机的作者经过调查发现,大部分情况下,加锁的代码不仅仅不存在多线程竞争,而且总是由同一个线程多次获得。
所以基于这样一个概率,synchronized 在 JDK1.6 之后做了一些优化,为了减少获得锁和释放锁来的性能开销,引入了偏向锁、轻量级锁,锁的状态根据竞争激烈的程度从低到高不断升级。
总结来讲,锁有四种状态,并且会因实际情况进行膨胀升级,其膨胀方向是:无锁——>偏向锁——>轻量级锁——>重量级锁,并且膨胀方向不可逆。
1. 无锁
无锁没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。
2. 偏向锁
偏向锁是 JDK1.6 中引用的优化,它的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的性能。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低
而引入了偏向锁。偏向锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要同步。一句话总结它的作用:减少统一线程获取锁的代价。
3. 轻量级锁
轻量级锁也是在 JDK1.6 中引入的新型锁机制。它不是用来替换重量级锁的,它的本意是在没有多线程竞争的情况下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。
是指当锁是偏向锁,存在其它线程申请同一个锁对象时的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。也就是说这里的其它线程只是申
请锁,不存在两个线程同时竞争锁,可以是一前一后地交替执行同步块。
4. 重量级锁
指的是原始的 Synchronized 的实现,重量级锁的特点:当同一时间有多个线程竞争锁时,都会被阻塞,只有持有锁的线程释放锁之后才会唤醒这些线程此时其申请锁带来的开销也就变大。
重量级锁一般使用场景会在追求吞吐量,同步块或者同步方法执行时间较长的场景。
总结
以上内容从 Java 中锁的特性、分类及优化三方面介绍了 Synchronized 的知识,但并未触及底层原理,请继续关注博主,接下里会继续就 Synchronized 展开讨论。
下一篇文章主题
jvm 中对象的结构模型、Synchronized 具体使用实例
版权声明: 本文为 InfoQ 作者【小刘学编程】的原创文章。
原文链接:【http://xie.infoq.cn/article/e8eba8a7bdd7360a9d8c76874】。文章转载请联系作者。
评论 (1 条评论)