• synchronized类锁,对象锁,方法锁


    synchronized从语法的维度一共有3个用法:

    1. 静态方法加上关键字

    2. 实例方法(也就是普通方法)加上关键字

    3. 方法中使用同步代码块

    前两种方式最为偷懒,第三种方式比前两种性能要好。

    synchronized从锁的是谁的维度一共有两种情况:

    1. 锁住类

    2. 锁住对象实例

    我们还是从直观的语法结构上来讲述synchronized。

    1)静态方法上的锁

    静态方法是属于“类”,不属于某个实例,是所有对象实例所共享的方法。也就是说如果在静态方法上加入synchronized,那么它获取的就是这个类的锁,锁住的就是这个类

    2)实例方法(普通方法)上的锁

    实例方法并不是类所独有的,每个对象实例独立拥有它,它并不被对象实例所共享。这也比较能推出,在实例方法上加入synchronized,那么它获取的就是这个累的锁,锁住的就是这个对象实例

    那锁住类还是锁住对象实例,这跟我线程安全关系大吗?大,差之毫厘谬以千里的大。为了更好的理解锁住类还是锁住对象实例,在进入“3)方法中使用同步代码块”前,先直观的感受下这两者的区别。

    对实例方法(普通方法)上加关键字锁住对象实例锁的解释

    首先定义一个Demo类,其中的实例方法加上了synchronized关键字,按照所述也就是说锁住的对象实例。

    public class Demo {
    
       public synchronized void demo() {
           while (true) {   //synchronized方法内部是一个死循环,一旦一个线程持有过后就不会释放这个锁
               System.out.println(Thread.currentThread());
           }
       }
    }

    可以看到在demo方法中定义了一个死循环,一旦一个线程持有这个锁后其他线程就不可能获取这个锁。结合上述synchronized修饰实例方法锁住的是对象实例,如果两个线程针对的是一个对象实例,那么其中一个线程必然不可能获取这个锁;如果两个线程针对的是两个对象实例,那么这两个线程不相关均能获取这个锁。

    自定义线程,调用demo方法。

    public class MyThread implements Runnable {
       private Demo demo;
    
       public MyThread(Demo demo) {
           this.demo = demo;
       }
    
       @Override
       public void run() {
           demo.demo();
       }
    }

    测试程序1:两个线程抢占一个对象实例的锁

    public class Main1 {
       public static void main(String[] args) {
           Demo demo = new Demo();
           Thread thread1 = new Thread(new MyThread(demo));
           Thread thread2 = new Thread(new MyThread(demo));
           thread1.start();
           thread2.start();
       }
    }

    如上图所示,输出结果显然只会打印一个线程的信息,另一个线程永远也获取不到这个锁。

    测试程序2:两个线程分别抢占两个对象实例的锁

    public class Main2 {
       public static void main(String[] args) {
           Demo demo1 = new Demo();
           Demo demo2 = new Demo();
           Thread thread1 = new Thread(new MyThread(demo1));
           Thread thread2 = new Thread(new MyThread(demo2));
           thread1.start();
           thread2.start();
       }
    }

    如上图所示,显然,两个线程均进入到了demo方法,也就是均获取到了锁,证明,两个线程抢占的就不是同一个锁,这就是synchronized修饰实例方法时,锁住的是对象实例的解释。

    对静态方法上加关键字锁住类锁的解释

    静态方法是类所有对象实例所共享的,无论定义多少个实例,是要是静态方法上的锁,它至始至终只有1个。将上面的程序Demo中的方法加上static,无论使用“测试程序1”还是“测试程序2”,均只有一个线程可以抢占到锁,另一个线程仍然是永远无法获取到锁。

    让我们重新回到从语法结构上解释synchronized。

    3)方法中使用同步代码块

    程序的改良优化需要建立在有坚实的基础,如果在不了解其内部机制,改良也仅仅是“形式主义”。

    结合开始CodeReview的例子:

    你的同事在CodeReview时,要求你将实例方法上的synchronized,改为效率更高的同步代码块方式。在你不清楚同步代码的用法时,网上搜到了一段synchronized(this){}代码,复制下来发现也能用,此时你以为你改良优化了代码。但实际上,你可能只是做了一点形式主义上的优化。

    为什么这么说?这需要清楚地认识同步代码块到底应该怎么用。

    3.1)synchronized(this){...}

    this关键字所代表的意思是该对象实例,换句话说,这种用法synchronized锁住的仍然是对象实例,他和public synchronized void demo(){}可以说仅仅是做了语法上的改变。 

    public class Demo {
      
       public synchronized void demo1() {
           while (true) {  //死循环目的是为了让线程一直持有该锁
               System.out.println(Thread.currentThread());
           }
       }
    
       public synchronized void demo2() {
           while (true) {
               System.out.println(Thread.currentThread());
           }
       }
    }

    改为以下方式:

    public class Demo {
    
       public void demo1() {
           synchronized (this) {
               while (true) {  //死循环目的是为了让线程一直持有该锁
                   System.out.println(Thread.currentThread());
               }
           }
       }
    
       public void demo2() {
           synchronized (this) {
               while (true) {
                   System.out.println(Thread.currentThread());
               }
           }
       }
    }

    也许后者在JVM中可能会做一些特殊的优化,但从代码分析上来讲,两者并没有做到很大的优化,线程1执行demo1,线程2执行demo2,由于两个方法均是抢占对象实例的锁,只要有一个线程获取到锁,另外一个线程只能阻塞等待,即使两个方法不相关。

    3.2)private Object obj = new Object();    synchronized(obj){...}

    public class Demo {
       private Object lock1 = new Object();
       private Object lock2 = new Object();
    
       public void demo1() {
           synchronized (lock1) {
               while (true) {  //死循环目的是为了让线程一直持有该锁
                   System.out.println(Thread.currentThread());
               }
           }
       }
    
       public void demo2() {
           synchronized (lock2) {
               while (true) {
                   System.out.println(Thread.currentThread());
               }
           }
       }
    }

    经过上面的分析,看到这里,你可能会开始懂了,可以看到demo1方法中的同步代码块锁住的是lock1对象实例,demo2方法中的同步代码块锁住的是lock2对象实例。如果线程1执行demo1,线程2执行demo2,由于两个方法抢占的是不同的对象实例锁,也就是说两个线程均能获取到锁执行各自的方法(当然前提是两个方法互不相关,才不会出现逻辑错误)。

    3.3)synchronized(Demo.class){...}

    这种形式等同于抢占获取类锁,这种方式,同样和3.1一样,收效甚微。

    所以CodeReivew后的代码应该是3.2) private Object obj = new Object();    synchronized(obj){...},这才是对你代码的改良优化。

  • 相关阅读:
    太可爱了!CSS3 & SVG 制作的米老鼠钟表
    20个免费的 AngularJS 资源和开发教程
    比尔盖茨:反垄断案让我分心,不然微软定能打败安卓(胜者通吃的行业要不计代价的三班倒,评论很精彩)
    C++11 新特性之智能指针(shared_ptr, unique_ptr, weak_ptr)
    C++编译器会对没有构造函数的类生成默认构造函数吗?(有必要的时候才生成,要看情况。有反汇编验证)
    qt5信息提示框QMessageBox用法(很全)
    (RPC) Remote Procedure Call Protocol 远程过程调用协议
    分布式事务就是由多个本地事务组合而成的事务
    内存管理--虚拟内存管理技术
    NET适合搞大数据,机器学习、人工智能
  • 原文地址:https://www.cnblogs.com/uoar/p/11673131.html
Copyright © 2020-2023  润新知