【JAVA】#详细介绍!!! synchronized 加锁 详解(1)!

news2024/12/26 21:03:13

本文分以下几点来介绍synchronized(根据JDK1.8)

1. 介绍synchronized

2. synchronized 为什么能保证线程安全

3. synchronized 的 用法

4. synchronized 的锁特性

目录

1. 介绍synchronized

2. synchronized的用法

2.1 synchronized修饰指定代码块

2.2 synchronized修饰实例方法、

2.3 synchronized修饰static修饰的静态方法

3. synchronized具体是如何实现线程安全(底层实现)

4. synchronized的锁特性



1. 介绍synchronized

synchronized是解决多线程执行下,访问资源同步性问题的关键字;

我们都知道多线程情况下针对同一个资源进行写访问会导致线程不安全问题;

那么synchronized关键字则是JVM给开发者提供的针对针对指定对象加锁以保证线程安全的关键字

synchronized是依赖监视器锁(monitor)来实现加锁的

在JDK1.6之前,监视器锁(monitor)是直接使用底层操作系统的mutex lock 来实现的,那么每次线程进行加锁解锁操作都得依赖操作系统调度,此时操作系统调度线程是内核态操作,大量的用户态内核态操作切换,则会导致系统资源的浪费,并且内核态操作时间效率也很低下。

所以在JDK1.6之前使用synchronized进行加锁操作,那么这个锁必然是重量级锁,效率较低

从JDK1.6开始,针对synchronized进行了一系列强有力的优化,synchronized加锁不直接依赖使用操作系统底层的互斥锁(mutex lock)来进行加锁的,而是根据一系列情况(锁竞争的强弱等)来升级锁的强度。只有在锁竞争很激烈且线程每次占用锁时间较长的时候才会把锁升级为直接依赖互斥锁的重量级锁。这样很好的提高了线程的并发效率并且大大减少了内核态操作,提高了线程执行效率

大致升级优化过程如下(下篇文章详解):

无锁->偏向锁->轻量级锁->重量级锁

其中包括:偏向锁,锁消除,锁粗化,自旋锁,自适应自旋锁等一系列优化手段,大大提高了锁操作的效率(详情请看下篇文章)

2. synchronized的用法

synchronized的使用得基于某个锁对象进行加锁操作,在一个线程中针对某个锁对象进行加锁后,在该线程没对加锁对象解锁前,其他线程不能使用该锁对象进行加锁操作

举个例子:

public class Test {
    //手动指定一个不变得Object对象,专门用于某个synchronized加锁
    private static final Object lock = new Object();

    public static void showThread (Thread t){
        //对锁对象进行加锁
        synchronized (lock){
            while(true){
                System.out.println(t.getName()+"正在执行");
                
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args) {
        //创建两个线程并发执行加锁代码块
        Thread t1 = new Thread(() -> {
            showThread(Thread.currentThread());
        },"t1");

        Thread t2 = new Thread(() -> {
            showThread(Thread.currentThread());
        },"t2");

        t1.start();
        t2.start();
    }
}

结果:

上诉代码创建了两个线程(t1,t2)去同时执行同一个锁对象的加锁代码块,发现当t1线程先抢到锁(t1线程先使用指定锁对象进行了加锁操作去执行代码),并且在锁对象没解锁的情况下,t2线程使用相同锁对象并不能进入加锁代码块执行代码

当然必须得是同一个锁对象进行加锁,不同的锁对象之间不会发生线程互斥执行的反应,但是多线程针对不同锁对象加锁去访问相同的数据,加锁也就等于不加锁,虽然加锁了但是保证不了线程安全,违背了加锁的初心 

2.1 synchronized修饰指定代码块

class CountAdd {
    private final Object lock = new Object();
    public int count;
    CountAdd(int count){
        this.count = count;
    }
    //方法1.使用自定义的对象锁lock
    public void add(){
        synchronized (lock){
            count++;
        }
    }
    //方法2.使用对象的引用作为对象锁
        //这里的this是对象的引用,synchronized锁对象可以是任何Object类对象
    public void add1(){
        synchronized (this){
            count++;
        }
    } 
}
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        CountAdd countAdd = new CountAdd(0);
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                countAdd.add();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                countAdd.add();
            }
        });

        t1.start();
        t2.start();
        Thread.sleep(1000);
        System.out.println(countAdd.count);
    }
}

上述代码 定义了两个线程同时对count进行自增操作,结果count=10000(线程安全);

synchronized使用:

方法1:使用自定义的锁对象lock,来控制线程进行synchronized加锁解锁操作

方法2:直接使用对象的引用this来作为锁对象控制线程加锁解锁

这里 用使用lock和this效率一样,但是如果有两个加锁代码块,且两者并不相关,那么此时就可以定义两个锁对象来分别控制并发保证线程安全

此时都用this也能保证线程安全,但是这两者的加锁代码块进行了绑定,本来多线程分别执行这两个代码块,本来就是线程安全的,由于你们的锁对象相同,依旧会串行执行,使得效率变低

注意:还是上面说的:多个线程访问修改同一个数据,进行加锁能保证线程安全的前提是锁对象相同,锁对象相同时使得线程执行加锁的代码块是串行执行的

如果多个加锁代码块并发执行也时线程安全,那么此时用一个锁对象使得两个代码块串行执行就是多次一举了,此时我们自己给定锁对象显然更好

2.2 synchronized修饰实例方法、

synchronized public void add(){
      count++;
}

给整个add方法加锁,那么此时synchronized的作用域就是整个方法,进入该方法前加锁,执行完方法解锁。锁对象为this(调用该方法的对象)

这里的效果和上面synchronized(this)那个方案相同

2.3 synchronized修饰static修饰的静态方法

synchronized public static void add(){
      count++;
}

给static静态方法加锁,那么等同于给整个类对象加锁

我们都知道static修饰的变量和方法不属于任何实例对象,时属于类的成员

那么给static方法加锁,那么执行时会对整个类进行加锁操作,多线程执行下,该类的其他加锁操作执行全部受影响

锁对象:类对象

3. synchronized具体是如何实现线程安全(底层实现)

观察某加锁的代码执行的汇编码

 前面说了synchronized是基于monitor(监视器锁)来实现的,此时控制加锁和解锁的指令如图所示为:monitorenter(进入监视器锁),monitorexit(结束监视器锁)

加锁执行的代码块就是monitorenter和monitorexit中间的部分

注意:细心的读者会会发现在正常执行完monitorexit之后后面又有一条monitorexit指令

原因:避免加锁代码在执行过程中发生异常没指令monitorexit指令就直接退出了加锁代码块,此时编译器会产生一个异常处理器,目的是代码出现异常时执行monitorexit,确保该线程正常解锁,避免其他线程因为该线程没解锁就出锁进而等待的异常

根据Java虚拟机(HotShop),每个Object对象都内置了一个monitor对象。

当执行到monitorenter指定时,线程会试图获取锁对象的monitor的所有权

获取成功:此时线程执行加锁代码,直到执行到monitorexit解锁

获取失败:如果当前锁对象的monitor已经被其他线程占有,此时当前线程就获取失败,那么当前线程就进行锁等待,等待到线程释放了锁(执行了monitorexit),在尝试去获取锁对象的monitor所有权。

如果当前线程获取到锁对象的monitor所有权时,那么锁的计数器会进行+1,表示该锁对象已经被使用,其他线程不能对该锁对象进行加锁

注意:可重入锁可以使锁对象对相同线程进行多次加锁(下篇介绍)

图解:

 解锁操作同理:执行monitorexit时该对象锁的计数器会进行-1操作,表时该线程本次已经解锁

4. synchronized的锁特性

synchronized 开始为乐观锁;当冲突过高且线程持有锁时间较长 时转化为悲观锁

synchronized时轻量级锁(大概率使用自旋锁实现),也是重量级锁,是可重入锁,

是不公平锁

synchronized不是读写锁 

每种锁的基本详情请看:http://t.csdn.cn/TTzAD(常见锁策略)


本篇文章讲到这就大致结束了,下篇会详细将synchronized锁的优化详情,感兴趣的友友可以关注一下!!!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/422208.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何定位Spark数据倾斜问题,解决方案

文章目录前言一、数据倾斜和数据过量二、 数据倾斜的表现三、定位数据倾斜问题定位思路&#xff1a;查看任务-》查看Stage-》查看代码四、7种典型的数据倾斜场景解决方案一&#xff1a;聚合元数据解决方案二&#xff1a;过滤导致倾斜的key解决方案三&#xff1a;提高shuffle操作…

谁才是天下第一关?

什么是关&#xff0c;中华大地有多少关&#xff1f; 关是往来必由之要处。“山川扼要&#xff0c;是设关津。表封藏&#xff0c;以达道路&#xff0c;天险既呈&#xff0c;人力并济”。 关可分为&#xff1a; 关防&#xff0c;驻兵防守的要塞&#xff1b;关津&#xff0c;水陆…

python笔记:qgrid

在Jupyter Notebook中像在Excel一样操作pandas的DataFrames&#xff0c;如sort/filter&#xff0c;并轻松把操作后的数据用于后续分析。 0 安装 pip install qgrid jupyter nbextension enable --py --sys-prefix qgrid 1 基本使用方法 1.1 数据 import numpy as np import…

Carla 保姆级安装教程

一&#xff1a;电脑配置 carla支持windows,Linux系统构建&#xff0c;官方对于安装电脑的最低配置要求是拥有6G显存的GPU&#xff0c;推荐8G显存的GPU&#xff0c;至少需要20G的存储空间&#xff0c;所有对电脑的配置要求是不小的挑战。 我所使用电脑的硬件配置&#xff1a;3…

3.7 曲率

学习目标&#xff1a; 如果我要学习高等数学中的曲率&#xff0c;我会遵循以下步骤&#xff1a; 1.熟悉相关的数学概念&#xff1a;在学习曲率之前&#xff0c;我们需要了解曲线、切线和曲率半径等相关的数学概念。因此&#xff0c;我会复习这些概念&#xff0c;以便更好地理…

网卡别名的设置

文章目录1. 网卡别名是什么2. 工作原理3. 设置3.1 临时添加&#xff0c;重启失效3.1.1 使用ipconfig命令来设置网卡别名3.1.2 使用ip addr命令来设置网卡别名3.2 永久性添加3.3 查看参考1. 网卡别名是什么 IP别名就是一张物理网卡上配置多个IP&#xff0c;实现类似子接口之类的…

制作PassMarkMemTest86启动U盘

制作PassMarkMemTest86启动U盘1. 概述2.制作 PassMarkMemTest86 启动U盘结束语1. 概述 PassMarkMenTest86 是一款免费、开源且强大的内存检测工具&#xff0c;能测试电脑内存的稳定性、存储大小和隐性问题&#xff0c;它还拥有 13 种不同的 RAM 测试算法&#xff0c;在主菜单中…

洛丽运动会 NFT 作品集第一弹

欢迎来到 2036 年洛丽运动会&#xff0c;这是一个以史前世界为背景的体育小游戏体验。为了庆祝这场伟大比赛的开始&#xff0c;结合了史前和运动配件的 NFT 系列将于北 The Sandbox 市场平台发布。 运动和格斗设备将提高你在运动会上的技能&#xff1b;而史前配件将使你与体育场…

Linux高并发服务器(webserver)

一.有限状态机 它的转移函数表示系统从一个状态转移到另一个状态的条件 二.EPOLL 在内核中创建一个数据&#xff0c;这个数据有两个比较重要的数据&#xff0c;一个是需要检测的文件描述符的信息&#xff08;红黑树&#xff09;&#xff0c;一个双向链表&#xff0c;存放检测到…

Java类加载机制介绍

类加载机制的简单介绍 类加载机制是指将.class字节码文件读入到内存中。在运行时数据区中的方法区保留类的数据结构&#xff0c;在堆中创建一个与之对应的Class对象。 类的生命周期主要经历7个阶段&#xff1a;加载、验证、准备、解析、初始化、使用、卸载 其中从加载到初始化…

如何通俗易懂的解释无线通信中的那些专业术语!

这是一篇来自网络的非常经典的一篇老文&#xff0c;原作者不详&#xff0c;但非常值得一读&#xff01; 香农定理 类比&#xff1a;城市道路上的汽车的车速和什么有关系&#xff1f;和道路的宽度有关系&#xff0c;和自己车的动力有关系&#xff0c;也其他干扰因素有关系&…

Unity接SDK - 极光推送

2021.09.09记录&#xff0c;2023发布&#xff0c;如有不对&#xff0c;还请包含。发晚了 如果想看Android原生接入JPush - SDK&#xff0c;移步Android原生集成JPush SDK_jpush android sdk v4.7.2 极光推送 - 接入 版本&#xff1a; Unity 2020.3.10f1 JPush - Unity 3…

linux系统安全及应用

目录一、账号安全控制1.1基本安全措施1.1.1系统账号的清理1.1.1.1将非登录用户的Shell设为/sbin/nologin1.1.1.2锁定长期不使用的账号1.1.1.3删除无用账号1.1.1.4锁定账号文件passwd、shadow1.1.2密码安全控制1.1.2.1设置密码有效期1.1.2.2要求用户下次登录时修改密码1.1.3命令…

服务端开发之Java秋招面试11

努力了那么多年,回头一望,几乎全是漫长的挫折和煎熬。对于大多数人的一生来说,顺风顺水只是偶尔,挫折、不堪、焦虑和迷茫才是主旋律。我们登上并非我们所选择的舞台,演出并非我们所选择的剧本。继续加油吧&#xff01; 目录 1.MySQL的多版本并发控制具体实现过程&#xff1f;…

目标检测YOLO系列-YOLOVX运行步骤(推理、训练全过程)

下载项目&#xff1a;点击下载 进入项目根目录&#xff08;通过cd命令&#xff09; apex的安装与下载 下载apex git clone https://github.com/NVIDIA/apex进入apex目录 cd apex执行安装命令 python setup.py install首先安装相关的类库&#xff1a; pip install -i https://p…

深入学习MongoDB---1---入门篇+基础重点篇

MongoDB入门 MongDB作为NoSQL数据库之一&#xff0c;主要关注&#xff1a;灵活性、扩展性、高可用灵活性&#xff1a;NoSQL的特点就是反范式理论&#xff0c;为数据的水平扩展和字段的组织提供了巨大的便利高可用&#xff1a;天生就伴随副本集&#xff08;从节点&#xff09;的…

计数排序的实现

计数排序是非比较排序的一种&#xff0c;是对哈希直接定址法的变形应用&#xff0c;其操作步骤如下&#xff1a; 1.统计相同元素出现的次数。 2.根据统计结果将序列回收到原来的序列中。 拿一组重复元素较多的数组来举例子&#xff1a; 10 11 10 15 14 15…

Disruptor-源码解读

前言 Disruptor的高性能&#xff0c;是多种技术结合以及本身架构的结果。本文主要讲源码&#xff0c;涉及到的相关知识点需要读者自行去了解&#xff0c;以下列出&#xff1a; 锁和CAS伪共享和缓存行volatile和内存屏障 原理 此节结合demo来看更容易理解&#xff1a;传送门…

数云融合|新手入门,5分钟秒懂开源

目录一、开源软件开源领域的两大组织&#xff1a;FSF和OSI二、开源许可证开源意味着免费吗&#xff1f;三、开源技术应用领域四、总结一、开源软件 开源即开放源代码&#xff0c;他的核心是源代码公开&#xff0c;任何人都可以查看、使用、修改和分发。与之相对的是闭源&#…

js排序算法

排序算法 - jsjs交换两个值的三种方法方式1&#xff1a;算术运算方式2&#xff1a;ES6解构方式3&#xff1a;数组的特性冒泡排序实现思路图解bubbleSort参考视频选择排序实现思路图解selectionSort参考视频插入排序实现思路图解insertionSort参考视频js交换两个值的三种方法 方…