Java synchronized 关键字

一个同步代码块由两部分组成:一个提供锁的对象引用和被锁限定的代码块。每一个 java 对象都可以作为实现同步的锁(即提供锁的对象),该锁是互斥锁、可重入锁。在同一时间,只允许一个线程执行同步代码块。
根据代码写法和提供锁的对象的不同,synchronized 关键字有四种用法。

  1. 修饰类的实例方法。此时,调用该方法的类的实例提供同步锁,被锁限定的代码块是整个方法体。例如
class DoSomethingCounter{
	private int cnt;
	public synchronized void service() throws InterruptedException {
		// Do something
		TimeUnit.MILLISECONDS.sleep(100);
		cnt++;
	}
	public synchronized int getCnt(){
		return cnt;
	}
}

当一个线程通过 DoSomethingCounter 对象实例 doSomethingCounter 调用 service()getCnt() 方法时,它获得了对该实例的锁,其它线程不允许同时访问doSomethingCounter 实例的所有同步方法,但是可以访问非同步方法。同步方法调用完成后,当前线程会自动失去对该实例的锁。
2. 修饰类的静态方法。此时,由该类的 Class 实例提供同步锁,被锁限定的代码块是该静态方法整个方法体。

class DoSomethingCounter{
	private static int cnt;
	public synchronized static void service()  {
		// Do something
		cnt++;
	}
	public synchronized int getCnt(){
		return cnt;
	}
}

Synchronized static methods are synchronized on the class object of the class the synchronized static method belongs to. Since only one class object exists in the Java VM per class, only one thread can execute inside a static synchronized method in the same class.
3. 修饰实例方法中的代码块。此时由 synchronized 关键字后小括号中的实例提供同步锁,被锁限定的的代码块是大括号中的代码。例如

class DoSomethingCounter{
	private int cnt;
	public void service()  {
		// Do something
		// this 代表当前调用该方法的实例,即由该实例提供同步锁 
		synchronized(this){
			cnt++;
		}
	}
	public synchronized int getCnt(){
		return cnt;
	}
}

同样的,当一个线程执行被 synchronized 修饰的代码块时,其它线程不允许访问提供同步锁的实例的其它同步方法或代码。当由其它的类实例(而非 this)提供同步锁时,被保护的实例状态并非当前提供被调用方法的实例,而是提供同步锁的其它类的类实例。
4. 修饰静态方法中的代码块。此时由 synchronized 关键字后小括号中的 Class 实例提供同步锁,被锁限定的的代码块是大括号中的代码。例如

class DoSomethingCounter{
	private static int cnt;
	public static void service()  {
		// Do something
		// 由
		synchronized(DoSomethingCounter.class){
			cnt++;
		}
	}
	public synchronized int getCnt(){
		return cnt;
	}
}
  • 修饰方法和方法中的代码块
    若将一个大的方法声明为synchronized 将会大大影响效率。学过设计模式的同学可能会对单例模式中的双重校验锁印象深刻,双重校验锁即是同时考虑了线程安全和程序效率的产物。
    首先我们看一下保证线程安全的单例模式的简单实现方法。
class Singleton{
	private static Singleton instance;
	private Singleton(){}
	public synchronzied static Singleton getInstance(){
		if(instance ==  null){
			instance = new Singleton();
		}
		return instance;
	}
}

通过 synchronized 获得实例的方法,保证不会有多个同时线程去检验 instance == null 进而导致可能生成多个实例,从而保证了线程安全。然而这样做的弊端很明显,单例第一次被产生后,以后调用该方法每次都只会返回该单例,不改变类的状态,因此也不需要同步了。
我们可以通过减少 synchronized 的范围来提高程序运行效率。

/* (1)和 (2)处进行两次校验,防止多个线程同时运行到 (1)处,其中一个线程得到锁 -> 产生实例 -> 释放锁 -> 其它线程得到锁,从而生成多个实例。volatile 关键字的一个作用是禁止指令重排(jdk 1.5 后才能保证),这点在这里至关重要,因此双重校验锁单例只在 jdk 1.5 及其后的版本中有效。
这里只是为了讲解修饰方法和方法中的代码块,实际上 jdk 1.5 之后的版本,推荐使用枚举来实现单例模式。
*/
class Singleton{
	private volatile static Singleton instance;
	private Singleton(){}
	public static Singleton getInstance(){
		if(instance ==  null){// (1)
			synchronzied(Singleton.class) {
				if(instance ==  null){ //(2)
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}

  • 修饰静态方法和实例方法
    如果一个类中定义了一个synchronized 的 static 方法 method1,也定义了一个 synchronized 的 instance 方法 method2,那么这个类的同一对象实例在多线程中分别访问 method1 和 method2 两个方法时,不会构成同步,因为它们的锁不一样。

  • The synchronized mechanism was Java’s first mechanism for synchronizing access to objects shared by multiple threads. The synchronized mechanism isn’t very advanced though. That is why Java 5 got a whole set of concurrency utility classes (java.util.concurrent)to help developers implement more fine grained concurrency control than what you get with synchronized.

Reference: Java Concurrency

你可能感兴趣的:(JAVA编程)