【Kill Thread】补充:Synchronized的作用

【Kill Thread】补充:Synchronized的作用

synchronize详解,锁升级
https://blog.csdn.net/lpf463061655/article/details/105149322

一、Synchronized简介

1、Synchronized的作用

image-20220120225537544

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

2、Synchronized的地位

image-20220120225752025

二、Synchronized的两种用法(对象锁和类锁)

1、对象锁

①方法锁(默认锁对象为this 当前实例对象)

/**
 * 描述:对象锁示例1,代码块形式
 */
public class SynchronizedObjectCodeBlock2 implements Runnable {
    private static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();
    @Override
    public synchronized void run() {
        synchronized (this) {
            System.out.println("我是对象锁的方法修饰符形式,我叫:" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
    }

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

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

运行结果

image-20220120231842114

②同步代码块锁(自己制定锁对象)

/**
 * 描述:对象锁示例1,代码块形式
 */
public class SynchronizedObjectCodeBlock2 implements Runnable {
    private static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();
    @Override
    public void run() {
        synchronized (this) {
            System.out.println("我是对象锁的代码块方式,我叫:" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
    }

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

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

image-20220120231235313

  • 确保了只有一个线程来运行这个代码块

③区别

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

2、类锁

概念

  • Java类可能有很多个对象,但是只有一个Class对象。
  • 本质:所谓的类所,不过是Class对象的锁而已。
  • 用法和效果:类锁只能在同一时刻被一个对象拥有。

①Synchronized修饰静态static的方法

/**
 * 描述: 类锁的第一种形式,static形式
 */
public class SynchronizedClassStatic4 implements Runnable{
    private static SynchronizedClassStatic4 instance1 = new SynchronizedClassStatic4();
    private static SynchronizedClassStatic4 instance2 = new SynchronizedClassStatic4();

    //static很关键
    public static synchronized void method() {
        System.out.println("我是对象锁的代码块方式,我叫:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }
    @Override
    public void run() {
        method();
    }

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

        }
        System.out.println("finish");
    }
}
  • 如果不加static,根据两个对象创建出来的线程,默认的锁是当前对象的锁,而加上static之后,这个method代码块升级为类锁。

②synchronized(*.class)代码块

public class SynchronizedClassClass5 implements Runnable{
    private static SynchronizedClassClass5 instance1 = new SynchronizedClassClass5();
    private static SynchronizedClassClass5 instance2 = new SynchronizedClassClass5();

    //static很关键
    public void method() {
        synchronized (SynchronizedClassClass5.class) {
            System.out.println("我是对象锁的代码块方式,我叫:" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
    }
    @Override
    public void run() {
        method();
    }

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

        }
        System.out.println("finish");
    }
}
  • 这里的锁为这个类的对象,只有一个。

三、多线程访问同步方法的7种情况

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

  • 线程安全,一个线程执行完之后,另一个线程执行。

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

  • 两个线程一起执行

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

  • 类锁,先后执行

4、同时访问同步方法与非同步方法

  • 同步方法最多只能被一个线程调用,非同步方法没有任何限制,谁调用都可以

5、访问同一个对象的不同的普通同步方法

  • 先后执行,因为synchronized方法默认的锁,是当前这个对象,还是会互斥的

6、同时访问静态synchronized和非静态synchronized方法

  • 两个程序可以同时运行,一个是类锁,一个是对象锁,是两个不同的锁,互不影响

7、方法抛异常后,会释放锁

四、Synchronized的性质

1、可重入

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

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

就比如说,排队去摇车牌号,轮到A人了,此时A获得了锁,A可以一直获取牌号,给A的三辆车弄三个车牌号,这是允许的,这就叫可重入。如果不允许这种操作,一次性只允许获取一个牌照,再次获取牌照需要重新排队,这就叫不可重入。

image-20220120235225489

image-20220120235338976

运行结果:

image-20220120235350937

2、不可中断

image-20220120235418937

五、深入原理

1、加锁和释放锁的原理:看字节码文件

  • 获取和释放锁的设计:进入和退出同步代码块(包括抛出异常)

测试代码:

public class SynchronizedToLock {

    Lock lock = new ReentrantLock();

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

    private void method2() {
        lock.lock();
        try {
            System.out.println("我是lock形式");
        } finally {
            lock.unlock();
        }
    }

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

}
  • synchronized可以自己释放锁,如果自己控制锁的话,需要手动的加锁和释放锁。

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

测试代码

public class Decompilation {
   private Object object = new Object();

   public void insert(Thread thread) {
       synchronized (object) {

       }
   }
}

编译生成class文件

image-20220121103743803

monitorenter和monitorexit JVM指令

image-20220121103852842

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

  • JVm会记录被加锁的次数
  • 第一次加锁的时候,次数从0变为1,之后如果再次加锁,就从1变成2,以此类推
  • 退出一层代码块的时候,计数减一,当计数为0的时候,代表锁释放

3、保证可见性的原理:内存模型

  • 可见性:一个线程执行的结果,另外的线程不一定可见
    • 线程1操作x = 5,之后线程2可能读取x = 3
  • synchronized可以保证可见性

六、Synchronized的缺陷

1、效率低

锁的释放情况少,试图获得锁时不能设定超时、不能中断一个正在视图获得锁的线程。

2、不够灵活(读写锁更灵活)

加锁和释放的时机单一、每个锁仅有单一的条件(某个对象),可能是不够的

3、无法知道是否成功的获取了锁

七、常见面试问题

1、使用注意点

锁的范围不宜过大,避免锁的嵌套

2、如何选择Lock和Synchronized关键字

手动和自动的情况,如果synchronized可以满足需求,优先选择synchronized

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

synchronized锁的升级

https://blog.csdn.net/lucky_love816/article/details/114633313
posted @ 2022-01-21 10:49  DarkerG  阅读(45)  评论(0编辑  收藏  举报