二、synchronized的用法
可以参考之前的文章Thread类的使用,线程同步一节有讲到
1.作用于代码块上,只同步这一段代码:
synchronize(this){//this指锁定当前对象
num++;
System. out.println(name + ", 你是第" + num + "个使用timer的线程" );
}
?2.放在方法声明中,表明整个方法为同步方法:
public synchronized void add(String name) {//还可以修饰static方法
num++;
System. out.println(name + ", 你是第" + num + "个使用timer的线程" );
}
?有几点需要注意的地方:
1.当一个线程正在访问一个对象的synchronized方法时,其它线程不能访问该对象的其它synchronized方法。因为该对象的锁还未被释放,其它线程拿不到。
2.当一个线程正在访问一个对象的synchronized方法时,其它线程可以访问该对象的其它非synchronized方法。因为非synchronized方法,不需要锁。
3.当一个线程正在访问一个对象的synchronized方法时,其它线程可以访问其它对象的synchronized方法或非synchronized方法。因为锁的对象不一样。
?
另外,每个类也有自己的锁,被synchronized修饰的static方法,类锁与对象的锁也不会发生互斥。看下面代码:
public synchronized void test1(){//对象锁
//
}
public synchronized static void test2(){//类锁
//
}
?当多线程同时访问test1()和test2()时,可以并发访问,不会发生互斥。因为锁的对象不一样,一个是类,一个是对象。
?
三、synchronized的实现原理
synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令,这两个字节码都需要一个reference类型参数来指明需要锁定和解锁的对象。如果在程序中明确指定了对象参数,那就是这个对象的reference,如果没有明确指定,那根据synchronized修饰的是实例方法还是静态方法,去取对应的对象实例或Class对象来作为锁对象。
看下面这段代码:
package com.yuwl.thread.demo;
public class TestSynchronized {
public void test1(){
}
public void test2(){
synchronized( this){
}
}
public synchronized void test3(){
}
}
?对其反编译的字节码为:

从反编译的字节码可以看出,加了synchronized的代码块多了两个指令。
?
根据虚拟机规范的要求,在执行monitorenter指令时,首先要尝试获取对象的锁。如果已经拿到对象的锁,则把计数器加1,相应的在执行monitorexit指令时会将计数器减1,当计算器为0时,锁就被释放了。如果获取对象失败,那当前线程就要阻塞等待,直到对象锁被另外一个线程释放。对于synchronized方法,执行中的线程会识别该方法的method_info结构是否有ACC_SYNCHRONIZED标记,如果有会自动获取对象的锁,调用方法,最后释放锁。
?
synchronized还有一个重要特性——可重入性,不会自己把自己锁死。解释下,比喻一个线程执行同步方法1,而同步方法1又要调用同步方法2,这种情况是可重入的。
?
synchronized对异常的处理,如果同步块有异常发生,线程会自动释放锁。
?
参考
《深入Java 虚拟机》