Java 多线程 --- 线程同步 内部锁synchronized

2023-11-04

Intrinsic Lock (Monitor)

  • synchronized锁机制是基于monitor对象(也被叫做Monitor 或 Monitor Lock监视器锁或 Intrinsic Lock)实现的,每个对象都存在一个monitor对象与之关联,对象头中有一块专门的内存区域用于存储与之关联的monitor对象的地址。
  • 每个monitor对象有三个部分
  • The Owner: 表示目前锁的持有者, 如果为null则表示是无锁状态
  • Entry Set: 记录等待获得相应内部锁的线程. 多个线程申请同一个锁的时候, 只有一个申请者能够成为该锁的持有线程, 其他申请失败者会继续保留在Entry Set.
  • Wait Set: 当一个线程获得锁之后, 因为没有满足某些条件而不得不放弃锁 (调用wait方法). 会被放入Wait Set并进入阻塞状态
  • 比如在HotSpot虚拟机中,monitor是由ObjectMonitor实现的,其主要数据结构如下(源码ObjectMonitor.hpp文件,C++实现):
//只列举出部分关键字段
ObjectMonitor() {
	_object;      = NULL;	//当前monitor关联的锁对象
    _header       = NULL;	//当前monitor关联的锁对象的原始对象头
    _count        = 0;		//抢占该monitor的线程数
    _owner        = NULL;	//占用当前monitor的线程
    _WaitSet      = NULL; 	//处于wait状态的线程,会被加入到该列表
    _EntryList    = NULL ; 	//处于block状态的线程,会被加入到该列表
}
  • 内部锁是非公平锁. 所以在wait set的线程被唤醒时, 会有其他的活跃线程(处于Runnable状态, 并且是第一次竞争该锁) 来一起竞争. 所以不是内部锁不是先到先得, 允许线程插队获得锁.
  • 内部锁是可重入锁, 有一个计数器记录目前线程的所有权, 为0时代表无锁, 为1时代表已被抢占, 当拥有锁的线程再次申请时, 计数器会进行加一操作.
  • 内部锁是重量级锁 因为monitor锁机制依赖于底层操作系统的Mutex Lock实现,挂起线程和恢复线程都需要从用户态切换到内核态去完成,状态转换耗费的成本非常高,所以synchronized是Java语言中的一个重量级操作

synchronized 关键字

  • Intrinsic Lock是通过synchronized关键字触发的
  • synchronized修饰实例方法上,锁对象是当前的this对象
  • synchronized修饰静态方法上,锁对象是方法区中的类对象,是一个全局锁
  • synchronized修饰代码块,也就是synchronized(object){},锁对象是()中的对象
  • synchronized保证原子性, 可见性, 有序性
  • 原子性: 基于 monitorenter 和 monitorexit 字节码指令,保证同步块只有单一线程执行。
  • 可见性: synchronized 的可见性是由“对一个变量执行 unlock 操作之前,必须先把此变量同步回主内存中(执行 store、write 操作)”这条规则获得的。
  • 有序性:

具体实现

  • 在修饰代码块时,字节码层面上是通过 montiorentermonitorexit 指令来实现的锁获取与释放动作. 当线程进入到monitorenter指令后, 线程将会持有monitor对象. 退出monitorenter指令后,线程将会释放monitor对象
  • monitorenter指令: 获取monitor对象的所有权, 并会发生如下3中情况之一:
  • monitor计数器为0,意味着目前还没有被获得,那这个线程就会立刻获得然后把锁计数器+1,一旦+1,别的线程再想获取,就需要等待
  • 如果这个monitor已经拿到了这个锁的所有权,又重入了这把锁,那锁计数器就会累加,变成2,并且随着重入的次数,会一直累加 (可重入性)
  • 第三种情况就是这把锁已经被别的线程获取了,等待锁释放
  • monitorexit指令:释放对于monitor的所有权,
  • 释放过程很简单,就是讲monitor的计数器减1,如果减完以后,计数器不是0,则代表刚才是重入进来的,当前线程还继续持有这把锁的所有权
  • 如果计数器变成0,则代表当前线程不再拥有该monitor的所有权,即释放锁.
  • 在修饰方法时, JVM通过ACC_SYNCHRONIZED这个标志区分一个方法是否为同步方法. 如果有ACC_SYNCHRONIZED标志, 则会先持有方法所在的monitor对象,然后再执行方法. 在该方法执行时间,其它任何线程均无法再获取到这个monitor对象,当线程执行完该方法后,他会释放掉这个monitor对象
  • Intrinsic Lock的一些限制
  • You cannot interrupt a thread that is trying to acquire a lock.
  • You cannot specify a timeout when trying to acquire a lock.

synchronized 修饰实例方法

  • 用synchronized修饰的实例方法叫做同步实例方法, 锁对象是当前的this对象
  • 同步方法的整个方法体就是一个临界区

Example 1: 循环递增序列号生成器

//只有一个线程可以更新序列号, 保证多线程情况下序列号正常更新
public class SafeCircularSeGenerator {
	private short sequence = -1;
	public synchronized short nextSequence() {
		if (sequence >= 999) {
			sequence = 0;
		}
		else {
			sequence++:
		}
		return sequence;
	}
}

Example 2: 使用synchronized重写银行账户的transfer方法:

class Bank
{
	 private double[] accounts;
	 public synchronized void transfer(int from, int to, int amount) throws InterruptedException {
	 	 //余额不足, 等待入账
		 while (accounts[from] < amount)
			wait(); // wait on intrinsic object lock's single condition
		 accounts[from] -= amount;
		 accounts[to] += amount;
		 notifyAll(); // notify all threads waiting on the condition
	 }
	 public synchronized double getTotalBalance() { . . . }
}

synchronized 修饰代码块

  • synchronized修饰的代码块就是临界区,
  • synchronized(object),锁对象是()中的对象叫锁句柄, 是对应的锁 (可以是任何类型的Object或者this指针)
  • 具体格式如下
synchronized(锁句柄 handle) {
	//critical section
}
  • 锁句柄的变量通常采用 private final 修饰. 这是因为锁句柄变量的值一旦改变, 会导致执行同一个同步块的多线程实际上使用不同的锁, 从而产生混乱 如: private final Object lock = new Object()

Example 1: 使用sychronized代码块 实现循环递增序列号生成器

//只有一个线程可以更新序列号, 保证多线程情况下序列号正常更新
public short nextSequence() {
	synchronied(this) {
		if (sequence >= 999) {
			sequence = 0;
		}
		else {
			sequence++:
		}
		return sequence;
	}
}

Example 2: 使用synchronized代码块重写银行账户的transfer方法:

class Bank {
	private final Object lock = new Object();
	
	public void transfer(int from, int to, int amount) {
		synchronized (lock) {
			accounts[from] -= amount;
			accounts[to] += amount;
		}
		System.out.println(. . .);
	}
}

synchronized 修饰静态方法

  • synchronized 关键字可以修饰静态方法, 也就是对 类 加锁, 也叫做类锁
  • 用static修饰的同步函数使用的锁为.class文件
public class Car {

    public static synchronized void staticRuning1(Thread thread){
        System.out.println(thread.getName()+ " static car1 得到锁");
        System.out.println("------ static car1 is running ------");
        working();
        System.out.println(thread.getName()+ " static car1 释放锁");
        System.out.println();
    }
    public static synchronized void staticRuning2(Thread thread){
        System.out.println(thread.getName()+ " static car2 得到锁");
        System.out.println("------ static car2 is running ------");
        working();
        System.out.println(thread.getName()+ " static car2 释放锁");
        System.out.println();
    }

	public static void  working(){
        try{
            Thread.sleep(1000);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }
}

public class test02 {
    public static void main(String[] args) {

        //线程1 类
        Thread t1 = new Thread(){
            @Override
            public void run() {
                Car.staticRuning1(Thread.currentThread()); //同步类方法1
            }
        };
        t1.start();

        //线程2 类
        Thread t2 = new Thread(){
            @Override
            public void run() {
                Car.staticRuning2(Thread.currentThread()); //同步类方法2
            }
        };
        t2.start();
    }
}

output --- Car类不能同时访问两个静态方法
Thread-0 static car1 得到锁
------ static car1 is running ------
Thread-0 static car1 释放锁

Thread-1 static car2 得到锁
------ static car2 is running ------
Thread-1 static car2 释放锁

sychronized的可重入性

  • 内部锁是可重入锁, monitor有一个计数器记录目前线程的所有权, 为0时代表无锁, 为1时代表已被抢占, 当拥有锁的线程再次申请时, 计数器会进行加一操作
  • 当释放时, monitor会进行减一操作
  • 可重入锁的好处是可以避免一定程度的死锁情况(自己调用自己), 可以递归调用
  • Example 1: 同一类的同一方法
  • 因为方法被synchronized修饰,如果不可重入的话,无法执行递归,输出0和1代表进入了两次method方法,说明了Synchronized的可重用性。
public class SynchronizedReusing {
    public static void main(String[] args) {
        SynchronizedReusing reusing = new SynchronizedReusing();
        reusing.method(0);
    }

    public synchronized void method(int i){
        if(i==1){
            System.out.println(i++);
            return;
        }
        System.out.println(i++);
        method(i);
    }
}

Output:
0
1
Process finished with exit code 0
  • Example 2: 同一类的不同方法
  • 两个方法都被synchronized修饰,如果不可重入的话,method是无法访问method1的(锁对象都是this, 会有死锁),说明了Synchronized的可重用性
public class SynchronizedReusing {
    public static void main(String[] args) {
        SynchronizedReusing reusing = new SynchronizedReusing();
        reusing.method(0);
    }

    public synchronized void method(int i){
        System.out.println("method    " + i++);
        method1(i);
    }

    public synchronized void method1(int i){
        System.out.println("method1   " + i);
    }
}

Output:
method    0
method1   1
Process finished with exit code 0
  • 例三 不同类的方法
public class SynchronizedReusing {

    public synchronized void method(){
        System.out.println("父类的方法");
    }
}

class SynchronizedReusingSon extends SynchronizedReusing {

    @Override
    public synchronized void method() {
        System.out.println("子类的方法");
        super.method();
    }

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

Output:
子类的方法
父类的方法
Process finished with exit code 0
``

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 多线程 --- 线程同步 内部锁synchronized 的相关文章

  • 如何在itext中设置自定义颜色?

    感谢您花时间回答我的问题 我正在使用 Java 中的 iText 生成 PDF 文档 我需要将表的列标题设置为与值列中的颜色不同的颜色 我有来自 Photoshop 的颜色十六进制值 我正在使用带有块和段落的 PdfPTable 除了 Ba
  • Maven UTF-8编码问题

    当我使用两个不同的项目运行下面的代码时 我得到不同的输出 String myString T rk e Karakter Testi i String value new String myString getBytes UTF 8 Sys
  • 如果我在 JSP 中有 html 元素,那么执行顺序是什么?

    什么将执行第一个 body 元素或 head 元素 Head Body scriplet 如果我明白您的要求 JSP 文件中的每个元素都会按照代码从上到下出现的顺序进行处理
  • 具有“繁忙”线程的 threadPoolExecutor 如何被终止?

    我的问题有点复杂 让我尝试彻底解释一下 但如果您需要更多详细信息 请随时询问我 我会添加它们 我最近 通过实验 了解到 如果线程连续工作 例如 while true 循环中的整数运算 则中断线程对其没有影响 话题继续进行 就像什么都没发生一
  • 为什么从 Eclipse 导出的可运行 JAR 不起作用?

    我有一个在 Eclipse 中运行良好的项目 但是 当我将其导出到可运行的 JAR 其中包含依赖项 时 它不会运行 从控制台运行 JAR 时出现的错误是 编辑 之前没有使用 jar 运行它 INFO Loading XML bean def
  • 从 HashMap 条目列表中删除重复项

    我有一个List
  • 如何使用 hibernate 过滤器过滤 hibernate 中的实体

    我需要过滤对象列表中的实体 例如 public class Student private int id private List
  • 从 java.util.TimeZone 转换为 org.joda.DateTimeZone

    在Java中如何将一个实例转换为java util TimeZone to org joda DateTimeZone并保持夏令时 Joda Time 处于维护模式 The 乔达时间 http www joda org joda time
  • 在 IIS 中运行 Java Web 应用程序

    有人找到了在 IIS 中运行 Java Web 应用程序的方法吗 在我看来 编写一个将 Jetty 或自定义 servlet 容器与 IIS 集成的 ISAPI 插件 这个词正确吗 应该是完全可能的 这样做的好处是 许多优秀的高端 Java
  • 如何暂停程序直到按下按钮?

    我使用从 jframe 扩展的类 它有一个按钮 我在程序中使用它 我希望当在我的程序中运行 jframe 时我的整个程序暂停 直到我按下按钮 我该怎么做 in c getch 做这个 我想要一个这样的功能 通过睡眠暂停执行 http dow
  • 为什么ArrayList没有getSize()而不是size()?

    我在一些 JSP 页面中大量使用 ArrayList 我想像这样访问 ArrayList myArrayList size 但由于对象必须符合 JavaBean 标准 其中myArrayList getMyPropertyName is m
  • 使用 Gradle 构建 Kotlin + Java 9 项目

    我对 Gradle 老实说 还有 Java 9 相当陌生 我正在尝试使用 Gradle 构建一个混合了 Java 9 和 Kotlin 的简单库项目 更详细地说 Java中有一个接口 Kotlin中有一个实现 我会用 Kotlin 做所有事
  • Java 会话变量

    我听说有些人认为在会话中将信息存储在服务器上是一个坏主意 因为它不安全 因此 在多页面业务流程功能中 应用程序将数据写入数据库 然后在需要时检索信息 在会话中存储私人信息是否一定不安全 只要会话本身安全 在会话中存储属性就不存在安全风险劫持
  • 在 Scala 中创建 Java 对象

    我有一个 Java 类 Listings 我在 Java MapReduce 作业中使用它 如下所示 public void map Object key Text value Context context throws IOExcept
  • java代码的等效vb代码

    谁能告诉我这段Java代码到底做了什么 SecureRandom random SecureRandom getInstance SHA1PRNG byte bytes new byte 20 synchronized random ran
  • spring-hibernate 花费更多时间的任何原因?

    目前 我正在春季和冬眠期间从事一个项目 我来到这里 获取记录并在 JSP 中显示这些记录需要更多时间 我在各处都保留了时间戳 以查看哪里花费了更多时间 Time HomeController start 2014 07 09 18 58 5
  • 如何删除 Spring 的 RestTemplate 添加的某些 HTTP 标头?

    我在远程服务方面遇到问题 我无法控制对使用 Spring 的 RestTemplate 发送的请求进行 HTTP 400 响应 使用发送的请求curl但被接受了 所以我将它们与通过 RestTemplate 发送的内容进行了比较 特别是 S
  • 如何列出所有已加载的 Spring bean 定义文件

    在大型企业系统中 并不总是清楚在 ApplicationContext 构建期间导入了哪些文件 有没有办法列出过程中加载的所有文件 我知道如何列出加载的属性文件 但不知道导入的 bean 文件 更新示例 文件 1 applicationCo
  • 使用用户名和密码登录 LinkedIn 失败

    LinkedIn使用oauth登录其api 服务器中无法登录api 我尝试使用http请求登录linkedin并获取oauth verifier 但我得到了这样的回应 很抱歉 出现了问题 你的申请 请确保您 启用cookie并重试 或点击此
  • 尝试 Catch 性能 Java

    当捕获异常而不是进行检查时 try catch 需要多长时间 以纳秒为单位 假设消息具有用于查找的 HashMap 类型性能 try timestamp message getLongField MessageField TIMESTAMP

随机推荐

  • 蓝桥杯模拟赛 排列序列

    标题 排列序数 X星系的某次考古活动发现了史前智能痕迹 这是一些用来计数的符号 经过分析它的计数规律如下 为了表示方便 我们把这些奇怪的符号用a q代替 abcdefghijklmnopq 表示0 abcdefghijklmnoqp 表示1
  • Typecho博客搭建 实现公网访问内网站点

    文章目录 前言 1 环境安装 2 安装Typecho 3 安装cpolar内网穿透 4 固定公网地址 5 配置Typecho 前言 Typecho是一款PHP语言编写的开源博客程序 它是一个轻量级的内容管理系统 专注于博客领域 支持多用户
  • crontab命令详解,慎用crontab -r命令

    crontab命令详解 慎用crontab r命令 常用命令 常用命令 crontab e是编辑 crontab r 是删除 crontab l是查看 crontab r命令万恶 且不可恢复 切记勿用
  • Android 系统开发系列(1):Android 12 源代码下载、编译和刷机

    Android 12 正式版 已经发布 https mp weixin qq com s OiFSWEnc 0N2z7JYWTJluw 本文就带大家下载和编译最新的 Android 12 代码 本地编译的代码有下面几个好处 可以刷真机 方便
  • LeetCode 349. 两个数组的交集

    题目链接 https leetcode cn problems intersection of two arrays 思路如下 由题目可知 nums1 数组和 nums2 数组中的元素的大小都在 0 1000 0 1000
  • 《画解数据结构》(2 - 4)- AVL 树

    画解数据结构 2 4 AVL 树
  • ChatGPT的未来:研究和开发的领域,渴望带来哪些变革?

    ChatGPT作为一种基于自然语言处理 NLP 技术的人工智能机器人 其未来研究和开发的领域包括但不限于以下几个方面 1 对话质量的提高 ChatGPT目前仍然存在一些语义理解和生成上的问题 未来的研究和开发将会着重于解决这些问题 提高Ch
  • 网站信息收集及nmap的下载使用

    本专栏是笔者的网络安全学习笔记 一面分享 同时作为笔记 前文链接 WAMP DVWA sqli labs 搭建 burpsuite工具抓包及Intruder暴力破解的使用 目录扫描 请求重发 漏洞扫描等工具的使用 信息收集 我们在对一个网站
  • Flash Player 8 中的安全性更改

    要求 用户级别 中级 Macromedia 已更改了 Flash Player 8 中应用于本地 Flash 内容的安全模型 默认情况下 从用户本地文件系统而不是通过 HTTP 运行的 Flash 应用程序在 Flash Player 8
  • petalinux添加AD9361驱动

    文章目录 一 准备工具 二 步骤 需要 petalinux2016 2 包含AD9361驱动的Linux内核 xcomm zynq 4 4 一 准备工具 ADI提供的AD9361Linux驱动 https wiki analog com r
  • tomcat高并发下优化详解及连接数和线程池

    高并发环境下 我知道优化配置tomcat 对连接数和线程池作修改 最重要的是connector的协议Http Connector使用NIO 而不是默认的AJP Connector 当时也没有仔细研究其原理 现在来为以上这些设置做一下剖析 要
  • Java private类构造函数笔记

    前言 最近在看Android源码的时候遇到了private类构造函数 于是写了一个测试小程序 帮助理解 代码 class TestPrivate private TestPrivate System out println TestPriv
  • PAT题解——Basic Level——1094 谷歌的招聘

    题目链接 https pintia cn problem sets 994805260223102976 problems 1071785997033074688 题面 本题要求你编程解决一个更通用的问题 从任一给定的长度为 L 的数字中
  • 设计模式(十五)中介者模式

    版权声明 转载必须注明本文转自晓 晨的博客 http blog csdn net niunai112 目录 目录 导航 前言 例子 总结 优点 缺点 Git地址 导航 设计模式之六大设计原则 设计模式 一 单例模式 设计模式 二 工厂模式
  • appuploader不是开发者账号

    Appuploader是一款可以帮助开发者上传iOS应用到Apple App Store的工具 很多开发者都知道 在上传应用到App Store之前 需要创建开发者账号并获得苹果官方的认证才能进行上传 但是 有些开发者可能并不想去注册开发者
  • JS生成uuid的四种方法

    在开发过程中 有时候需要js生成全局唯一标识符 在java中可以使用uuid 但是JS中没有现成的函数 总结了一下 JS生成唯一标识符的几种方法 第一种 function guid return xxxxxxxx xxxx 4xxx yxx
  • 阈值分割方法总结

    阈值是界限的意思 阈值分割就是以一个合适的像素值作为界限将图像处理成高对比度 容易识别的图像的一种方法 threshold double cv threshold cv InputArray src 输入图像 cv OutputArray
  • 网易实况足球获取服务器信息,网易实况足球国际服和国服数据互通吗_游戏评价介绍...

    网易实况足球国际服和国服数据互通吗 游戏评价介绍 2018 03 14 17 34 11 网易实况足球在3月13日正式命名为实况王者集结 其实这款手游在国际服已经上线了 很多喜欢足球的小伙伴也玩过了这款游戏 很多小伙伴因为外国服务器不稳定想
  • Flask入门学习教程

    Flask学习 文章目录 Flask学习 1 简介 2 安装 3 最小的应用 4 路由 5 变量规则 6 URL构建 7 HTTP方法 8 文件存放 9 渲染模板 10 Request对象 11 Cookie 12 会话Session 实例
  • Java 多线程 --- 线程同步 内部锁synchronized

    Java 多线程 线程同步 内部锁synchronized Intrinsic Lock Monitor synchronized 关键字 synchronized 修饰实例方法 synchronized 修饰代码块 synchronize