Java之Synchronized小结

目录

1. Synchronized简介

2. Synchronized的两种用法(对象锁和类锁)

2.1对象锁

2.1.1 代码块形式

2.1.2 方法锁形式

2.2 类锁

2.2.1 static形式

2.2.2 static形式

3. 多线程访问同步方法的7种情况

3.1 两个线程同时访问一个对象的同步方法

3.2 两个线程访问的是两个对象的同步方法

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

3.4 同时访问同步方法与非同步方法

3.5 访问同一个类的不同的普通同步方法

3.6 同时访问静态的synchronized和非静态synchronized方法

3.7 方法抛出异常后,会释放锁吗

3.8 7种情况总结

4. synchronized的性质

4.1 可重入

4.2 证明同一个方法是可重入的

4.3 证明可重入不要求是同一个方法

4.4 证明可重入不要求是同一个类中的

4.5 加锁和释放锁的原理

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

4.7 可见性原理:Java内存模型 

5. synchronized的缺陷

6. synchronized常见面试题

7. 总结


1.Synchronized简介

Synchronized的作用能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发的安全的效果。

Synchronized被Java原生支持,是最基本的互斥同步手段。

 

2.Synchronized的两种用法(对象锁和类锁)

2.1对象锁

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

对象锁分为两种形式:

  • 代码块形式:手动指定锁对象;
  • 方法锁形式:Synchronized修饰普通方法,锁对象默认为this。

2.1.1 代码块形式

/**
 * 对象锁示例1:代码块形式
 * */
public class SynchronizedObjectCodeBlock2 implements Runnable{

   static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();

    @Override
    public void run() {
        synchronized (this){
            System.out.println("我是对象锁的代码块形式,我叫"+ Thread.currentThread().getName());
            try {
                /** 休眠3秒 */
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"运行结束");
        }
    }


    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println("finished");
    }
}

输出:

我是对象锁的代码块形式,我叫Thread-0
Thread-0运行结束
我是对象锁的代码块形式,我叫Thread-1
Thread-1运行结束
finished

 注释 synchronized (this){ }

/**
 * 对象锁示例1:代码块形式
 * */
public class SynchronizedObjectCodeBlock2 implements Runnable{

   static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();

    @Override
    public void run() {
       // synchronized (this){
            System.out.println("我是对象锁的代码块形式,我叫"+ Thread.currentThread().getName());
            try {
                /** 休眠3秒 */
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"运行结束");
        }
    //}


    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println("finished");
    }
}

输出:

我是对象锁的代码块形式,我叫Thread-1
我是对象锁的代码块形式,我叫Thread-0
Thread-1运行结束
Thread-0运行结束
finished

 则同时运行和结束。

 

public class SynchronizedObjectCodeBlock3 implements Runnable{

    static SynchronizedObjectCodeBlock3 instance = new SynchronizedObjectCodeBlock3();
    Object lock = new Object();

    @Override
    public void run() {
        synchronized (lock){
            System.out.println("我是对象锁的代码块形式,我叫"+ Thread.currentThread().getName());
            try {
                /** 休眠3秒 */
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"运行结束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println("finished");
    }
}

输出:

我是对象锁的代码块形式,我叫Thread-0
Thread-0运行结束
我是对象锁的代码块形式,我叫Thread-1
Thread-1运行结束
finished

修改代码,定义两个锁对象

public class SynchronizedObjectCodeBlock3 implements Runnable{

    static SynchronizedObjectCodeBlock3 instance = new SynchronizedObjectCodeBlock3();
    Object lock1 = new Object();
    Object lock2 = new Object();
    
    @Override
    public void run() {
        synchronized (lock1){
            System.out.println("我是lock1,我叫"+ Thread.currentThread().getName());
            try {
                /** 休眠3秒 */
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"lock1运行结束");
        }

        synchronized (lock2){
            System.out.println("我是lock2,我叫"+ Thread.currentThread().getName());
            try {
                /** 休眠3秒 */
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"lock2运行结束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println("finished");
    }
}

输出:

我是lock1,我叫Thread-1
Thread-1lock1运行结束
我是lock2,我叫Thread-1
我是lock1,我叫Thread-0
Thread-1lock2运行结束
Thread-0lock1运行结束
我是lock2,我叫Thread-0
Thread-0lock2运行结束
finished

 因为用的是不同的锁,

我是lock2,我叫Thread-1
我是lock1,我叫Thread-0

这两句是同时打印出来的,是并行执行。

如果将lock2改为lock1:

public class SynchronizedObjectCodeBlock3 implements Runnable{

    static SynchronizedObjectCodeBlock3 instance = new SynchronizedObjectCodeBlock3();
    Object lock1 = new Object();
    Object lock2 = new Object();

    @Override
    public void run() {
        synchronized (lock1){
            System.out.println("我是lock1,我叫"+ Thread.currentThread().getName());
            try {
                /** 休眠3秒 */
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"lock1运行结束");
        }

        synchronized (lock1){
            System.out.println("我是lock2,我叫"+ Thread.currentThread().getName());
            try {
                /** 休眠3秒 */
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"lock2运行结束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println("finished");
    }
}

输出:

我是lock1,我叫Thread-0
Thread-0lock1运行结束
我是lock2,我叫Thread-0
Thread-0lock2运行结束
我是lock1,我叫Thread-1
Thread-1lock1运行结束
我是lock2,我叫Thread-1
Thread-1lock2运行结束
finished

用同一把锁则是串行执行。

小结:

以上代码使用synchronized的对象锁方式,使用了this或者自定义一把锁。

 

2.1.2 方法锁形式

使用synchronized修饰方法,新建类SynchronizedObjectMethod3.class

public class SynchronizedObjectMethod3 implements Runnable{

    static SynchronizedObjectMethod3 instance1 = new SynchronizedObjectMethod3();

    @Override
    public void run() {
        method();
    }

    public synchronized void method(){
        System.out.println("我是对象锁的方法修饰形式,我叫"+Thread.currentThread().getName());
        try {
            /** 休眠3秒 */
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"运行结束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance1);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println("finished");
    }
}

输出:

我是对象锁的方法修饰形式,我叫Thread-0
Thread-0运行结束
我是对象锁的方法修饰形式,我叫Thread-1
Thread-1运行结束
finished

由于使用synchronized修饰了method()方法,所以即使开了两个线程,也只是一个一个地执行。

 

2.2 类锁

Java类可能有很多个对象,但只有1个Class对象。

类锁:指Synchronized修饰静态的方法或指定为Class对象。类锁的本质就是,Class对象的锁。

  • 形式1:synchronized 加在 static 方法上;
  • 形式2:synchronized(*.class)代码块。

使用类锁之后,类锁只能在同一时刻被一个对象拥有。

 

2.2.1 static形式

新建 SynchronizedClassStatic4.class,不在method()前面加上static:

/**类锁的第一种形式:static形式  */
public class SynchronizedClassStatic4 implements Runnable{
    static SynchronizedClassStatic4 instanceA = new SynchronizedClassStatic4();
    static SynchronizedClassStatic4 instanceB = new SynchronizedClassStatic4();

    @Override
    public void run() {
        method();
    }

    public synchronized void method(){
        System.out.println("我是类锁的第一种形式,我叫"+Thread.currentThread().getName());
        try {
            /** 休眠3秒 */
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"运行结束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instanceA);
        Thread t2 = new Thread(instanceB);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println("finished");
    }
}

输出结果:

我是类锁的第一种形式,我叫Thread-0
我是类锁的第一种形式,我叫Thread-1
Thread-0运行结束
Thread-1运行结束
finished

两个线程几乎同时开始,同时结束,意味着它们没有做到同步,它们是并行执行。

再在method()前面加上static:

/**类锁的第一种形式:static形式  */
public class SynchronizedClassStatic4 implements Runnable{
    static SynchronizedClassStatic4 instanceA = new SynchronizedClassStatic4();
    static SynchronizedClassStatic4 instanceB = new SynchronizedClassStatic4();

    @Override
    public void run() {
        method();
    }

    public static synchronized void method(){
        System.out.println("我是类锁的第一种形式,我叫"+Thread.currentThread().getName());
        try {
            /** 休眠3秒 */
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"运行结束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instanceA);
        Thread t2 = new Thread(instanceB);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println("finished");
    }
}

 输出:

我是类锁的第一种形式,我叫Thread-0
Thread-0运行结束
我是类锁的第一种形式,我叫Thread-1
Thread-1运行结束
finished

小结:

当遇到需要全局同步方法的情况,就需要public static synchronized void method() 这样的形式进行同步保护。

 

2.2.2 static形式

新建SynchronizedClassClass5.class :

/** 类锁的第二种形式: */
public class SynchronizedClassClass5 implements Runnable{

    static SynchronizedClassClass5 instanceC = new SynchronizedClassClass5();
    static SynchronizedClassClass5 instanceD = new SynchronizedClassClass5();

    @Override
    public void run() {
        method();
    }

    private void method(){
        synchronized (SynchronizedClassClass5.class){
            System.out.println("我是类锁的第二种形式: synchronized(*.class),我叫"+Thread.currentThread().getName());
            try {
                /** 休眠3秒 */
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"运行结束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instanceC);
        Thread t2 = new Thread(instanceD);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println("finished");
    }
}

输出:

我是类锁的第二种形式: synchronized(*.class),我叫Thread-0
Thread-0运行结束
我是类锁的第二种形式: synchronized(*.class),我叫Thread-1
Thread-1运行结束
finished

小结:

无论run()跑的是哪一个实例,只要同步代码块中 synchronized (SynchronizedClassClass5.class) ,也就说大家共用SynchronizedClassClass5.class这个对象,即便是不同的实例,也要一个一个按顺序地进行串行访问。

 

3. 多线程访问同步方法的7种情况

1)两个线程同时访问一个对象的同步方法;

2)两个线程访问的是两个对象的同步方法;

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

4)同时访问同步方法非同步方法

5)访问同一个类的不同的普通同步方法;

6)同时访问静态的synchronized和非静态synchronized方法;

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

 

3.1 两个线程同时访问一个对象的同步方法

对应前一节 SynchronizedObjectMethod3.class ,同一个实例的两个不同线程。

public class SynchronizedObjectMethod3 implements Runnable{

    static SynchronizedObjectMethod3 instance1 = new SynchronizedObjectMethod3();

    @Override
    public void run() {
        method();
    }

    public synchronized void method(){
        System.out.println("我是对象锁的方法修饰形式,我叫"+Thread.currentThread().getName());
        try {
            /** 休眠3秒 */
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"运行结束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance1);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println("finished");
    }
}

输出:

我是对象锁的方法修饰形式,我叫Thread-0
Thread-0运行结束
我是对象锁的方法修饰形式,我叫Thread-1
Thread-1运行结束
finished

小结:

两个线程同时访问一个对象的同步方法,在争抢同一把锁,必然某一时刻只有一个人拥有,其他人等待,串行执行。

 

3.2 两个线程访问的是两个对象的同步方法

对应上一节 SynchronizedObjectCodeBlock2.class 的改进版,两个不同的实例:

public class SynchronizedObjectCodeBlock2 implements Runnable{

   static SynchronizedObjectCodeBlock2 instance01 = new SynchronizedObjectCodeBlock2();
   static SynchronizedObjectCodeBlock2 instance02 = new SynchronizedObjectCodeBlock2();

    @Override
    public void run() {
        synchronized (this){
            System.out.println("我是对象锁的代码块形式,我叫"+ Thread.currentThread().getName());
            try {
                /** 休眠3秒 */
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"运行结束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance01);
        Thread t2 = new Thread(instance02);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println("finished");
    }
}

输出:

我是对象锁的代码块形式,我叫Thread-1
我是对象锁的代码块形式,我叫Thread-0
Thread-1运行结束
Thread-0运行结束
finished

小结:

当两个线程访问的是两个对象的同步方法,这时 synchronized 方法不起作用,和普通方法一样,因为锁的是不同的实例,所以同时开始同时结束,并行执行。

 

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

对应 SynchronizedClassStatic4.class ,虽然有两个不同实例,但是 method() 是静态的,它们对应是锁就是同一把。

/**类锁的第一种形式:static形式  */
public class SynchronizedClassStatic4 implements Runnable{
    static SynchronizedClassStatic4 instanceA = new SynchronizedClassStatic4();
    static SynchronizedClassStatic4 instanceB = new SynchronizedClassStatic4();

    @Override
    public void run() {
        method();
    }

    public static synchronized void method(){
        System.out.println("我是类锁的第一种形式,我叫"+Thread.currentThread().getName());
        try {
            /** 休眠3秒 */
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"运行结束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instanceA);
        Thread t2 = new Thread(instanceB);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println("finished");
    }
}

输出:

我是类锁的第一种形式,我叫Thread-0
Thread-0运行结束
我是类锁的第一种形式,我叫Thread-1
Thread-1运行结束
finished

小结:

两个线程访问的是synchronized的静态方法,锁生效,一个一个地执行。

 

3.4 同时访问同步方法非同步方法

新建SynchronizedYesAndNo6.class :

/** 同时访问同步方法和非同步方法 */
public class SynchronizedYesAndNo6 implements Runnable{
    static SynchronizedYesAndNo6 instance6 = new SynchronizedYesAndNo6();

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")){
            method1();
        }else {
            method2();
        }
    }
    public synchronized void method1(){
        System.out.println("我是加锁的方法1。我叫"+Thread.currentThread().getName());
        try {
            /** 休眠3秒 */
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"运行结束");
    }

    public void method2(){
        System.out.println("我是不加锁的方法2。我叫"+Thread.currentThread().getName());
        try {
            /** 休眠3秒 */
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"运行结束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance6);
        Thread t2 = new Thread(instance6);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println("finished");
    }
}

输出 

我是不加锁的方法2。我叫Thread-1
我是加锁的方法1。我叫Thread-0
Thread-0运行结束
Thread-1运行结束
finished

小结:

同时访问同步方法非同步方法,则非同步方法不受影响。线程1和0几乎同时开始,同时结束 。说明synchronized只作用与它修饰的,其他没用它修饰则根本不受影响。

 

3.5 访问同一个类的不同的普通同步方法

新建  SynchronizedDifferentMethod7.class

/** 访问同一个类的不同的普通同步方法 */
public class SynchronizedDifferentMethod7 implements Runnable{

    static SynchronizedDifferentMethod7 instance7 = new SynchronizedDifferentMethod7();

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")){
            method1();
        }else {
            method2();
        }
    }
    public synchronized void method1(){
        System.out.println("我是加锁的方法1。我叫"+Thread.currentThread().getName());
        try {
            /** 休眠3秒 */
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"运行结束");
    }

    public synchronized void method2(){
        System.out.println("我是加锁的方法2。我叫"+Thread.currentThread().getName());
        try {
            /** 休眠3秒 */
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"运行结束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance7);
        Thread t2 = new Thread(instance7);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println("finished");
    }
}

 输出:

我是加锁的方法1。我叫Thread-0
Thread-0运行结束
我是加锁的方法2。我叫Thread-1
Thread-1运行结束
finished

小结:

访问同一个类的不同的普通同步方法,默认指定了this作为同一把锁。对于同一个实例来讲,method1()和method2()拿到的实例是一样的,没法同时运行,所以是串行执行。

 

3.6 同时访问静态的synchronized和非静态synchronized方法

新建 SynchronizedStaticAndNormal8.class 类:

/** 同时访问静态的synchronized和非静态synchronized方法 */
public class SynchronizedStaticAndNormal8 implements Runnable{

    static SynchronizedStaticAndNormal8 instance8 = new SynchronizedStaticAndNormal8();

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")){
            method1();
        }else {
            method2();
        }
    }
    public synchronized static void method1(){
        System.out.println("我是静态加锁的方法1。我叫"+Thread.currentThread().getName());
        try {
            /** 休眠3秒 */
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"运行结束");
    }

    public synchronized void method2(){
        System.out.println("我是非静态加锁的方法2。我叫"+Thread.currentThread().getName());
        try {
            /** 休眠3秒 */
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"运行结束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance8);
        Thread t2 = new Thread(instance8);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println("finished");
    }
}

输出:

我是静态加锁的方法1。我叫Thread-0
我是非静态加锁的方法2。我叫Thread-1
Thread-0运行结束
Thread-1运行结束
finished

小结:

method()方法,有static修饰时,锁的对象是.class对象,没有static修饰时,锁的对象是这个对象的实例本身this,两把锁不一样,本身没有冲突。这里输出结果几乎同时开始同时结束。

 

3.7 方法抛出异常后,会释放锁吗

展示不抛出异常前和抛出异常后的对比:一旦抛出异常,第二个线程会立刻进入同步方法,意味着锁已经释放。

新建 SynchronizedException9.class :

/** 方法抛出异常后,会释放锁吗 */
public class SynchronizedException9 implements Runnable{

    static SynchronizedException9 instance9 = new SynchronizedException9();

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("Thread-0")){
            method1();
        }else {
            method2();
        }
    }
    public synchronized void method1(){
        System.out.println("我是静态加锁的方法1。我叫"+Thread.currentThread().getName());
        try {
            /** 休眠3秒 */
            Thread.sleep(3000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        throw new RuntimeException();
        //System.out.println(Thread.currentThread().getName()+"运行结束");
    }

    public synchronized void method2(){
        System.out.println("我是非静态加锁的方法2。我叫"+Thread.currentThread().getName());
        try {
            /** 休眠3秒 */
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"运行结束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instance9);
        Thread t2 = new Thread(instance9);
        t1.start();
        t2.start();
        while (t1.isAlive() || t2.isAlive()){

        }
        System.out.println("finished");
    }
}

输出:

我是静态加锁的方法1。我叫Thread-0
Exception in thread "Thread-0" 我是非静态加锁的方法2。我叫Thread-1
java.lang.RuntimeException
	at SynchronizedException9.method1(SynchronizedException9.java:24)
	at SynchronizedException9.run(SynchronizedException9.java:10)
	at java.lang.Thread.run(Thread.java:748)
Thread-1运行结束
finished

小结:

方法抛出异常后,由JVM帮忙释放这把锁,才能让下一个想要获得这把锁的线程拿到。

 

3.8 7种情况总结

3点核心思想:

  • 一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应第1、5种情况);
  • 每个实例都对应有自己的一把锁,不同实例之间互不影响;例外:锁对象是*.class 以及 synchronized 修饰的是static 方法的时候,所有对象共用同一把类锁(对应第2、3、4、6种情况);
  • 无论是方法正常执行完毕或者方法抛出异常,都会释放锁(对应第7种情况)。

 

4. synchronized的性质

4.1 可重入

什么是可重入:指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁。

好处:避免死锁,提升封装性。

粒度:

  • 证明同一个方法是可重入的;
  • 证明可重入不要求是同一个方法;
  • 证明可重入不要求是同一个类中的。

 

4.2 证明同一个方法是可重入的

新建SynchronizedRecursion10.class :

/** 可重入粒度测试:递归调用本方法 */
public class SynchronizedRecursion10 {

    int a = 0;

    private synchronized void method1(){
        System.out.println("这是method1,a = " + a);
        if (a==0){
            a++;
            method1();
        }
    }

    public static void main(String[] args) {
        SynchronizedRecursion10 synchronizedRecursion10 = new SynchronizedRecursion10();
        synchronizedRecursion10.method1();
    }
}

输出:

这是method1,a = 0
这是method1,a = 1

 

4.3 证明可重入不要求是同一个方法

新建 SynchronizedOtherMethod11.class :

/** 可重入粒度测试:调用类内另外的方法 */
public class SynchronizedOtherMethod11 {

    public synchronized void method1(){
        System.out.println("我是method1");
        method2();
    }

    public synchronized void method2(){
        System.out.println("我是method2");
    }

    public static void main(String[] args) {
        SynchronizedOtherMethod11 s = new SynchronizedOtherMethod11();
        s.method1();
    }
}

输出:

我是method1
我是method2

 

4.4 证明可重入不要求是同一个类中的

新建 SynchronizedSuperClass12.class :

/** 证明可重入不要求是同一个类中的 */
public class SynchronizedSuperClass12 {
    public synchronized void doSomething(){
        System.out.println("我是父类方法");
    }
}

class TestClass extends SynchronizedSuperClass12{
    public synchronized void doSomething(){
        System.out.println("我是子类方法");
        super.doSomething();
    }

    public static void main(String[] args) {
        TestClass s = new TestClass();
        s.doSomething();
    }
}

输出:

我是子类方法
我是父类方法

 

4.5 加锁和释放锁的原理

新建 SynchronizedToLock13.class :

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SynchronizedToLock13 {
    Lock lock = new ReentrantLock();

    public synchronized void method1(){
        System.out.println("我是Synchronized形式的锁");
    }

    public void method2(){
        lock.lock(); //调用锁,锁住
        try{
            System.out.println("我是lock形式的锁");
        }finally {
            lock.unlock(); //释放锁
            System.out.println("锁已释放");
        }
    }

    public static void main(String[] args) {
        SynchronizedToLock13 s = new SynchronizedToLock13();
        s.method1();
        s.method2();
    }
}

输出:

我是Synchronized形式的锁
我是lock形式的锁
锁已释放

 

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

  • JVM负责跟踪对象被加锁的次数;
  • 线程第一次给对象加锁的时候,计数变为1。每当这个相同的线程在此对象上再次获得锁时,计数会递增;
  • 每当任务离开时,计数递减,当计数为0时,锁被完全释放。

 

4.7 可见性原理:Java内存模型 

 

5. synchronized的缺陷

1)效率低:锁的释放情况少、试图获得锁时不能设定超时、不能中断一个正在试图获得锁的线程。

2)不够灵活(读写锁更灵活):加锁和释放的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的。

3)无法知道是否成功获取到锁。

 

6.synchronized常见面试题

1)使用注意点:锁对象不能为空、作用域不宜过大、避免死锁;

 

2)如何选择 Lock 和 synchronized 关键字?

答:如果可以,既不使用Lock也不使用synchronized关键字,而是使用java.util包中各种各样的类。如果没有就先用synchronized

 

3) 多线程访问同步方法的各种具体情况

参考前面第3节。

 

延伸:

1)多个线程等待同一个synchronized锁的时候,JVM如何选择下一个获取锁的是哪个线程?

 

2)synchronized 使得同时只有一个线程可以执行,性能较差,有什么办法可以提升性能?

答:优化使用范围,使用其他类型的lock例如读写锁。

 

3)如何更灵活地控制锁的获取和释放?

答:可以试着自行实现一个lock接口。

 

4)什么是锁的升级?降级?什么是JVM里的偏斜锁?轻量级锁?重量级锁?

 

7.总结

一句话介绍synchronized:

JVM会自动通过使用monitor来加锁和解锁,保证了同时只有一个线程可以执行指定代码,从而保证了线程安全,同时具有可重入性和不可中断的性质。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值