• Java并发——synchronized关键字


    前言:

      只要涉及到Java并发那么我们就会考虑线程安全,实际上能够实现线程安全的方法很多,今天先介绍一下synchronized关键字,主要从使用,原理介绍

    一、synchronized的使用方法

      1、修饰代码块:大括号括起来的代码,作用于调用的对象

      2、修饰方法:整个方法,作用于调用的对象

    下面来演示代码,并且理解这两个使用的方法

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class SynchronizedExample {
        //使用synchronized修饰代码块
        public void test1(int j) {
            synchronized(this){
                for(int i=0;i<10;i++) {
                    System.out.println("test->"+j+"->"+i);
                }
            }
        }
        //使用synchronized修饰方法
        public synchronized void test2(int j) {
                for(int i=0;i<10;i++) {
                    System.out.println("test->"+j+"->"+i);
                }
        }
        public static void main(String args[]) {
            SynchronizedExample example1=new SynchronizedExample();
            SynchronizedExample example2=new SynchronizedExample(); 
            ExecutorService e=Executors.newCachedThreadPool();
            e.execute(()->{
                example1.test1(1);
            });
            e.execute(()->{
                example2.test1(2);
            });
        }
    }

      上面的代码演示了如何修饰代码块和方法。这里主要说“作用于调用的对象”如何理解。上面两个线程分别执行两个对象中的test1方法,结果是两个线程会结合在一起执行,这是因为调用的对象不同,一个是example1对象,另一个是example2对象。如果同时调用example1对象中的test1和test2方法,那么会先执行test1再执行test2。就是这时候只在调用的对象相同的时候才会实现同步操作。结果如下图

      3、修饰静态方法:整个静态方法,作用于所有对象

      4、修饰类:括号起来的部分,作用于所有对象

      

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class SynchronizedExample {
        //使用synchronized修饰类
        public void test1(int j) {
            synchronized(SynchronizeExample.class){
                for(int i=0;i<10;i++) {
                    System.out.println("test->"+j+"->"+i);
                }
            }
        }
        //使用synchronized修饰静态方法
        public static synchronized void test2(int j) {
                for(int i=0;i<10;i++) {
                    System.out.println("test->"+j+"->"+i);
                }
        }
        public static void main(String args[]) {
            SynchronizedExample example1=new SynchronizedExample();
            SynchronizedExample example2=new SynchronizedExample(); 
            ExecutorService e=Executors.newCachedThreadPool();
            e.execute(()->{
                example1.test1(1);
            });
            e.execute(()->{
                example2.test1(2);
            });
        }
    }

      当理解了上面两条,下面的结果就很显然了。这里有点不同就是使用synchronized修饰类和修饰方法块时,后面的一个为当前类,一个为this,this可以理解为当前类的实例化对象,所以只对当前调用的对象有加锁。而锁一个类则是锁了当前类的所有实例化对象。即作用范围不一样。

    二、synchronized原理

     上面我们只是介绍了一下synchronized关键字的使用方法。接下来就是底层实现原理的介绍。

    1、执行synchronized代码块的三个步骤:

      (1)、线程尝试获得锁,如果获得就进入第二步,否则就加入等待队列,阻塞并等待唤醒

      (2)、执行synchronized修饰部分的代码块

      (3)、线程释放锁。如果等待队列上有等待的线程,从中取一个并唤醒,但是不一定是按顺序的,也就是不保证公平性

    2、JVM中的synchronized底层实现

      JVM是基于进入和退出monitor对象来实现方法同步和代码块同步。对于代码块同步:任何对象都有一个monitor与之关联,当一个monitor被持有后就会处于锁定状态,当线程执行到monitorenter指令时,就会尝试获得对象的monitor所有权。而反映到上面步骤即尝试获得锁。而后执行代码,当遇到monitorexit指令后就会释放锁。所以monitorexit指令一般放在方法结束处和异常处。上述这些指令都是JVM中编译后自动放置的。平常写代码的时候并不需要。

    3、为什么synchronized作用的是对象

      在上面的使用方法中基本上都说了synchronized作用于对象,无论是调用的对象还是所有对象。为什么是作用于对象而不是代码块呢?是因为synchronized用的锁是存在Java对象头里的

      synchronized代表重量级锁(其实JDK有对synchronized进行各种优化,所以性能上有所提升。并不是说重量级锁就不使用了。)所以synchronized锁的是对象而不是代码块。

    4、synchronized的可重入性

      synchronized是可重入的。可重入是通过记录锁的持有线程和持有数量来实现。当被调用synchronized保护的代码时,检查对象是否被锁,如果是再检查当前线程锁定,如果还是,那么增加持有数量。也就是上面例子中当一个线程拿到example1的锁之后,在没有释放锁的时候再次请求example1的锁是可以一定可以拿到的。这时候在JVM中关联的计数器就会加一,就相当于该线程持有锁的数量加一。而后每次执行一次monitorexit指令就会数量减一。当计数器为0的时候才会使得该线程释放对象锁(这里一定要弄清是对象而不是代码块,线程持有的是一个对象)。

    5、synchronized的内存可见性

      synchronized是保证内存可见性的。在释放锁的时候,所有写入都会写回内存,获得锁后,就会从内存中读最新数据。但是若只为了保证内存可见性,简易使用volatile这个更加轻量级的锁。

    四、总结

      其实重点强调的就是synchronized虽然是包裹着一个代码块,但是作用的一定是一个对象。这里并不是说线程拿到这个对象,这个对象就会被锁住,里面的其他代码都不可执行。这里意思是两个或多个线程尝试获得synchronized修饰的同一个代码块或方法,才会引起锁竞争。但是由于有时候作用的只是当前的调用对象,所以其他对象访问的时候也会也不会引起锁竞争,具体看使用方法的第一个例子。更重要还是去理解JVM中加入的monitorenter指令和monitorexit指令,会对理解synchronized有非常重要的帮助。同时说一下,当子类继承父类的时候,没有重写父类的synchronized方法,那么使用的时候是直接使用父类的方法,所以会同步。但是如果重写了方法却没有加上synchronized修饰。那么就不会有同步操作。所以子类是不会继承父类的synchronized锁。

  • 相关阅读:
    jquery中$.get()提交和$.post()提交有区别吗?
    src = "security/afafsff/?ip=123.4.56.78&id=45",请写一段代码用正则匹配出ip
    python如何捕获异常
    平衡点问题
    支配点问题:
    python程序中文输出问题怎么解决? 用encode和decode
    介绍一下Python中webbrowser的用法?
    XML文档定义有几种形式?它们之间有何本质区别?解析XML文档有哪几种方式?
    垃圾回收的优点和原理。并考虑2种回收机制。
    设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。写出程序。
  • 原文地址:https://www.cnblogs.com/Cubemen/p/10753430.html
Copyright © 2020-2023  润新知