关于synchronized关键字的思考

  最近看了一篇大佬的博文,深有体会,来了一手依瓢画葫芦仿写一篇关于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究竟是对什么起作用了,是代码?还是对象? 相信当你把测试代码浏览一遍的时候你心中已经有了答案。

  接下来,验证你的猜测吧,在测试代码注释中有三种方法我们来测试一下:

  1. 将Mutex对象定义为静态变量,实现共享资源的唯一性。
  2. 将运用了synchronized关键字的方法改写,改为锁住ThreadTest3.class这个对象
  3. 在测试运行的时候将循环下的第一句ThreadTest3的构造方法移到外面,从而只创建一个实例对象。

  接下来结果验证如下:

开始进行中—-
结束
开始进行中—-
结束
开始进行中—-
结束

  可见,synchronized(this)以及非static的synchronized方法,只能防止多个线程同时执行同一个对象的同步代码段,它锁住的正是括号中的对象。

  在测试代码中我设置了一个私有变量Mutex以便理解,其实可以将它注释掉,在源代码中对应位置改为synchronized(this)是一个道理。

扫描二维码关注公众号,回复: 2462061 查看本文章

  而我们在代码中所做的以上几种修改其实也只有一个目的,那便是使它们锁住的对象一致。

  在这里再多谈一句关于“锁”的理解,这种说法其实并不是很严谨,准确的来说应该是某线程获取了与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.

猜你喜欢

转载自blog.csdn.net/BraveAsta/article/details/81271092
今日推荐