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是互斥的,所以在切换线程的时候,线程上下文切换会引起大量的性能开销。也正是因为这个性能原因饱受诟病,因此后面有了锁的升级过程。这个问题后面再讲

  • 相关阅读:
    mysql数据库常用指令
    解决windows的mysql无法启动 服务没有报告任何错误的经验。
    “Can't open file for writing”或“operation not permitted”的解决办法
    启动Apache出现错误Port 80 in use by "Unable to open process" with PID 4!
    如何打开windows的服务services.msc
    常见的HTTP状态码 404 500 301 200
    linux系统常用的重启、关机指令
    (wifi)wifi移植之命令行调试driver和supplicant
    linux(debian)安装USB无线网卡(tp-link TL-WN725N rtl8188eu )
    alloc_chrdev_region申请一个动态主设备号,并申请一系列次设备号
  • 原文地址:https://www.cnblogs.com/blackmlik/p/12889414.html
Copyright © 2011-2022 走看看