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();
    }

运行结果:

扫描二维码关注公众号,回复: 11191842 查看本文章
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到底锁的是什么?对于普通的方法,synchronized锁的是当前调用的实例对象,例如test.method1(),可以理解成这样,synchronized(test){},而对于静态方法synchronized,锁的则是当前类的Class对象,并且这个对象只有一个,所以锁也是只有一把,所有的实例对象访问的这个同步方法,实际上读的都是当前这个类的类对象。正是基于这个锁的概念,因此synchronized可以实现上面所说的内存可见性和互斥性作用,从而实现线程安全。但是synchronized毕竟还是一个重量级的锁,性能比较低,因为synchronized是互斥的,所以在切换线程的时候,线程上下文切换会引起大量的性能开销。也正是因为这个性能原因饱受诟病,因此后面有了锁的升级过程。这个问题后面再讲

猜你喜欢

转载自www.cnblogs.com/blackmlik/p/12889414.html