我的Synchronized为什么失效了?分析如何正确的使用Synchronized关键字

2,005 阅读6分钟

并发编程的核心问题就是分工、互斥、同步。

锁的使用

锁是为了保证共享资源只能同时被一个线程使用而存在,其根本是为了实现互斥,使线程阻塞。大部分锁的失效都是因为没有理解什么叫共同资源导致,拿A家的锁去锁B家的门想要保护C家的东西。

锁到底锁住了什么?

  1. 实例对象。(普通方法、new Object())
  2. class类。(静态方法、静态对象)

锁的范围、类型

范围

  1. 方法锁。
  2. 对象锁。
  3. 类锁。

方法锁

image.png

如上代码其实就是方法锁,字面意思就是方法上的锁,锁一个方法,因为这里是一个普通方法(非静态方法),所以他锁住的就是一个实例对象 test1。

类锁

类锁一般有三种形式。因为在java虚拟机中一个类的静态对象只有一份,所以static修饰的方法就是一种类锁,修饰的变量也是类锁,直接使用class也是类锁。

image.png

对象锁

对象锁,既可以锁一个实例,也可以锁一个类。如下图:

image.png

其实这里主要体现一个粒度范围的概念,方法锁是作用在方法上,范围就是整个方法。对象锁一般通过synchronized(obj) 代码块来使用,范围为代码块的范围。很明显,对象锁会更灵活一些,效率相对也会好一点(如果一个方法仅有几行代码需要加锁,使用方法锁其实就没有什么必要了)。

问题来了

  1. 锁一个实例对象与锁一个类有什么区别?锁一个实例对象与锁一个类会互斥么?
  2. 同一个类中两个方法锁(都是静态或者都是非静态),两个线程使用同一实例访问不同方法会互斥么?
  3. this关键字锁的是实例还是类?
  4. 静态方法A使用方法锁,与非静态方法B使用静态变量对象锁之间互斥么?

解答

锁一个实例对象与锁一个类有什么区别?锁一个实例对象与锁一个类会互斥么?

区别:

synchronized 写在方法上有两种情况。

1、在普通方法上锁 当前实例对象。再新建一个实例去调用就不会有冲突。

2、在静态方法上 表示锁一个类class。此时不管新建多少个对象都冲突。

如下代码:

方法methodS1作为一个非静态方法锁,锁的只是 Synchronized 类的某一个实例,所以在线程 thread1,thread2分别使用不同实例 test1、test2 调用时,是不会互斥的。

但是如果调用静态方法 methodStatic2,则会互斥。

另外,锁一个实例对象(普通方法)与锁一个类(静态方法) 两者并不互斥,thread1使用test1实例调用methodStatic2方法持有class类锁,thread2仍然可以用test1实例调用method1方法成功。可以这么理解:静态方法是多实例共享资源(方法区)。普通方法为单实例资源(堆),两者并不相同(锁的并不是同一个对象),所以不互斥。

public class Synchronized {

    // 方法锁 所的是 某一个 Synchronized 实例
    public synchronized void method1() throws InterruptedException {
        System.out.println("method1");
        Thread.sleep(10000);
    }

    // 类锁 锁的是 Synchronized.class对象
    public static synchronized void methodStatic2() throws InterruptedException {
        System.out.println("methodStatic2");
        Thread.sleep(10000);
    }


    public static void main(String[] args) {
        // 普通方法对于两个对象来讲,没有互斥。因为锁的是各自的实例对象 test1 和 test2 。
        // 静态方法 锁的是整个class 无论哪个对象只要属于一个class都互斥。
        Synchronized test1 = new Synchronized();
        Synchronized test2 = new Synchronized();
        try {
            Thread thread1 = new Thread(() -> {
                try {
                    test1.method1();
                } catch (InterruptedException interruptedException) {
                    interruptedException.printStackTrace();
                }
            });

            Thread thread2 = new Thread(() -> {
                try {
                    test2.method1();
                } catch (InterruptedException interruptedException) {
                    interruptedException.printStackTrace();
                }
            });

            thread1.start();
            Thread.sleep(100);
            thread2.start();
        }catch (Exception ex){

        }
    }
}

答: 锁一个实例对象场景中,使用不同实例访问就不会互斥。比如new两个实例对象Synchronized(); 锁一个类则不一样,即使new两个实例对象来去访问一样会互斥。

同一个类中两个方法锁(都是静态或者都是非静态),两个线程使用同一实例访问不同方法会互斥么?

public class SynchronizedTest1 {
//    public static synchronized void methodStatic1() {
    public synchronized void method1() {
        System.out.println("test1");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("test1 end");
    }

//    public static synchronized void methodStatic2() {
    public synchronized void method2() {
        System.out.println("test1");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("test1 end");
    }

    public static void main(String[] args) {
        SynchronizedTest1 test1 = new SynchronizedTest1();
        new Thread(()->{
            test1.method1();
        }).start();
        new Thread(()->{
            test1.method2();
        }).start();
        new Thread(() -> {
            while (true) {
                System.out.println("------------------");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }
}

答:会互斥。因为对于是否互斥判断而言,主要看锁的对象是否一样。

非静态方法情况下,锁的是实例对象,而两个线程使用的是同一实例对象 test1,所以非静态会互斥。

静态情况下,锁的是类,同一实例对象必然属于同一个类,因此也会互斥。

this关键字锁的是实例还是类?

答:实例。因为this代指当前实例,而不是当前类。在写代码时,SynchronizedTest1.静态属性正常可用,this.静态属性编译报错,而且this在静态方法中无法使用。如下代码 肯定是互斥的。

public class SynchronizedTest1 {
    public void method1() {
        synchronized (this) {
            System.out.println("test1");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public synchronized void method2() {
        System.out.println("test2");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        SynchronizedTest1 test1 = new SynchronizedTest1();
        new Thread(()->{
            test1.method1();
        }).start();
        new Thread(()->{
            test1.method2();
        }).start();
        new Thread(() -> {
            while (true) {
                System.out.println("------------------");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }
}

扩展一下: 静态方法锁,与对象锁是否互斥:

public class SynchronizedTest1 {
    public void method1() {
        synchronized (SynchronizedTest1.class) {
            System.out.println("test1");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static synchronized void method2() {
        System.out.println("test2");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    } 
}

静态方法A使用方法锁,与非静态方法B使用静态变量对象锁之间互斥么?

public class SynchronizedTest1 {

    private static final String LOCK = "LOCK";

    public void method1() {
        synchronized (LOCK) {
            System.out.println("test1");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public synchronized void method2() {
        System.out.println("test2");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        SynchronizedTest1 test1 = new SynchronizedTest1();
        new Thread(()->{
            test1.method1();
        }).start();
        new Thread(()->{
            test1.method2();
        }).start();
        new Thread(() -> {
            while (true) {
                System.out.println("------------------");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }
}

答:不互斥。继续重复一句话,对于是否互斥判断而言,主要看锁的对象是否一样。重点是锁的对象是否一样,锁同一个对象必然互斥,锁不同对象必然不互斥。

静态方法A使用方法锁,锁的对象是Class对象,非静态方法B锁的是静态变量对象,虽然从类型上来讲都是属于类锁,但是不是同一对象,等价于锁了另外一个Class,所以肯定不互斥。