• Synchronized锁的使用


    前言:大家好,我是小威,24届毕业生,在一家满意的公司实习。本篇文章是关于并发编程中Synchronized锁的用法知识记录,由于篇幅原因,核心原理知识下篇记录。
    本篇文章记录的基础知识,适合在学Java的小白,也适合复习中,面试中的大佬🤩🤩。
    如果文章有什么需要改进的地方还请大佬不吝赐教👏👏。
    小威在此先感谢各位大佬啦~~🤞🤞
    在这里插入图片描述

    🏠个人主页:小威要向诸佬学习呀
    🧑个人简介:大家好,我是小威,一个想要与大家共同进步的男人😉😉
    目前状况🎉:24届毕业生,在一家满意的公司实习👏👏

    🎁如果大佬在准备面试,可以使用我找实习前用的刷题神器哦刷题神器点这里哟
    💕欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,我亲爱的大佬😘

    以下正文开始
    在这里插入图片描述

    Synchronized的用法

    在多线程并发问题中,常用Synchronized锁解决问题。Synchronized锁通常用于同步示例方法,同步静态方法,同步代码块等。

    同步示例方法

    我们可能自己使用过在方法前加Synchronized锁修饰,在多线程并发同时调用同一个实例化对象时,如果这个方法加上了Synchronized锁,那么也是线程安全的。
    举个栗子:

    package Thread;
    
    import java.util.stream.IntStream;
    
    public class ThreadTest {
        private Long count=0L;
        public void incrementCount(){
            count++;
        }
        public Long execute() throws InterruptedException {
            Thread thread1=new Thread(()->{
                IntStream.range(0,1000).forEach((i)->incrementCount());//线程1循环1000次
            });
            Thread thread2=new Thread(()->{
                IntStream.range(0,1000).forEach((i)->incrementCount());//线程2循环1000次
            });
            thread1.start();//开启线程
            thread2.start();
            thread1.join();//等待线程1和线程2执行完毕
            thread2.join();
            return count;
        }
    
        public static void main(String[] args) throws InterruptedException {
            ThreadTest threadTest=new ThreadTest();
            Long count = threadTest.execute();
            System.out.println(count);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    在上面的程序中,count变量为成员变量,在多线程同时使用时极大可能会发生错误,在前面也讲到过count++包含三个步骤:1.将变量count从主内存中加载到CPU的寄存器中;2.在CPU的寄存器中执行count++或++count的操作;3.将运算的count++的结果写入缓存或内存中。两个线程都会更新count的值到内存中,当其中一个线程再从内存中读取数据时,可能读到的成员变量会与当前的变量不一致,从而使得最终count的结果不为2000,因此会发生错误。

    如何能解决这种错误?就是为incrementCount方法加锁:

     public synchronized void incrementCount(){
            count++;
        }
    
    • 1
    • 2
    • 3

    这样就能保证所得到的count最终值为2000了。
    在这里插入图片描述

    同步静态方法

    当一个类的某个静态方法加了synchronized锁时,就相当于给这个类的class对象加锁。所以无论创建多少个当前类的对象调用这个被synchronized锁修饰的静态方法时,都是线程安全的。
    如上面的例子,修改如下:

    package Thread;
    
    import java.util.stream.IntStream;
    
    public class ThreadTest {
        private static Long count=0L;
        public static synchronized void incrementCount(){
            count++;
        }
        public static Long execute() throws InterruptedException {
            Thread thread1=new Thread(()->{
                IntStream.range(0,1000).forEach((i)->incrementCount());
            });
            Thread thread2=new Thread(()->{
                IntStream.range(0,1000).forEach((i)->incrementCount());
            });
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
            return count;
        }
    
        public static void main(String[] args) throws InterruptedException {
            ThreadTest threadTest=new ThreadTest();
            Long count = threadTest.execute();
            System.out.println(count);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    因此,当多个线程并发执行调用被synchronized锁修饰的静态方法时,这个静态方法是线程安全的。
    在这里插入图片描述

    同步代码块

    前面提到加了synchronized锁的方法在多线程并发条件下是线程安全的,但是在执行业务逻辑过多的代码块时,可能会影响程序的执行效率。对于此时,可以把一个方法分成多个小的临界区
    举个栗子:

        private Long count1=0L;
        private Long count2=0L;
        public synchronized void incrementCount(){
            count1++;
            count2++;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在上面的代码中,count1和count2为两个不同的自增操作,因此对于count1和count2来说是两个不同的临界区资源。当一个线程进入incrementCount方法中时,会对整个方法进行加锁,在对count1进行自增操作时,也会占用count2的资源,相当于占用全部的资源。只有等到这个线程执行完count1++和count2++的操作时,释放锁时,其它线程才能拿到锁资源进入incrementCount方法。

    但是这样会影响程序的性能。因为count1++和count2++为两个互不影响的两个临界区资源,当线程拿到锁,会占用两个资源,使得临界区资源进行闲置等待,因此可以优化代码,让synchronized锁修饰代码块。

    修改后的代码:

        private Long count1=0L;
        private Long count2=0L;
        public Object count1Lock=new Object();
        public Object count2Lock=new Object();
        public void incrementCount(){
            synchronized (count1Lock){
                count1++;
            }
            synchronized (count2Lock){
                count2++;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    上面代码中,对count1和count2分别建立了对象锁count1Lock和count2Lock,而没有对incrementCount加锁,意为当一个线程进入incrementCount方法时,其他线程也能进入此方法,当线程1拿到count1Lock对象锁时,不影响线程2拿到count2Lock对象锁来对count2执行自增操作。

    这样既提高了程序的执行效率,同时,由于临界区资源都加了锁,incrementCount方法也是线程安全的。

    本篇文章就分享到这里了,后续将会分享Synchronized锁的核心原理内容,感谢大佬认真读完支持咯 ~
    在这里插入图片描述

    文章到这里就结束了,如果有什么疑问的地方请指出,诸佬们一起讨论🍻
    希望能和诸佬们一起努力,今后进入到心仪的公司
    再次感谢各位小伙伴儿们的支持🤞

    在这里插入图片描述

  • 相关阅读:
    《异常检测——从经典算法到深度学习》20 HotSpot:多维特征 Additive KPI 的异常定位
    【JDK 8-Lambda】3.2 自定义函数式编程实战
    WebKit Inside: CSS 样式表的匹配时机
    Pytorch Advanced(二) Variational Auto-Encoder
    从输入URL到渲染的过程中到底发生了什么?
    【数一】【概率论与数理统计】
    网站页头被挂马状态及新增了index.html文件解决思路
    施努卡:什么是视觉定位系统 视觉定位系统的工作原理
    Java的I/O框架
    万字总结:分布式系统的 38 个知识点
  • 原文地址:https://blog.csdn.net/qq_53847859/article/details/127520451