synchronized可以修饰静态方法、非静态方法、代码块
1.锁的对象是什么
synchronized关键字其实是“给某个对象加了把锁”,这个锁究竟加在了什么对象上面?如下面代码所示,给函数f1()、f2()加上synchronized关键字。
public class A {
public synchronized void f1(){ ...}
public synchronized static void f2(){...}
}
等价于如下代码:
public class A {
public void f1(){
synchronized (this){ ...}
}
public static void f2(){
synchronized (Main.class){...}
}
}
A a = new A()
a.f1();
a.f2();
对于非静态成员函数,锁其实是加载对象a上的;对于静态成员函数,锁是加在A.class上面的。
这间接回答了关于synchronized常见问题:一个静态成员函数和一个非静态成员函数,都加了synchronized关键字,分别被两个线程调用,它们是否互斥?很显然,因为是两把不同的锁,所以不会互斥。
锁的本质是什么
无论使用什么编程语言,只要是多线程,就一定会涉及锁。既然锁如此常见,那么锁的本质是什么呢?
多个线程要访问同一个资源,线程就是一段段运行的代码,资源就是一个变量、一个对象或一个文件等;而锁就是要实现线程对资源的访问控制,保证同一时间只能有一个线程去访问某一个资源。如果同一时间允许多个线程访问某一资源,锁就变成了信号量。
从程序角度看,锁其实就是一个“对象”,这个对象要完成一下几件事情:
(1)这个对象内部得有一个标志位(state变量)记录自己有没有被某个线程占用。最简单的情况是这个state有0、1两个取值,0表示没有现成占用这个锁,1表示有某个线程占用了这个锁。
(2)如果这个对象被某个线程占用,它得记录这个线程的thread ID,知道自己被哪个线程占用了
(3)这个对象还得维护一个thread id list,记录其他所有阻塞的、等待拿这个锁的线程。在当前线程释放锁后(也就是把state从1改回0),从这个thread id list里面取一个线程唤醒。
既然锁是一个“对象”,要访问共享资源本身也是一个对象,例如前面的a,这两个对象可以合为一个对象。代码就变成了synchronized(this){…},我们要访问的共享资源是对象a,而锁也是加在对象a上面的。当然,也可以另外新建一个对象,代码变成synchronized(obj1){…}。这个时候,访问的共享资源是对象a,而锁是加载新建的对象obj1上面的。
在HotSpot JVM实现中,锁有个专门的名字:对象监视器。
synchorinized实现原理
答案在对象头里。在对象头里,有一块数据叫Mark Word。在64位机器上,Mark Word是8字节的(64位)的,这64位中有2个重要字段:锁标志位和占用该锁的thread ID。因为不同版本的JVM实现,对象头的数据结构会有各种差异,此处不再进一步讨论。