zoukankan      html  css  js  c++  java
  • synchronized锁住的到底是什么以及用法作用

    前言:现在网上很多文章讲synchronized的锁这个锁那个,让人很是迷糊,那么synchronized锁住的到底是什么呢?

    作用

    synchronized主要可以用来解决以下几个问题:

    • 解决变量内存可见性问题:保证共享变量的修改的可以及时的刷新到主存中。实现方式为:被synchronized修饰的方法或者代码块中是用到的所有变量。都不会从当前线程本地中获取,而是直接从主存读,另外在退出synchronized修饰的方法或者代码块之后,就会把变化刷新到主存中。这种方式就可以解决,变量的内存可见性问题。
    • 互斥问题:确保线程互斥的访问同步代码,被synchronized修饰的代码和方法同时只允许一个线程访问。锁住了当前的对象

    用法

    一般来说,synchronized有三种用法,分别是:

    • 普通方法
    • 静态方法
    • 代码块

    在说明这三种用法之前,要先说一个概念,就是synchronized锁住的是对象!!!对于普通的方法,锁住的是当前对象实例的对象。对于静态方法,因为静态方法是和类的Class相关联的,因此锁住的是当前类的Class对象。下面代码中,所有的运行结果都是基于这个概念

    不加锁时

    public  class SynchronizedTest1 {
    
        private static   int value = 0;
        public    void method1(){
            System.out.println(Thread.currentThread().getName() +"---Method 1 start");
            try {
                value++;
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() +"---Method 1 execute---value:"+value);
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +"---Method 1 end");
        }
    
        public   void method2(){
            System.out.println(Thread.currentThread().getName() +"---Method 2 start");
            try {
                value++;
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() +"---Method 2 execute---value:"+value);
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +"---Method 2 end");
        }
    
        public static void main(String[] args) {
            final SynchronizedTest1 test = new SynchronizedTest1();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method2();
                }
            }).start();
        }
    }
    
    
    

    运行结果

    Thread-0---Method 1 start
    Thread-1---Method 2 start
    Thread-1---Method 2 execute---value:2
    Thread-1---Method 2 end
    Thread-0---Method 1 execute---value:2
    Thread-0---Method 1 end
    

    可以看到不加锁的时候,没有采取任何的同步措施,结果是线程之间抢占式的运行。没有线程安全性可言。线程0在休眠的时候还没执行完就被线程1给抢占了。第一次递增的时候,value的值应当是打印1,但是因为此时线程休眠,被其他线程抢了,然后再把value递增了一次,因此,两次的value都变成了2。

    对方法进行加锁

    单对象双同步方法

    /**
     * 对方法进行加锁
     */
    public class SynchronizedTest2 {
    
        private static   int value = 0;
        public synchronized void method1(){
            System.out.println(Thread.currentThread().getName() +"---Method 1 start");
            try {
                value++;
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() +"---Method 1 execute---value:"+value);
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +"---Method 1 end");
        }
    
        public  synchronized   void method2(){
            System.out.println(Thread.currentThread().getName() +"---Method 2 start");
            try {
                value++;
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() +"---Method 2 execute---value:"+value);
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +"---Method 2 end");
        }
    
        public static void main(String[] args) {
            final SynchronizedTest2 test = new SynchronizedTest2();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method2();
                }
            }).start();
        }
    }
    
    

    运行结果:

    Thread-0---Method 1 start
    Thread-0---Method 1 execute---value:1
    Thread-0---Method 1 end
    Thread-1---Method 2 start
    Thread-1---Method 2 execute---value:2
    Thread-1---Method 2 end
    

    可以看到,上述两个线程都是严格按照顺序来执行的,即便是method1方法休眠了3秒,method2也不能获取执行权,因为被锁住的是test这个对象,并且是通过同一个对象去调用的,所以调用之前都需要先去竞争同一个对象上的锁(monitor),也就只能互斥的获取到锁,因此,method1和method2只能顺序的执行。必须等method1执行完毕之后,被锁住的test对象才会被释放,给method2执行,synchronized保证了在方法执行完毕之前,test实例对象中只会有有一个线程执行对象中的方法,因此value的值可以正常的递增,保证了线程安全性。

    双对象单个同步方法

    下面通过一个例子来说明,synchronized锁的是当前实例的对象,而对另一个实例的对象毫无影响,因为根本不是同一把锁。

    我们把主函数的代码改成如下这样,我们new了两个对象,并且让这两个线程分别访问这两个对象的method1方法,如果synchronized锁的不是实例对象的话,会严格按照执行顺序来执行代码,线程1执行完method1之后,线程2才会再执行method1,然后两次打印的值分别是1和2。但是结果真的是这样吗?

     public static void main(String[] args) {
            final SynchronizedTest2 test = new SynchronizedTest2();
            final SynchronizedTest2 test2 = new SynchronizedTest2();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test2.method1();
                }
            }).start();
        }
    

    运行结果:

    Thread-0---Method 1 start
    Thread-1---Method 1 start
    Thread-0---Method 1 execute---value:2
    Thread-0---Method 1 end
    Thread-1---Method 1 execute---value:2
    Thread-1---Method 1 end
    

    注意看线程的编号,执行过程是这样的,首先线程1执行test对象中的method1,线程1休眠的时候,线程2获得执行权。执行test2对象中的method1。为什么可以执行method1呢?因为这是两把不同的锁,synchronized锁的是不同的对象,不存在访问时候互斥的问题,各玩各的,所以根本不会有影响。因此,线程1休眠的时候,线程2获取执行权,自然可以去执行test2对象的method方法了。这也印证了synchronized锁的是对象。

    单对象同步普通方法

    如果是这种情况的话,一个方法有synchronized一个是普通方法,那么synchronized方法被线程1执行,普通方法被线程2执行,相互之间不会有影响,因为方法2没有加锁,方法1需要读对象的锁,而方法2不用所以可以直接执行,

    public class SynchronizedTest4 {
    
    private int value = 0;
        public synchronized void method1(){
            System.out.println(Thread.currentThread().getName() +"---Method 1 start");
            try {
                value++;
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() +"---Method 1 execute---value:"+value);
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +"---Method 1 end");
        }
    
        public   void method2(){
            System.out.println(Thread.currentThread().getName() +"---Method 2 start");
            try {
                value++;
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() +"---Method 2 execute---value:"+value);
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +"---Method 2 end");
        }
    
        public static void main(String[] args) {
            final SynchronizedTest4 test = new SynchronizedTest4();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
    
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method2();
                }
            }).start();
        }
    }
    

    运行结果

    Thread-0---Method 1 start
    Thread-1---Method 2 start
    Thread-1---Method 2 execute---value:2
    Thread-0---Method 1 execute---value:2
    Thread-1---Method 2 end
    Thread-0---Method 1 end
    

    用图解释:

    对静态方法加锁

    在谈对静态方法进行加锁之前,先要回顾一下一个概念, 被static修饰的成员变量和成员方法独立于该类的任何实例对象。也就是说,它不依赖类特定的实例,被类的所有实例共享。因此,static所属的对象是该类的Class类对象,一个类只会有一个Class类对象。详细的可以看我这篇文章

    所以对于被synchronized修饰的静态方法,它锁住的对象就是这个Class对象,而且这把锁只有一个,意味着不管是多少个线程,new了多少个实例对象, 访问的都是同一把锁。看代码

    两个同步静态

    public class SynchronizedTest5 {
    
        private static int value = 0;
        public synchronized static void method1(){
            System.out.println(Thread.currentThread().getName() +"---Method 1 start");
            try {
                value++;
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() +"---Method 1 execute---value:"+value);
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +"---Method 1 end");
        }
    
        public synchronized  static   void method2(){
            System.out.println(Thread.currentThread().getName() +"---Method 2 start");
            try {
                value++;
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() +"---Method 2 execute---value:"+value);
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +"---Method 2 end");
        }
    
        public static void main(String[] args) {
            final SynchronizedTest5 test = new SynchronizedTest5();
            final SynchronizedTest5 test2 = new SynchronizedTest5();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
    
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test2.method2();
                }
            }).start();
        }
    }
    
    

    运行结果

    Thread-0---Method 1 start
    Thread-0---Method 1 execute---value:1
    Thread-0---Method 1 end
    Thread-1---Method 2 start
    Thread-1---Method 2 execute---value:2
    Thread-1---Method 2 end
    

    上述的代码,new了两个实例对象,每个对象访问的是不同的方法。如果按照之前的思维来看,这两个线程的锁应当是不关联的,在线程1休眠的时候,线程2应当会获得cpu的执行权。但是事实却是还是要等线程1执行完毕才会执行线程2,并且可以看到,value的值也是线程安全的递增,因此可以验证,对于静态方法,调用的时候需要获取同一个类上锁(由于每个类只对应一个class对象),锁住的对象是类的Class对象。因此只能顺序执行。

    非静态同步

    public class SynchronizedTest5 {
    
        private static int value = 0;
        public synchronized static void method1(){
            System.out.println(Thread.currentThread().getName() +"---Method 1 start");
            try {
                value++;
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() +"---Method 1 execute---value:"+value);
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +"---Method 1 end");
        }
    
        public    synchronized    void method2(){
            System.out.println(Thread.currentThread().getName() +"---Method 2 start");
            try {
                value++;
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() +"---Method 2 execute---value:"+value);
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +"---Method 2 end");
        }
    
        public static void main(String[] args) {
            final SynchronizedTest5 test = new SynchronizedTest5();
    //        final SynchronizedTest5 test2 = new SynchronizedTest5();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
    
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method2();
                }
            }).start();
        }
    }
    

    运行结果:

    Thread-0---Method 1 start
    Thread-1---Method 2 start
    Thread-0---Method 1 execute---value:2
    Thread-0---Method 1 end
    Thread-1---Method 2 execute---value:2
    Thread-1---Method 2 end
    

    由上面的代码可以看到,method1休眠的时候,method2拿到了执行权,可以继续执行,而不是让method1一直阻塞下去,这是因为这是两把锁,method1是静态方法,是属于SynchronizedTest5.class对象的锁,而method2方法也加了synchronized,但是是属于实例对象test的锁,不会互相影响,因此也是各玩各的,看图理解

    对代码块加锁

    public class SynchronizedTest6 {
    
        private static int value = 0;
    
        public  void method1(){
            System.out.println(Thread.currentThread().getName() +"---Method 1 start");
            try {
                synchronized (this){
                    value++;
                    Thread.sleep(3000);
                    System.out.println(Thread.currentThread().getName() +"---Method 1 execute---value:"+value);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +"---Method 1 end");
        }
    
        public  void method2(){
            System.out.println(Thread.currentThread().getName() +"---Method 2 start");
            try {
                synchronized (this){
                    value++;
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() +"---Method 2 execute---value:"+value);
                }
    
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +"---Method 2 end");
        }
    
        public static void main(String[] args) {
            final SynchronizedTest6 test = new SynchronizedTest6();
    //        final SynchronizedTest5 test2 = new SynchronizedTest5();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method1();
    
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.method2();
                }
            }).start();
        }
    }
    

    运行结果:

    Thread-0---Method 1 start
    Thread-1---Method 2 start
    Thread-0---Method 1 execute---value:1
    Thread-0---Method 1 end
    Thread-1---Method 2 execute---value:2
    Thread-1---Method 2 end
    

    和synchronized加在方法上差不多的意思,需要线程1先把代码执行完毕,释放锁,线程2才能进入代码块,因为锁的也是当前的实例对象,其他的情况,按照上面所说的都可以总结出来

    synchronized是可重入锁

    为什么么synchronized是可重入锁,理由很简单,因为他必须是可重入锁。我们假设这样一种情况,子类继承了父类,然后重写了父类的方法,在我们平时的开发中,通过super.xxx()调用父类的方法是一件很常见的需求,如果synchronized不可重入,那么调用super.xxx的时候,就会出错,这从设计上来说就不合理。那么这个时候其实问题也来了,synchronized的对于子父类,锁的又是谁呢?看代码

    class SynchronizedTest8{
        public synchronized void method1() {
            System.out.println(this.toString());
        }
    }
    public class SynchronizedTest7 extends SynchronizedTest8{
        @Override
        public synchronized void method1() {
            System.out.println(super.toString());
            System.out.println(this.toString());
            super.method1();
        }
        public static void main(String[] args) {
            SynchronizedTest7 test7 = new SynchronizedTest7();
            System.out.println(test7.toString());
            test7.method1();
        }
    }
    

    运行结果

    com.black.synchronize.SynchronizedTest7@4554617c
    com.black.synchronize.SynchronizedTest7@4554617c
    com.black.synchronize.SynchronizedTest7@4554617c
    com.black.synchronize.SynchronizedTest7@4554617c
    

    看后面的地址就知道,这四个都是同一个对象,super和this是同一个引用,而且父类的this也是。并且都是当前的这个子类的对象,所以子类调用父类synchronized方法,也是对子类对象进行上锁,所以才会说锁住的是同一个对象,这也很好理解,因为方法调用是在子类发起的,所以锁子类的对象,而父类都没有实例对象,因此当然是锁子类的对象了。也就是super本身仍然是子类的引用,只不过它可以调用到父类的方法或变量。另外经实验发现,即便子类重写的方法不加synchronized,也是可以调用父类的synchronized方法,理由也很简单,因为都是一个对象锁,同一把锁,只是过程不一样而已,一开始调用子类方法的时候,发现不需要同步也就不用加锁,调用父类synchronized方法的时候,发现要同步,这时把锁给加上就行了。也就满足了可重入锁的条件了。代码就不贴了,只需要把子类的synchronized关键字去了,运行结果也是一样的

    总结:

    通过以上大量的代码演示,可以知道,synchronized的一些常见用法,然后可以推出synchronized到底锁的是什么?对于普通的方法,synchronized锁的是当前调用的实例对象,例如test.method1(),可以理解成这样,synchronized(test){},而对于静态方法synchronized,锁的则是当前类的Class对象,并且这个对象只有一个,所以锁也是只有一把,所有的实例对象访问的这个同步方法,实际上读的都是当前这个类的类对象。正是基于这个锁的概念,因此synchronized可以实现上面所说的内存可见性和互斥性作用,从而实现线程安全。但是synchronized毕竟还是一个重量级的锁,性能比较低,因为synchronized是互斥的,所以在切换线程的时候,线程上下文切换会引起大量的性能开销。也正是因为这个性能原因饱受诟病,因此后面有了锁的升级过程。这个问题后面再讲

  • 相关阅读:
    [C语言
    [C语言
    [C语言
    [C语言
    [C语言
    [C语言
    [iOS]超详细Apache服务器的配置(10.10系统)
    IOS优秀博客
    「C」 数组、字符串、指针
    103.Binary Tree Zigzag Level Order Traversal(层序遍历)
  • 原文地址:https://www.cnblogs.com/blackmlik/p/12889414.html
Copyright © 2011-2022 走看看