zoukankan      html  css  js  c++  java
  • Synchronized 详解

    为了方便记忆,将锁做如下的分类

    一、对象锁

    包括方法锁(默认锁对象为this,当前实例对象)和同步代码块锁(自己指定锁对象)

    1.代码块形式:手动指定锁定对象,也可是是this,也可以是自定义的锁

    public class SynchronizedObjectLock implements Runnable {
        static SynchronizedObjectLock instence = new SynchronizedObjectLock();
    
        @Override
        public void run() {
            // 同步代码块形式——锁为this,两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行
            synchronized (this) {
                System.out.println("我是线程" + Thread.currentThread().getName());
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "结束");
            }
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(instence);
            Thread t2 = new Thread(instence);
            t1.start();
            t2.start();
        }
    }

    输出结果:

      我是线程Thread-0
      Thread-0结束
      我是线程Thread-1
      Thread-1结束

    public class SynchronizedObjectLock implements Runnable {
        static SynchronizedObjectLock instence = new SynchronizedObjectLock();
        // 创建2把锁
        Object block1 = new Object();
        Object block2 = new Object();
    
        @Override
        public void run() {
            // 这个代码块使用的是第一把锁,当他释放后,后面的代码块由于使用的是第二把锁,因此可以马上执行
            synchronized (block1) {
                System.out.println("blocl1锁,我是线程" + Thread.currentThread().getName());
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("blocl1锁,"+Thread.currentThread().getName() + "结束");
            }
    
            synchronized (block2) {
                System.out.println("blocl2锁,我是线程" + Thread.currentThread().getName());
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("blocl2锁,"+Thread.currentThread().getName() + "结束");
            }
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(instence);
            Thread t2 = new Thread(instence);
            t1.start();
            t2.start();
        }

    输出结果:

      blocl1锁,我是线程Thread-0
      blocl1锁,Thread-0结束
      blocl2锁,我是线程Thread-0  // 可以看到当第一个线程在执行完第一段同步代码块之后,第二个同步代码块可以马上得到执行,因为他们使用的锁不是同一把
      blocl1锁,我是线程Thread-1
      blocl2锁,Thread-0结束
      blocl1锁,Thread-1结束
      blocl2锁,我是线程Thread-1
      blocl2锁,Thread-1结束

    2.方法锁形式:synchronized修饰普通方法,锁对象默认为this

    public class SynchronizedObjectLock implements Runnable {
        static SynchronizedObjectLock instence = new SynchronizedObjectLock();
    
        @Override
        public void run() {
            method();
        }
    
        public synchronized void method() {
            System.out.println("我是线程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "结束");
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(instence);
            Thread t2 = new Thread(instence);
            t1.start();
            t2.start();
        }
    }

    输出结果:

      我是线程Thread-0
      Thread-0结束
      我是线程Thread-1
      Thread-1结束

    二、类锁

    指synchronize修饰静态的方法或指定锁对象为Class对象

    1.synchronize修饰静态方法

    public class SynchronizedObjectLock implements Runnable {
        static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
        static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();
    
        @Override
        public void run() {
            method();
        }
    
        // synchronized用在普通方法上,默认的锁就是this,当前实例
        public synchronized void method() {
            System.out.println("我是线程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "结束");
        }
    
        public static void main(String[] args) {
            // t1和t2对应的this是两个不同的实例,所以代码不会串行
            Thread t1 = new Thread(instence1);
            Thread t2 = new Thread(instence2);
            t1.start();
            t2.start();
        }
    }

    输出结果:

      我是线程Thread-0
      我是线程Thread-1
      Thread-1结束
      Thread-0结束

    public class SynchronizedObjectLock implements Runnable {
        static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
        static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();
    
        @Override
        public void run() {
            method();
        }
    
        // synchronized用在静态方法上,默认的锁就是当前所在的Class类,所以无论是哪个线程访问它,需要的锁都只有一把
        public static synchronized void method() {
            System.out.println("我是线程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "结束");
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(instence1);
            Thread t2 = new Thread(instence2);
            t1.start();
            t2.start();
        }
    }

    输出结果:

      我是线程Thread-0
      Thread-0结束
      我是线程Thread-1
      Thread-1结束

    2.synchronized指定锁对象为Class对象

    public class SynchronizedObjectLock implements Runnable {
        static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
        static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();
    
        @Override
        public void run() {
            // 所有线程需要的锁都是同一把
            synchronized(SynchronizedObjectLock.class){
                System.out.println("我是线程" + Thread.currentThread().getName());
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "结束");
            }
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(instence1);
            Thread t2 = new Thread(instence2);
            t1.start();
            t2.start();
        }
    }

    输出结果:
    我是线程Thread-0
    Thread-0结束
    我是线程Thread-1
    Thread-1结束

    三、思考

    1.两个线程同时访问1个对象的同步方法

    2.两个线程同时访问2个对象的同步方法

    3.两个线程访问的是synchronized静态方法

    4.两个线程同时访问同步(被synchronized修饰)和非同步(未被snychronized修饰)方法

    5.两个线程同时访问1个对象的不同的普通同步方法

    6.两个线程同时访问一个静态的synchronized方法和非静态的synchronized方法

    7.方法抛出异常后,会释放锁吗?

    核心思想:

    1.一把锁只能同时被一个线程获取,没有难道锁的线程只能等待(对应上面的1,5)

    2.每个实例都对应有自己的一把锁(this),不同实例之间互不影响;例外:锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象公用同一把锁(对应上面的2,3,4,6)

    3.synchronized修饰的方法,无论方法正常执行完毕还是抛出异常,都会释放锁(对应上面的7)

    四、synchronized的性质

    1.可重入性

    概念:指同一个线程外层函数获取到锁之后,内层函数可以直接使用该锁

    好处:避免死锁,提升封装性(如果不可重入,假设method1拿到锁之后,在method1中又调用了method2,如果method2没办法使用method1拿到的锁,那method2将一直等待,但是method1由于未执行完毕,又无法释放锁,就导致了死锁,可重入正好避免这这种情况)

    粒度:线程而非调用(用3中情况来说明与pthread的区别)

    1)情况1:证明同一个方法是可重入的(递归)

    public class SynchronizedDemo2 {
        int a = 0;
    
        public static void main(String[] args) {
            new SynchronizedDemo2().method1();
        }
    
        public synchronized void method1() {
            System.out.println("a=" + a);
            if (a == 0) {
                a++;
                method1();
            }
        }
    }

    输出结果:

      a=0
      a=1

    2)情况2:证明可重入不要求是同一个方法

    public class SynchronizedDemo2 {
        public static void main(String[] args) {
            new SynchronizedDemo2().method1();
        }
    
        public synchronized void method1() {
            System.out.println("method1");
            method2();
        }
    
        public synchronized void method2() {
            System.out.println("method2");
        }
    }

    输出结果:

      method1
      method2

    3)情况3:证明可重入不要求是同一个类中

    public class SynchronizedDemo2 {
        public synchronized void method1() {
            System.out.println("父类method1");
        }
    }
    
    class SubClass extends SynchronizedDemo2 {
        public synchronized void method1() {
            System.out.println("子类method1");
            super.method1();
        }
    
        public static void main(String[] args) {
            new SubClass().method1();
        }
    }

    输出结果:

      子类method1
      父类method1

    2.不可中断性

    概念:如果这个锁被B线程获取,如果A线程想要获取这把锁,只能选择等待或者阻塞,直到B线程释放这把锁,如果B线程一直不释放这把锁,那么A线程将一直等待。

    相比之下,未来的Lock类,可以拥有中断的能力(如果一个线程等待锁的时间太长了,有权利中断当前已经获取锁的线程的执行,也可以退出等待)

    五、深入原理

    1.加锁和释放锁的原理:现象、时机(内置锁this)、深入JVM看字节码(反编译看monitor指令)

    Lock lock = new ReentrantLock();
    
    public synchronized void method1() {
      System.out.println("synchronized method1");
    }
    
    public void method2() {
      lock.lock();
      try {
        System.out.println("lock method2");
      } finally {
        lock.unlock();
      }
    }
    method1与method2等价,
    synchronized相当于先获取锁,执行结束/抛出异常后,释放锁。

    深入JVM看字节码,创建如下的代码:

    public class SynchronizedDemo2 {
        Object object = new Object();
    
        public void method1() {
            synchronized (object) {
    
            }
        }
    }
    使用javac命令进行编译生成.class文件
    
    >javac SynchronizedDemo2.java
    
    使用javap命令反编译查看.class文件的信息
    
    >javap -verbose SynchronizedDemo2.class
    得到如下的信息:

    关注红色方框里的monitorenter和monitorexit即可。

    Monitorenter和Monitorexit指令,会让对象在执行,使其锁计数器加1或者减1。每一个对象在同一时间只与一个monitor(锁)相关联,而一个monitor在同一时间只能被一个线程获得,一个对象在尝试获得与这个对象相关联的Monitor锁的所有权的时候,monitorenter指令会发生如下3中情况之一:

    1)monitor计数器为0,意味着目前还没有被获得,那这个线程就会立刻获得然后把锁计数器+1,一旦+1,别的线程再想获取,就需要等待

    2)如果这个monitor已经拿到了这个锁的所有权,又重入了这把锁,那锁计数器就会累加,变成2,并且随着重入的次数,会一直累加

    3)这把锁已经被别的线程获取了,等待锁释放

    monitorexit指令:释放对于monitor的所有权,释放过程很简单,就是讲monitor的计数器减1,如果减完以后,计数器不是0,则代表刚才是重入进来的,当前线程还继续持有这把锁的所有权,如果计数器变成0,则代表当前线程不再拥有该monitor的所有权,即释放锁。

    2.可重入原理:加锁次数计数器

    jvm会负责跟踪对象被加锁的次数

    线程第一次获得所,计数器+1,当锁重入的时候,计数器会递增

    当任务离开的时候(一个同步代码块的代码执行结束),计数器会减1,当减为0的时候,锁被完全释放。

    3.保证可见性的原理:内存模型

     访问链接 https://www.cnblogs.com/xyabk/p/10894384.html

    六、synchronized的缺陷

    效率低:锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock可以中断和设置超时

    不够灵活:加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活

    无法知道是否成功获得锁,相对而言,Lock可以拿到状态,如果成功获取锁,....,如果获取失败,.....

    七、Lock对synchronized的弥补

    Lock类这里不做过多解释,主要看上面红色方框里面的4个方法

    lock():加锁

    unlock():解锁

    tryLock():尝试获取锁,返回一个boolean值

    tryLock(long,TimeUtil):尝试获取锁,可以设置超时

    八、注意

    1.锁对象不能为空,因为锁的信息都保存在对象头里

    2.作用域不宜过大,影响程序执行的速度,控制范围过大,编写代码也容易出错

    3.避免死锁

    4.在能选择的情况下,既不要用Lock也不要用synchronized关键字,用java.util.concurrent包中的各种各样的类,如果不用该包下的类,在满足业务的情况下,可以使用synchronized关键,因为代码量少,避免出错

    九、思考

    1.多个线程等待同一个snchronized锁的时候,JVM如何选择下一个获取锁的线程?

    2.Synchronized使得同时只有一个线程可以执行,性能比较差,有什么提升的方法?

    3.我想更加灵活地控制锁的释放和获取(现在释放锁和获取锁的时机都被规定死了),怎么办?

    4.什么是锁的升级和降级?什么事JVM里的偏斜锁、轻量级锁、重量级锁?

  • 相关阅读:
    windows的80端口被占用时的处理方法
    Ansible自动化运维工具安装与使用实例
    Tomcat的测试网页换成自己项目首页
    LeetCode 219. Contains Duplicate II
    LeetCode Contest 177
    LeetCode 217. Contains Duplicate
    LeetCode 216. Combination Sum III(DFS)
    LeetCode 215. Kth Largest Element in an Array(排序)
    Contest 176 LeetCode 1354. Construct Target Array With Multiple Sums(优先队列,递推)
    Contest 176
  • 原文地址:https://www.cnblogs.com/xyabk/p/10901291.html
Copyright © 2011-2022 走看看