首页 > 其他分享 >synchronized学习笔记

synchronized学习笔记

时间:2022-11-14 16:15:08浏览次数:47  
标签:synchronized Thread System 笔记 学习 线程 println new out

Synchronized

1. synchronized的作用

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

2.不使用并发会有什么后果?

两个线程同时a++,最后结果会比预计的少

原因

i++,虽然是一行代码,但是实际上至少包含了以下这三个动作:

1.读取i的值

2.计算i+1

3.把i +1的计算结果写回到内存中,赋给i

3. synchronized的两种用法

3.1 对象锁

代码块形式:手动指定锁对象

方法锁形式:synchronized修饰普通方法,锁对象默认为this

3.1.1 对象锁的形式-同步代码块
/*
    线程一进入第一个同步代码块
     lock1线程一正在执行
     lock1线程一运行结束
    线程一退出第一个同步代码块

    线程二进入第一个同步代码块
     lock1线程二正在执行

     线程一进入第二个同步代码块
     lock2线程一正在执行

     lock1线程二运行结束
     线程二退出第一个同步代码块

     lock2线程一运行结束
     线程一退出第二个同步代码块

     线程二进入第二个同步代码块
     lock2线程二正在执行

     lock2线程二运行结束
     线程二退出第二个同步代码块

     finished

     线程一二在第一个同步等待,线程一出去之后,线程二进去,线程一同时也进去了第二个同步代码块,线程二退出了第一个同步代码块,
     线程一退出了第二个同步代码块, 此时线程二进入第二个代码块,之后程序结束
 */
public class Synchronized implements Runnable {

    private static Synchronized aSynchronized = new Synchronized();

    Object lock1 = new Object();
    Object lock2 = new Object();

    public static void main(String[] args) {

        Thread thread1 = new Thread(aSynchronized, "线程一");
        Thread thread2 = new Thread(aSynchronized, "线程二");
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()) {
        }
        System.out.println("finished");

    }

    @Override
    public void run() {
        synchronized (lock1) {
            System.out.println("lock1" + Thread.currentThread().getName() + "正在执行");

            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("lock1" + Thread.currentThread().getName() + "运行结束");
        }
        synchronized (lock2) {
            System.out.println("lock2" + Thread.currentThread().getName() + "正在执行");

            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("lock2" + Thread.currentThread().getName() + "运行结束");
        }


    }


}
3.1.2 对象锁的形式-方法锁
/*
   在方法加上锁
 */
public class Synchronized1 implements Runnable {

    private static Synchronized1 aSynchronized = new Synchronized1();

    Object lock = new Object();


    public static void main(String[] args) {

        Thread thread1 = new Thread(aSynchronized, "线程一");
        Thread thread2 = new Thread(aSynchronized, "线程二");
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()) {
        }
        System.out.println("finished");

    }

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

    }

    private synchronized void method() {
       
            System.out.println(Thread.currentThread().getName() + "正在执行");

            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
}

3.2 类锁

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

形式1: synchronized加在static方法上

形式2:synchronized (*.class )代码块

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

本质:所以所谓的类锁,不过是Class对象的锁而已。

用法和效果:类锁只能在同一时刻被一个对象拥有。

3.2.1 synchronized加在static方法上
/*
    加static ,为类锁,本质就是Synchronized2 一个对象
 */
public class Synchronized2 implements Runnable {

    private static Synchronized2 aSynchronized1 = new Synchronized2();
    private static Synchronized2 aSynchronized2 = new Synchronized2();

    Object lock = new Object();


    public static void main(String[] args) {

        Thread thread1 = new Thread(aSynchronized1, "线程一");
        Thread thread2 = new Thread(aSynchronized2, "线程二");
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()) {
        }
        System.out.println("finished");

    }

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

    }

    private 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() + "运行结束");

    }
}
3.2.2 synchronized (*.class )代码块
/*
    synchronized (*.class )代码块
 */
public class Synchronized3 implements Runnable {

    private static Synchronized3 aSynchronized1 = new Synchronized3();
    private static Synchronized3 aSynchronized2 = new Synchronized3();


    public static void main(String[] args) {

        Thread thread1 = new Thread(aSynchronized1, "线程一");
        Thread thread2 = new Thread(aSynchronized2, "线程二");
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()) {
        }
        System.out.println("finished");

    }

    @Override
    public void run() {
        synchronized (Synchronized3.class) {
            System.out.println(Thread.currentThread().getName() + "正在执行");

            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
    }
}

4. 调试技巧

可以看到线程一正在运行,线程二等待状态

image-20221102164421521

打开调试窗查看

image-20221102164729088

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

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

就一个Synchronized1对象可以使用sychronized保护

/*
   在方法加上锁
 */
public class Synchronized1 implements Runnable {

    private static Synchronized1 aSynchronized = new Synchronized1();

    Object lock = new Object();


    public static void main(String[] args) {

        Thread thread1 = new Thread(aSynchronized, "线程一");
        Thread thread2 = new Thread(aSynchronized, "线程二");
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()) {
        }
        System.out.println("finished");

    }

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

    }

    private synchronized void method() {

            System.out.println(Thread.currentThread().getName() + "正在执行");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
}

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

不能收到保护,并行执行

public class Synchronized1 implements Runnable {

    private static Synchronized1 aSynchronized1 = new Synchronized1();
    private static Synchronized1 aSynchronized2 = new Synchronized1();

    Object lock = new Object();


    public static void main(String[] args) {

        Thread thread1 = new Thread(aSynchronized1, "线程一");
        Thread thread2 = new Thread(aSynchronized2, "线程二");
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()) {
        }
        System.out.println("finished");

    }

    @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() + "运行结束");
        }
    }
}

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

升级到为类锁的情况,可以受到保护

/*
    加static ,为类锁,本质就是Synchronized2 一个对象
 */
public class Synchronized2 implements Runnable {

    private static Synchronized2 aSynchronized1 = new Synchronized2();
    private static Synchronized2 aSynchronized2 = new Synchronized2();

    Object lock = new Object();


    public static void main(String[] args) {

        Thread thread1 = new Thread(aSynchronized1, "线程一");
        Thread thread2 = new Thread(aSynchronized2, "线程二");
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()) {
        }
        System.out.println("finished");

    }

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

    }

    private 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() + "运行结束");

    }
}

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

并行执行

/*
    同时访问同步方法与非同步方法
 */
public class Synchronized4 implements Runnable {

    private static Synchronized4 aSynchronized = new Synchronized4();


    public static void main(String[] args) {

        Thread thread1 = new Thread(aSynchronized, "线程一");
        Thread thread2 = new Thread(aSynchronized, "线程二");
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()) {
        }
        System.out.println("finished");

    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("线程一")){
            method();
        }else {
            method1();
        }
    }

    private synchronized void method() {
        System.out.println(Thread.currentThread().getName() + "加锁正在执行");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "加锁运行结束");
    }
    private  void method1() {
        System.out.println(Thread.currentThread().getName() + "无锁正在执行");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "无锁运行结束");
    }
}

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

因为是同一个锁对象,所以要串行执行

/*
    访问同一个对象的不同的普通同步方法
 */
public class Synchronized5 implements Runnable {

    private static Synchronized5 aSynchronized = new Synchronized5();


    public static void main(String[] args) {

        Thread thread1 = new Thread(aSynchronized, "线程一");
        Thread thread2 = new Thread(aSynchronized, "线程二");
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()) {
        }
        System.out.println("finished");

    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("线程一")){
            method();
        }else {
            method1();
        }
    }

    private synchronized void method() {
        System.out.println(Thread.currentThread().getName() + "加锁1正在执行");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "加锁1运行结束");
    }
    private synchronized void method1() {
        System.out.println(Thread.currentThread().getName() + "加锁2正在执行");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "加锁2运行结束");
    }
}

5.6 同时访问静态synchronized和非静态synchronized方法

因为一个是类锁,一个是对象锁,所以并行执行

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

    private static Synchronized6 aSynchronized = new Synchronized6();


    public static void main(String[] args) {

        Thread thread1 = new Thread(aSynchronized, "线程一");
        Thread thread2 = new Thread(aSynchronized, "线程二");
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()) {
        }
        System.out.println("finished");

    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("线程一")){
            method();
        }else {
            method1();
        }
    }

    private static synchronized void method() {
        System.out.println(Thread.currentThread().getName() + "加锁1正在执行");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "加锁1运行结束");
    }
    private synchronized void method1() {
        System.out.println(Thread.currentThread().getName() + "加锁2正在执行");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "加锁2运行结束");
    }
}

5.7 抛出异常,会释放锁

/*
 抛出异常会释放锁
 */
public class Synchronized7 implements Runnable {

    private static Synchronized7 aSynchronized = new Synchronized7();


    public static void main(String[] args) {

        Thread thread1 = new Thread(aSynchronized, "线程一");
        Thread thread2 = new Thread(aSynchronized, "线程二");
        thread1.start();
        thread2.start();
        while (thread1.isAlive() || thread2.isAlive()) {
        }
        System.out.println("finished");

    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("线程一")){
            method();
        }else {
            method1();
        }
    }

    private  synchronized void method() {
        System.out.println(Thread.currentThread().getName() + "加锁1正在执行");

        try {
            Thread.sleep(1000);
            throw new RuntimeException();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        System.out.println(Thread.currentThread().getName() + "加锁1运行结束");

    }
    private synchronized void method1() {
        System.out.println(Thread.currentThread().getName() + "加锁2正在执行");

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "加锁2运行结束");
    }
}

6. 性质

6.1 什么是可重入

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

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

代码1

/*
   递归调用本方法:输出:
                    a=0
                    a=1
  说明是可重入的,因为进入一次线程又再次进入
 */
public class Reentrant {
    int a = 0;

    public synchronized void method() {
        System.out.println("a=" + a);
        if (a == 0) {
            a++;
            method();
        }
    }

    public static void main(String[] args) {
        Reentrant reentrant = new Reentrant();
        reentrant.method();
    }
}

代码2

/*
   输出:method1
        method2
        因为可重入所以方法一去调用方法二可以执行
 */
public class Reentrant1 {


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

    }

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

6.2 性质:不可中断

一旦这个锁已经被别人获得了,如果我还想获得,我只能选择等待或者阻塞,直到别的线程释放这个锁。如果别人永远不释放锁,那么我只能永远地等下去。

7. 原理

7.1 加锁和释放锁的原理

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

/*
 获取和释放锁的时机:进入和退出同步代码块(包括抛出异常)
 */
public class PrincipleOf {

    ReentrantLock lock = new ReentrantLock();

    public synchronized void method(){
        System.out.println("synchronized");
    }
    //一定要try因为如果抛出异常,就会释放不了锁
    public  void method1(){
        lock.lock();
        try{
            System.out.println("synchronized");
        }finally {
            lock.unlock();
        }
    }
}

字节码查看

进入目录:

执行命令:

javac Decompile.java
javap -verbose Decompile.class

image-20221102202822441

/*
看字节码 同步代码块
 */
public class Decompile {

    private Object object = new Object();

     public void method(Thread thread){
         synchronized (object){
         }
     }
}

有俩monitorexit,一个代表正常退出,一个代表异常退出。

7.2可重复原理:加锁次数计数器

JVM会记录被加锁的次数

第一次加锁时,次数从0变为1,之后如果再次加锁,就从1变成2以此类推

退出一层同步代码块时,计数减一,当计数为0的时候,代表锁释放

7.3 synchronized可以保证可见性

JMM内容

8. 缺陷

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

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

无法知道是否成功获取锁

9. 面试题

使用注意点:锁的范围不宜过大、避免锁的嵌套

如何选择Lock和synchronized关键字?

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

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

读写锁

我想更灵活地控制锁的获取和释放(现在释放锁的时机都被规定死了),怎么办?

使用lock锁

10. 总结

一句话介绍synchronized

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

Synchronized的作用、地位、不控制并发的后果

两种用法:对象锁和类锁

多线程访问同步方法的7种情况:是否是static, Synchronized方法等

Synchronized的性质:可重入、不可中断

原理:加解锁原理、可重入原理、可见性原理

标签:synchronized,Thread,System,笔记,学习,线程,println,new,out
From: https://www.cnblogs.com/mrwyk/p/16889320.html

相关文章

  • 36、Pytorch之小土堆学习记录
    基本思想:花了两天时间把bilibil上小土堆的视频刷完了,简单记录一下,主要目的是想学习如何转模型,陆陆续续结合ncnn源码,如何实现onnx--->ncnn或者其它学习框架的模型转化,先来......
  • 10、 ARM 内联汇编学习笔记
    基本思想:随手记录一下ARM的内联汇编的基础语法,以便更深入的学习NCNN源码~​​ARMGCCInlineAssemblerCookbook​​ 参考官网(1)、基本的汇编语法结构为asmvolatile(co......
  • Unity开发笔记-Timeline扩展笔记(1)
    ILayerable代码修改动画后推publicstaticvoidSetTimeClipExtrapolation(TimelineClipclip,TimelineClip.ClipExtrapolationextrapolation){vartype=clip.Get......
  • 如何提高孩子的学习兴趣?裸眼互动ar儿童沙盘,让孩子边玩边学!
    如何提高孩子的学习兴趣,是每个家长都关心的话题。裸眼互动ar儿童沙盘就是一种新型的儿童教学模式,利用ar虚拟现实技术,结合真实沙子,将各种大自然景象呈现出来为3D沙盘的形式,......
  • 『NLP学习笔记』如何理解attention中的Q,K,V
    如何理解attention中的Q,K,V?文章目录​​一.如何理解attention中的Q,K,V?​​​​1.1.定义三个线性变换矩阵​​​​1.2.定义QKV​​​​1.3.自注意力计算​​​​1.3.1......
  • Qt 控件学习2 获取当前日期和时间
    #include<QDateTime>//获取当前日期和时间QStringMyQtInterFace::GetCurTimeDate(){QStringdatetime=QDateTime::currentDateTime().toString("yyyy-MM-ddd......
  • java基础笔记
    java的数据类型分为两大类  进制前缀二进制:0b八进制:0十六进制:0xJava会直接将它们转换为十进制输出 float、double并不能准确表示每一位小数,对于有的小数只能无......
  • 使用 C++ 部署深度学习模型快速上手方案
    本文将从获取一个训练好的 shufflenet_v2 模型出发,讲解如何使用MegEngineLite的C++接口将其部署到CPU(Linuxx86/AndroidArm)环境下运行。主要分为以下小节:导......
  • Navicat使用笔记08---利用Navicat进行数据迁移
    1.使用背景需要将一台服务器上mysql数据迁移到另一台服务器的mysql中2.单库迁移2.1在目标服务器中创建一个和源服务器数据库名称一样的数据库2.2创建任务开始迁移......
  • 图学习初探Paddle Graph Learning 构建属于自己的图【系列三】
    项目链接:​​https://aistudio.baidu.com/aistudio/projectdetail/5000517?contributionType=1​​如遇到问题查看原项目解决图学习温故以及初探PaddleGraphLearning(PGL......