最近看了一篇大佬的博文,深有体会,来了一手依瓢画葫芦仿写一篇关于synchronized关键字思考的博文。
在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。synchronized既可以加在一段代码上,也可以加在方法上。
然而,给方法或代码块上加上synchronized关键字就万事大吉了吗?不妨运行一下下面这段代码试试:
测试代码
import java.util.concurrent.TimeUnit;
// synchronized锁住的究竟是什么?
public class ThreadTest3 implements Runnable {
private final Object Mutex=new Object();
//方法1 static 共享对象
@Override
public void run(){
//方法2 synchronized (ThreadTest3.class)
/*
synchronized(Sync.class)实现了全局锁的效果。
static synchronized方法,static方法可以直接类名加方法名调用,方法中无法使用this,所以它锁的不是this,而是类的Class对象,
所以,static synchronized方法也相当于全局锁,相当于锁住了代码段。
*/
synchronized (Mutex){
System.out.println("开始进行中----");
try {
//模拟程序进行过程
TimeUnit.MILLISECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束");
}
}
public static void main(String[] args){
// 方法3 ThreadTest3 t=new ThreadTest3();
for(int i=0;i<3;i++){
ThreadTest3 t=new ThreadTest3();
Thread newThread=new Thread(t);
newThread.start();
}
}
可见结果如下:
开始进行中—-
开始进行中—-
开始进行中—-
结束
结束
结束
程序并没有如我们想象中的那么运行,原因是什么呢,这要看synchronized究竟是对什么起作用了,是代码?还是对象? 相信当你把测试代码浏览一遍的时候你心中已经有了答案。
接下来,验证你的猜测吧,在测试代码注释中有三种方法我们来测试一下:
- 将Mutex对象定义为静态变量,实现共享资源的唯一性。
- 将运用了synchronized关键字的方法改写,改为锁住ThreadTest3.class这个对象
- 在测试运行的时候将循环下的第一句ThreadTest3的构造方法移到外面,从而只创建一个实例对象。
接下来结果验证如下:
开始进行中—-
结束
开始进行中—-
结束
开始进行中—-
结束
可见,synchronized(this)以及非static的synchronized方法,只能防止多个线程同时执行同一个对象的同步代码段,它锁住的正是括号中的对象。
在测试代码中我设置了一个私有变量Mutex以便理解,其实可以将它注释掉,在源代码中对应位置改为synchronized(this)是一个道理。
而我们在代码中所做的以上几种修改其实也只有一个目的,那便是使它们锁住的对象一致。
在这里再多谈一句关于“锁”的理解,这种说法其实并不是很严谨,准确的来说应该是某线程获取了与Mutex(对象)关联的monitor锁,为了方便起见,我们便直接以锁来称呼。
Q:为甚叫monitor锁?
A:synchronized关键字包括monitor enter和monitor exit两个JVM指令,它能够保证在任何时候任何线程执行到monitor enter成功之前都必须从主内存中获取数据,而不是从缓存中,在monitor exit运行成功之后,共享变量被更新后的值必须刷入主内存。
当synchronized锁住一个对象后,别的线程如果也想拿到这个对象的monitor锁 ,就必须等待这个线程执行完成释放锁,才能再次给对象加锁,不然只能进入阻塞状态。为了达到线程同步的目的。即使两个不同的代码段,都要锁住同一个引用(对象),这样的话两个代码段便不能再多线程的状态下并发执行。
由于synchronized关键字具有排他性,即所有线程必须串行地经过synchronized保护的共享区域,synchronized作用域越大,其效率也会越低,所以我们在进行开发的时候应该注意尽量控制synchronized的作用域。
PS: 一个小总结
对于非static用synchronized修饰的同步方法=方法内用synchronized(this)覆盖代码块
以下两条方法是等价的:
public synchronized void method(){
//执行代码块----
}
public void method(){
synchronized(this){
//执行代码块
}
}
- 对于static用synchronized修饰的静态方法=静态方法内用synchronized(类名.class)覆盖代码块
以下两条方法是等价的:
public static synchronized void method(){
//执行代码块
}
public static void method(){
synchronized(类名.class){
//执行代码块
}
}
参考资料
1.https://www.cnblogs.com/QQParadise/articles/5059824.html
2.汪文君.Java高并发编详解,2018(1):63-76.