Java中synchronized同步锁用法及作用范围

2023-11-06

Java 中的 synchronized 关键字可以在多线程环境下用来作为线程安全的同步锁。本文主要对 synchronized 的作用,以及其有效范围进行讨论。
Java中的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的。

synchronized 关键字主要有以下几种用法:
- 非静态方法的同步;
- 静态方法的同步;
- 代码块。

下面分对象锁和类锁来分别说明 synchronized 用法:

对象锁

非静态方法使用 synchronized 修饰的写法,修饰实例方法时,锁定的是当前对象:

    public synchronized void test(){
        // TODO
    }

代码块使用 synchronized 修饰的写法,使用代码块,如果传入的参数是 this,那么锁定的也是当前的对象:

    public void test(){
        synchronized (this) {
            // TODO
        }
    }

下面通过例子来说明对象锁:
定义一个类,方法如下,将 count 自减,从 5 到 0:

public class TestSynchronized {

    public synchronized void minus() {
        int count = 5;
        for (int i = 0; i < 5; i++) {
            count--;
            System.out.println(Thread.currentThread().getName() + " - " + count);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
            }
        }
    }

}

测试调用方法如下:

public class Run {

    public static void main(String[] args) {

        final TestSynchronized test = new TestSynchronized();

        Thread thread1 = new Thread(new Runnable() {

            @Override
            public void run() {
                test.minus();
            }
        });

        Thread thread2 = new Thread(new Runnable() {

            @Override
            public void run() {
                test.minus();
            }
        });

        thread1.start();
        thread2.start();

    }

}

两个线程 thread1 和 thread2,同时访问对象的方法,由于该方法是 synchronized 关键字修饰的,那么这两个线程都需要获得该对象锁,一个获得后另一个线程必须等待。所以我们可以猜测运行结果应该是,一个线程执行完毕后,另一个线程才开始执行,运行例子,输出打印结果如下:

Thread-0 - 4
Thread-0 - 3
Thread-0 - 2
Thread-0 - 1
Thread-0 - 0
Thread-1 - 4
Thread-1 - 3
Thread-1 - 2
Thread-1 - 1
Thread-1 - 0

(另:thread1 和 thread2 谁先执行并不一定)
本例对于对象锁进行了基础的解释。但是对象锁的范围是怎样的,对象的某个同步方法被一个线程访问后,其他线程能不能访问该对象的其他同步方法,以及是否可以访问对象的其他非同步方法呢,下面对两种进行验证:

对两个同步方法两个线程的验证:
修改类如下,加入 minus2() 方法,和 minus() 方法一样:

package com.test.run;

public class TestSynchronized {

    public synchronized void minus() {
        int count = 5;
        for (int i = 0; i < 5; i++) {
            count--;
            System.out.println(Thread.currentThread().getName() + " - " + count);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
            }
        }
    }

    public synchronized void minus2() {
        int count = 5;
        for (int i = 0; i < 5; i++) {
            count--;
            System.out.println(Thread.currentThread().getName() + " - " + count);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
            }
        }
    }

}

测试调用如下,两个线程访问不同的方法:

public class Run {

    public static void main(String[] args) {

        final TestSynchronized test = new TestSynchronized();

        Thread thread1 = new Thread(new Runnable() {

            @Override
            public void run() {
                test.minus();
            }
        });

        Thread thread2 = new Thread(new Runnable() {

            @Override
            public void run() {
                test.minus2();
            }
        });

        thread1.start();
        thread2.start();
    }

}

输出结果如下:

Thread-0 - 4
Thread-0 - 3
Thread-0 - 2
Thread-0 - 1
Thread-0 - 0
Thread-1 - 4
Thread-1 - 3
Thread-1 - 2
Thread-1 - 1
Thread-1 - 0

可以看到,某个线程得到了对象锁之后,该对象的其他同步方法是锁定的,其他线程是无法访问的。
下面看是否能访问非同步方法:
修改类代码如下,将 minus2() 的 synchronized 修饰去掉,代码如下:

public class TestSynchronized {

    public synchronized void minus() {
        int count = 5;
        for (int i = 0; i < 5; i++) {
            count--;
            System.out.println(Thread.currentThread().getName() + " - " + count);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
            }
        }
    }

    public void minus2() {
        int count = 5;
        for (int i = 0; i < 5; i++) {
            count--;
            System.out.println(Thread.currentThread().getName() + " - " + count);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
            }
        }
    }

}

测试调用的类不变,如下:

public class Run2 {

    public static void main(String[] args) {

        final TestSynchronized test = new TestSynchronized();

        Thread thread1 = new Thread(new Runnable() {

            @Override
            public void run() {
                test.minus();
            }
        });

        Thread thread2 = new Thread(new Runnable() {

            @Override
            public void run() {
                test.minus2();
            }
        });

        thread1.start();
        thread2.start();

    }

}

执行结果如下:

Thread-1 - 4
Thread-0 - 4
Thread-1 - 3
Thread-0 - 3
Thread-1 - 2
Thread-0 - 2
Thread-1 - 1
Thread-0 - 1
Thread-1 - 0
Thread-0 - 0

可以看到,结果是交替的,说明线程是交替执行的,说明如果某个线程得到了对象锁,但是另一个线程还是可以访问没有进行同步的方法或者代码。进行了同步的方法(加锁方法)和没有进行同步的方法(普通方法)是互不影响的,一个线程进入了同步方法,得到了对象锁,其他线程还是可以访问那些没有同步的方法(普通方法)。当获取到与对象关联的内置锁时,并不能阻止其他线程访问该对象,当某个线程获得对象的锁之后,只能阻止其他线程获得同一个锁。

类锁

类锁需要 synchronized 来修饰静态 static 方法,写法如下:

    public static synchronized void test(){
        // TODO
    }

或者使用代码块,需引用当前的类:

    public static void test(){
        synchronized (TestSynchronized.class) {
            // TODO
        }
    }

举例说明类锁的作用:

public class TestSynchronized {

    public static synchronized void minus() {
        int count = 5;
        for (int i = 0; i < 5; i++) {
            count--;
            System.out.println(Thread.currentThread().getName() + " - " + count);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
            }
        }
    }

}

测试调用类如下:

public class Run {

    public static void main(String[] args) {

        Thread thread1 = new Thread(new Runnable() {

            @Override
            public void run() {
                TestSynchronized.minus();
            }
        });

        Thread thread2 = new Thread(new Runnable() {

            @Override
            public void run() {
                TestSynchronized.minus();
            }
        });

        thread1.start();
        thread2.start();

    }

}

输出结果如下:

Thread-0 - 4
Thread-0 - 3
Thread-0 - 2
Thread-0 - 1
Thread-0 - 0
Thread-1 - 4
Thread-1 - 3
Thread-1 - 2
Thread-1 - 1
Thread-1 - 0

可以看到,类锁和对象锁其实是一样的,由于静态方法是类所有对象共用的,所以进行同步后,该静态方法的锁也是所有对象唯一的。每次只能有一个线程来访问对象的该非静态同步方法。
类锁的作用和对象锁类似,但是作用范围是否和对象锁一致呢,下面看对象锁和类锁是否等同:
修改类,两个同步方法,其中一个是静态的:

public class TestSynchronized {

    public static synchronized void minus() {
        int count = 5;
        for (int i = 0; i < 5; i++) {
            count--;
            System.out.println(Thread.currentThread().getName() + " - " + count);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
            }
        }
    }

    public synchronized void minus2() {
        int count = 5;
        for (int i = 0; i < 5; i++) {
            count--;
            System.out.println(Thread.currentThread().getName() + " - " + count);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
            }
        }
    }

}

测试调用类如下,静态方法直接用类调用,实例方法由对象来调用:

public class Run {

    public static void main(String[] args) {

        final TestSynchronized test = new TestSynchronized();

        Thread thread1 = new Thread(new Runnable() {

            @Override
            public void run() {
                TestSynchronized.minus();
            }
        });

        Thread thread2 = new Thread(new Runnable() {

            @Override
            public void run() {
                test.minus2();
            }
        });

        thread1.start();
        thread2.start();

    }

}

运行结果:

Thread-1 - 4
Thread-0 - 4
Thread-0 - 3
Thread-1 - 3
Thread-0 - 2
Thread-1 - 2
Thread-0 - 1
Thread-1 - 1
Thread-1 - 0
Thread-0 - 0

可以看到两个线程是交替进行的,也就是说类锁和对象锁是不一样的锁,是互相独立的。

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

Java中synchronized同步锁用法及作用范围 的相关文章

  • Hashmap并发问题

    我有一个哈希图 出于速度原因 我希望不需要锁定 假设我不介意过时的数据 同时更新它和访问它会导致任何问题吗 我的访问是获取 而不是迭代 删除是更新的一部分 是的 这会导致重大问题 一个例子是向散列映射添加值时可能发生的情况 这可能会导致表重
  • 使用 HttpUrlConnection Android 将 base64 编码的图像发送到服务器

    我正在尝试使用 HttpUrlConnection 将 base64 编码的图像发送到服务器 我遇到的问题是大多数图像均已成功发送 但有些图像会生成 FileNotFound 异常 我的图像编码代码可以在下面找到 public static
  • MongoTemplate upsert - 从 pojo 进行更新的简单方法(哪个用户已编辑)?

    这是一个简单的 pojo public class Description private String code private String name private String norwegian private String en
  • jvm 次要版本与编译器次要版本

    当运行使用具有相同主要版本但次要版本高于 JVM 的 JDK 编译的类时 JVM 会抛出异常吗 JDK 版本并不重要 类文件格式版本 http blogs oracle com darcy entry source target class
  • 在哪里可以获得有关 Java FitNesse 和 Slim 的一些教程? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • Java、Oracle 中索引处缺少 IN 或 OUT 参数:: 1 错误

    您好 我使用 Netbeans 8 0 2 和 Oracle 11g Express Edition 在 JSF 2 2 中编写了一个图书馆管理系统 我有几个名为 书籍 借阅者 等的页面 以及数据库中一些名为相同名称的表 我的问题是这样的
  • 如何开始使用 Chainsaw for Log4j?

    我想开始使用 Chainsaw v2 几乎没有关于它的信息 我只找到了this http www velocityreviews com forums t140105 help using chainsaw for log4j html 但
  • 使用 ChannelExec 的命令未执行 - Jsch

    我正在使用 Jsch 在服务器中创建一个文件并执行一些命令 对于文件创建 它工作正常 但是对于命令执行 则不然 它保持状态 1 仍在处理它 并永远保持该状态 这种情况发生在 shell 执行或我尝试成为 root 时 请按照以下方法操作 p
  • 哪个 Swing 布局管理器可以获得我想要的布局?

    我正在尝试按照这个模型制作一个基本的登录菜单 我决定将整个菜单放入 JPanel 中 以便在连接成功后我可以切换到另一个面板 所以我决定使用 Borderlayout 将标题放在北区 将连接按钮放在南区 我将边框布局的中心本身设置为面板 我
  • Java 正则表达式 - 字母数字,最多一个连字符,句点或下划线,七个字符长

    我是 Java 正则表达式工具的新手 尽管它们潜力巨大 但我很难完成这项任务 我想编写一个正则表达式来验证遵循以下语法的输入字符串 小写字母和数字的任意组合 仅一个下划线 一个破折号或一个句号 无其他特殊字符 最小长度为 5 我想出了以下解
  • 请参阅 Java EE eclipse 调试中的 POST 参数

    我在调试 Java EE 方面没有经验 我更像是一个 javascript 人 我需要查看哪些 HTTP POST 参数到达服务器端 我在表单将其操作指向的 jsp 文件中放置了一个断点 现在我在调试变量窗口中找不到 POST 内容 他们在
  • 正则表达式获取字符串中的第一个数字和其他字符

    我是正则表达式的新手 想知道如何才能只获取字符串中的第一个数字 例如100 2011 10 20 14 28 55 在这种情况下 我希望它返回100 但该数字也可以更短或更长 我在想类似的事情 0 9 但它单独获取每个数字 100 2001
  • 如何拦截 REST 端点以接收所有标头?

    我当前的代码是 Path login RequestScoped public class LoginResource GET SecurityChecked public Response getUser HeaderParam AUTH
  • 字符串池可以包含两个具有相同值的字符串吗? [复制]

    这个问题在这里已经有答案了 字符串池可以包含两个具有相同值的字符串吗 String str abc String str1 new String abc Will the second statement with new operator
  • 所有平台上的java

    如果您想用 java 为 Windows Mac 和 Linux 编写桌面应用程序 那么所有这些代码都相同吗 您只需更改 GUI 即可使 Windows 应用程序更像 Windows 等等 如果不深入细节 它是如何工作的 Java 的卖点之
  • 异步迭代器

    我有以下代码 while slowIterator hasNext performLengthTask slowIterator next 由于迭代器和任务都很慢 因此将它们放入单独的线程中是有意义的 这是对迭代器包装器的快速而肮脏的尝试
  • Jenkins 的代码覆盖率 [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • JSch中如何设置文件类型和文件传输模式?

    我使用 Apache Common NetFTPClient并设置了我的ftpClient在上传文件之前使用如下所示的方法 ftpClient setFileType FTP BINARY FILE TYPE ftpClient setFi
  • 如何在Java媒体框架中学习.wav持续时间?

    我正在尝试使用 java 媒体框架将 mov 文件与 wav 文件合并 因此我需要知道它们的持续时间 我怎样才能做到这一点 任何想法 将不胜感激 您可以使用以下方式了解声音文件的持续时间 即 VitalyVal 的第二种方式 import
  • 使用 JAD 反编译 java - 限制

    我正在尝试使用 Java 中的 JAD 反编译几个 jar 文件 我也尝试过 JD GUI 但运气更差 但出现了很多错误 一种类型 易于修复 似乎是内部类 但我也发现了这段代码 static int SWITCH TABLE atp com

随机推荐

  • GCC学习笔记

    学习GCC编译器的推荐图书是 An Introduction to GCC 有中文版和英文版 可下载 和Using GCC gcc的官方手册 gcc默认是用GNU C的标准编译程序的 如果要指定用ANSI标准时 gcc ansi 指定用c9
  • 国产化芯片FM33G0系列之低功耗串口

    七 低功耗串口 首先 先看官方例程中的LP串口配置 void LPUart Init void LPUART SInitTypeDef init para RCC PERCLK SetableEx LPUFCKEN ENABLE LPUAR
  • 新书赠送

    公众号 机器学习与AI生成创作 后台回复 168 即可参与免费寄送图书 因果推断与机器学习 活动 活动截止日期为 2023 01 05 晚上10点 公众号后台回复 168 建议选中复制 随着大数据时代的来临 机器学习技术突飞猛进 并且在人类
  • 二阶段提交协议(2PC)

    模型 核心 1 引入了一个事务协调者 TC 只有它有超时机制 在一定时间内没有收到参与者的消息则默认失败 以此满足了安全性 2 在真正的提交操作前 增加了一个准备 prepare 阶段 收集业务结点是否有能力进行提交 准备阶段就是开启一个事
  • Kudu :主键冲突 status=Already present: key already present

    1 美图 2 背景 2 1 kudu单个主键 CREATE TABLE one key id string name string sex string age string PRIMARY KEY id PARTITION BY HASH
  • 虚拟机 关闭防火墙

    关闭防火墙 1 结果展示 2 查看防火墙状态 3 关闭防火墙 4 关闭本次 当前 防火墙 1 结果展示 注意小伙伴 如果指令不太会 可以使用 Tab进行补充 2 查看防火墙状态 systemctl status firewalld 3 关闭
  • 召唤神龙~让我们来看看源码及简单的修改分析,讲的很清楚哦

    最近召唤神龙好火哈哈哈哈哈哈哈 先给大家一个官方正版链接 召唤神龙 在给大家一套源码 这个源码不能本地运行 可以简单搭个服务器环境就可以啦 https gitee com bendantada long 这套源码里 最核心的逻辑在asset
  • matlab频域滤波gscale,7.图像增强—频域滤波 - 数字图像处理实验报告.doc

    7 图像增强 频域滤波 数字图像处理实验报告 doc 计算机与信息工程学院验证性实验报告 专业 通信工程 年级 班级 2011级6节实验目的 1 掌握怎样利用傅立叶变换进行频域滤波 2 掌握频域滤波的概念及方法 3 熟练掌握频域空间的各类滤
  • uniapp for循环显示的数据,无法动态修改数据的解决方案

    重点 替换数组中的指定位置的元素 把修改后的元素赋值给新声明的变量 进行list splice 0 1 new data splice传入的第一个参数代表要替换的数组下标 第二个参数代表替换的个数 第三个参数代表要替换的元素
  • ctfshow web-2(sql注入)

    原题 ctf show 因为本人也是刚学sql注入 所以会尽可能的写的详细一点 1 判断是否可以进行sql注入 关于进行sql注入的方法有三种 第一种 整型注入 单引号字符型注入 双引号字符型注入 我们首先使用万能账密的方式来一个一个试属于
  • 算法训练营第四十四天(9.6)

    目录 Leecode 647 回文子串 Leecode 516 最长回文子序列 Leecode 647 回文子串 题目地址 力扣 LeetCode 官网 全球极客挚爱的技术成长平台 题目类型 回文 class Solution public
  • linux 程序/项目 一直运行/不退出

    首先我们需要安装screen sudo apt get install screen 完成后我们输入screen 进入screen的提示信息后再按回车来正式进入 接下来进入对应目录输入要运行的命令 cd test python3 test
  • 单元测试中的打桩技术

    一 桩是什么 桩 或称桩代码 是指用来代替关联代码或者未实现代码的代码 如果用函数B1来代替B 那么 B称为原函数 B1称为桩函数 打桩就是编写或生成桩代码 二 打桩的用途 打桩的目的主要有 隔离 补齐 控制 隔离是指将测试任务从产品项目中
  • JavaScript中回调函数的this使用

    假设某类有函数 somefunction a b c 当你用对象调用函数 someobject somefunction a b c 此时this默认为someobject 这个很简单 麻烦的是回调函数 const callback som
  • 【满分】【华为OD机试真题2023 JAVA&JS】统一限载货物数最小值

    华为OD机试真题 2023年度机试题库全覆盖 刷题指南点这里 统一限载货物数最小值 知识点二分查找 时间限制 1s 空间限制 64MB 限定语言 不限 题目描述 火车站附近的货物中转站负责将到站货物运往仓库 小明在中转站负责调度2K辆中转车
  • EEG开发进度

    目前的EEG项目已经可以说基本宣告完成了
  • layui中关于table表格reload后有缓存问题的解决办法

    问题描述 我在父窗口的table中的数据渲染方式不是异步请求后端 而是直接从后端加载数据 渲染到模板的时候直接渲染的 也就是下图中的data tableDatas 如下图 表格渲染 table render elem mytable tex
  • YOLO目标检测标签的格式解析

    一 YOLO标签的格式
  • APP 快速开发教程(安卓)

    前言 本篇博客从开发的角度来介绍如何开发一个Android App 需要说明一点是 这里只是提供一个如何开发一个app的思路 并不会介绍很多技术上的细节 从整个大局去把握如何去构思一个app的开发 让你对独立开发一款app的时候有个理解 如
  • Java中synchronized同步锁用法及作用范围

    Java 中的 synchronized 关键字可以在多线程环境下用来作为线程安全的同步锁 本文主要对 synchronized 的作用 以及其有效范围进行讨论 Java中的对象锁和类锁 java的对象锁和类锁在锁的概念上基本上和内置锁是一