synchronized是Java中的关键字,在并发编程中被称为内置锁或者监视器锁。当用它来修饰一个方法或者一个代码块的时候能够保证同一时刻最多只有一个线程执行该段代码。
Java的内置锁相当于一种互斥锁,最多只有一个线程能够持有这种锁,故而由这个锁保护的同步代码块会以原子方式执行,多个线程在执行该代码时就不会相互干扰。
但由于被锁保护的同步块代码是以串行形式来访问的,即多个线程以独占的方式访问对象,而这也导致如果被锁保护的同步代码块的作用范围过大,会导致并发不良。
这里有必要简单讲一下内置锁的实现同步的原理:Java中的每一个对象都可以作为锁,synchronized用的锁其实存在Java对象头里(这也是为什么称其为内置锁的原因)。
而根据synchronized修饰的方法或方法块的不同,其锁的形式也不一。
- 对于普通同步方法,锁是当前实例对象;
- 对于静态同步方法,锁是当前类的Class对象;
- 对于同步方法块,锁是synchronized括号内配置的对象;
以下是一个简单的例子,表示synchronized会根据其修饰对象不同而获取不同的锁。
public class Test { /** * 修饰方法,会对该类的实例进行加锁,该实例的所有synchronized方法都必须 * 等待当前锁释放后才能访问 */ public synchronized void syn1(){ //TODO } /** * 同步静态方法,会对类对象上锁,该类的其他实例的synchronized方法必须等当前锁释放后才能访问 */ public synchronized static void syn3() { //TODO } /** * 同步指定对象,会对对象上锁,其他使用该对象的synchronized方法必须等当前锁释放后才能访问 */ public void syn4() {
//obj仅代表要修饰的具体对象名 synchronized (obj) { } } /** * 同步指定的类,会对类对象上锁,该类的其他实例的synchronized方法必须等当前锁释放后才能访问 */ public void syn5() {
//ClassName仅代表要修饰的具体类 synchronized(ClassName.class) { //TODO } } }
此外,实现同步是要很大的系统开销作为代价的,甚至可能造成死锁(锁嵌套时),所以尽量避免无谓的同步控制。此外,为了并发的性能考虑,在实际使用synchronized关键字时,要注意确定synchronized保护的同步代码块大小合理。
为什么要重点提一下synchronized这个关键字呢?
- 在《Java并发编程实战》中提到,synchronized作为JVM的内置属性,它能执行一些优化,例如对线程封闭的锁对象的锁消除优化,通过增加锁的粒度来消除内置锁的同步,而基于类库的锁来实现这些功能则可能性不大。
- 内置锁的性能在Java6中已经有所提升,已经较为接近ReentrantLock。(而在Java5中ReentrantLock中是远远胜出的)
- 内置锁结构紧凑,而且危险性较低(ReentrantLock可能忘记调用unlock)
所以当内置锁可以满足需求时,尽可能使用内置锁,而仅当内置锁无法满足需求(如可中断、可定时、可轮询、公平性等)。简单的来讲,当需要对锁的获取和释放有更精确的控制要求时,考虑使用ReentrantLock。