其实volatile和synchronized 两个操作在多线程中应用都很多,上篇文章谈到了volatile 这里主要说下后者
volatile和synchronized区别:
- volatile用的恰当的话会比synchronized使用和执行成本更低,因为他不会引起线程上下文的切换和调度
- 关键之synchronized可以修饰方法或者以同步块的形式来进行使用,他主要是确保多线程在同一时刻,只能有一个线程处于方法或者同步块中,他保证了线程对变量访问的可见性
- 相对volatile ,synchronized是重量级锁
用synchronized锁的几种表现形式:
- 对于普通同步方法,锁是当前实例对象
- 对于静态同步方法,锁是当前类的Class对象
- 对于同步方法快,锁是Synchronized括号里配置的对象
synchronized的锁是咋回事:
- 当一个对象试图访问同步代码块时,他首先必须得到锁,退出或者抛出异常时必须释放锁
- JVM基于进入(monitorenter)和退出(monitorexit) Monitor 来实现方法和代码块同步
- monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit相匹配。任何对象都有一个monitor与之关联,并且当一个monitor被持有后,他处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象对应的monitor的所有权,即尝试获得对象的锁
对于以上原理的代码示例:
源码:
/**
*
*/
package com.hhx.offline_tools.encode;
/**
* Synchronized获取锁的原理解析
* @author 清水贤人
*
*/
public class SynchronizedTest {
/**
* @param args
*/
public static void main(String[] args) {
//对Synchronized Class 对象进行加锁
synchronized (SynchronizedTest.class) {
}
//静态同步方法,对Synchronized Class 对象进行加锁
m();
}
public static synchronized void m(){
}
}
编译后的代码:
{
public com.hhx.offline_tools.encode.SynchronizedTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/hhx/offline_tools/encode/SynchronizedTest;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #1 // class com/hhx/offline_tools/encode/SynchronizedTest
2: dup
3: monitorenter //monitorenter :监视器进入,获取锁
4: monitorexit //monitorexit :监视器退出,释放锁
5: invokestatic #16 // Method m:()V
8: return
LineNumberTable:
line 19: 0
line 23: 5
line 24: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
public static synchronized void m();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED //同步方法依靠方法上的修饰符:ACC_SYNCHRONIZED来完成
Code:
stack=0, locals=0, args_size=0
0: return
LineNumberTable:
line 29: 0
LocalVariableTable:
Start Length Slot Name Signature
}
SourceFile: "SynchronizedTest.java"
上面的class 信息中,对于同步块使用了monitorenter 和 monitorexit 指令,二同步方法则是依靠方法修饰符上的ACC_SYNCHRONIZED 来完成,无论猜中那种方式,其本质都是一个对象的监视器monitor进行获取,而这个过程是排他的。
任意对象都有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,调用的线程必须先获取到该对象的监视器(monitor)才能进入,而没有获取的监视器的线程将会被阻塞,进入BLOCKED状态。这也解释了 文章开头提到的为啥synchronized关键字能保证多线程下的原子性。