原理
synchronized代码块需要指定锁对象监视器,如果是实力方法则默认为实例对象。静态方法则为实例的class对象(全局唯一)。执行synchronized代码进入时会执行monitorenter指令申请对象monitor监视器,如果已经线程获取了则判断是否当前线程,如果是则继续执行,锁计数器递增1,如果不是则阻塞等待。退出synchronized代码块时,锁计数器递减1,如果monitor监视器计数为0,则执行monitorexit指令退出对象monitor监视器。
案例
非静态同步方法
package com.mytest;
/**
* @author 会灰翔的灰机
* @date 2021/3/7
*/
public class SyncTest {
private static int count;
public synchronized void countAddNormal(int delta) {
count += delta;
System.out.println(this + " : " + count);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sleep end");
}
public static void main(String[] args) {
SyncTest syncA = new SyncTest();
SyncTest syncB = new SyncTest();
// 线程0,线程1获取syncA对象监视器成功。其余线程等待对象监视器退出释放后继续执行
for (int i = 0; i < 10; i++) {
final int j = i;
new Thread(() -> {
if (j % 2 == 0) {
syncA.countAddNormal(1);
}
if (j % 2 == 1) {
syncB.countAddNormal(1);
}
}).start();
}
}
}
输出结果,共两个监视器对象syncA,syncB,所以同时进入的线程只有两个线程持有者(可重入)
com.mytest.SyncTest@2b074fd9 : 1
com.mytest.SyncTest@244c3e9e : 2
sleep end
sleep end
com.mytest.SyncTest@2b074fd9 : 3
com.mytest.SyncTest@244c3e9e : 4
sleep end
sleep end
com.mytest.SyncTest@2b074fd9 : 5
com.mytest.SyncTest@244c3e9e : 6
sleep end
sleep end
com.mytest.SyncTest@2b074fd9 : 7
com.mytest.SyncTest@244c3e9e : 8
sleep end
com.mytest.SyncTest@2b074fd9 : 9
sleep end
com.mytest.SyncTest@244c3e9e : 10
sleep end
sleep end
静态同步方法
package com.mytest;
/**
* @author 会灰翔的灰机
* @date 2021/3/7
*/
public class SyncTest {
private static int count;
public synchronized void countAddNormal(int delta) {
count += delta;
System.out.println(this + " : " + count);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sleep end");
}
public synchronized static void countAddStatic(int delta) {
count += delta;
System.out.println("static : " + count);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sleep end");
}
public static void main(String[] args) {
// 线程0,获取SyncTest.class对象监视器成功。其余线程等待对象监视器退出释放后继续执行
for (int i = 0; i < 10; i++) {
new Thread(() -> SyncTest.countAddStatic(1)).start();
}
}
}
输出结果,同时只有一个线程可以获取SyncTest.class对象监视器成功,所以就跟单线程执行结果是没有区别的
static : 1
sleep end
static : 2
sleep end
static : 3
sleep end
static : 4
sleep end
static : 5
sleep end
static : 6
sleep end
static : 7
sleep end
static : 8
sleep end
static : 9
sleep end
static : 10
sleep end
总结
synchronized与ReentrantLock对比,ReentrantLock提供了一些更加灵活的API,ReentrantLock主要有3个优势。jdk1.6及以后版本性能已经不再是它的优势了,synchronized性能与ReentrantLock基本持平
- 等待可中断。synchronized是无限期等待,无法定制中断条件,例如:超时时间,线程打断
- 可实现公平锁。synchronized是非公平锁竞争,默认ReentrantLock也是非公平锁,但是可以通过boolean类型构造器指定为公平锁
- 锁可以绑定多个条件。synchronized中锁对象的wait,notify,notifyAll方法可以实现一个隐含的条件,如果要实现多余一个条件的应用场景则不得不多加一个锁。例如:顺序输出ABC那篇博客的案例(https://blog.csdn.net/u010597819/article/details/113812365?spm=1001.2014.3001.5501)
刚才也提到了jdk对synchronized的优化使其性能可以与ReentrantLock持平,优化主要有几个点简单提下:自旋锁和自适应自旋锁,锁消除,锁粗化(锁合并),偏向锁,轻量锁,重量锁(锁膨胀)。