只有真正理解了Java中对象是什么,才能理解这个关键字是什么意思

字面解释

Java Guide中如此解释:

synchronized 关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

测试

但是这句话很多时候是有误导性的,synchronized这个关键字并不能保证同一时间只有一个线程访问,确切说,如果是用同一个对象调用方法的时候,方法的确是同一时间只能有一个线程访问:

class Solution {

    public static void main(String[] args) throws Exception {
Node node1 = new Node();
Node node2 = new Node();
new Thread(node1::test).start();
new Thread(node1::test).start(); System.out.println("主线程结束"); }
} class Node { public synchronized void test() {
System.out.println("!!!!!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成!!!");
}
}

输出为

!!!!!!

主线程结束

完成!!!

!!!!!!

完成!!!

没有问题,一个时间走一个线程。

但是,如果你用两个不同的对象调用同一个方法,synchronized关键字是无用的:

class Solution {

    public static void main(String[] args) throws Exception {
Node node1 = new Node();
Node node2 = new Node();
new Thread(node1::test).start();
new Thread(node2::test).start(); System.out.println("主线程结束"); }
} class Node { public synchronized void test() {
System.out.println("!!!!!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成!!!");
}
}

输出为:

!!!!!!

!!!!!!

主线程结束

完成!!!

完成!!!

这时候为什么同步监视器没用了呢?原因在于,synchronized加在普通方法的时候,当一个线程访问这个方法的时候,持有的是当前类实例对象的同步监视器,也就是说当node1调用test()的时候,node1本身被他自己的线程new Thread持有了。这时候如果node2再次调用test(),由于node2自己没有被任何线程持有,所以synchronized此时是失效的。如果是用node1对象调用调用了test(),这时node1被线程1持有以后,第二个new出来的线程是无法持有node1的,就只能等待。

所以一切都基于对Java“对象”这个概念的理解。

当synchronized加在静态方法上的时候,线程持有的是类的Class对象:

class Solution {

    public static void main(String[] args) throws Exception {
Node node1 = new Node();
Node node2 = new Node();
// new Thread(node1::test).start();
// new Thread(node2::test).start();
new Thread(() -> {
node1.test();
}).start(); new Thread(() -> {
node2.test();
}).start(); System.out.println("主线程结束"); }
} class Node { public static synchronized void test() {
System.out.println("!!!!!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成!!!");
}
}

这种情况下,即便两次调用test()方法属不同的对象,但是,由于线程持有的clazz对象是单例的,所以依然达到了同步的效果:

!!!!!!

主线程结束

完成!!!

!!!!!!

完成!!!

对于同步代码块,也是一样的操作,一般情况下我们会将同步代码块中传入this,就类似于将方法调用者的对象作为了同步监视器,这样的操作在单例模式的基础上是可以达到同步效果的。

Spring为什么是单例的

所以从这里可以看出,Spring为什么会把组件都设置为单例的呢?一方面Spring中各个组件的功能实现不需要多实例,请求和请求之间方法调用多为无状态的,当多个查询数据库的请求调用到DAO层的时候,自然有mybatis帮我们实现代理类的不同对象去隔离不同请求的数据,在Controller和Service构建多实例对象浪费内存空间;另一方面,单例是有助于实现同步效果的。当我们在控制器接口的方法声明为synchronized,这时用这个controller调用这个方法的时候,默认是用controller对象自己作为同步监视器的,而controller对象自然是满足单例的,这样就自然满足了同步的要求。

对synchronized的理解和Spring为什么是单例的的更多相关文章

  1. spring如何解决单例循环依赖问题?

    更多文章点击--spring源码分析系列 1.spring循环依赖场景2.循环依赖解决方式: 三级缓存 1.spring循环引用场景 循环依赖的产生可能有很多种情况,例如: A的构造方法中依赖了B的实 ...

  2. Spring对象类型——单例和多例

    由于看淘淘商城的项目,涉及到了项目中处理spring中bean对象的两种类型,分别是单例和多例,就在此记录一下,方便加深理解,写出更加健壮的代码. 一.单例和多例的概述 在Spring中,bean可以 ...

  3. Spring bean 和单例bean的线程安全

    Bean的作用域 Spring 3中为Bean定义了5中作用域,分别为singleton(单例).prototype(原型).request.session和global session,5种作用域说 ...

  4. Spring中的单例一二

    Spring框架很好的帮助我们创建和管理dao.bean.service.action等对象, 但是它创建的对象是单例呢还是多例,又有哪些区别以及为什么 1.在Spring中默认创建的是单例模式,简单 ...

  5. 转:【Spring MVC Controller单例陷阱】

    http://lavasoft.blog.51cto.com/62575/1394669/ Spring MVC Controller默认是单例的: 单例的原因有二:1.为了性能.2.不需要多例. 1 ...

  6. Spring MVC Controller单例陷阱

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://lavasoft.blog.51cto.com/62575/1394669 Spr ...

  7. 不使用synchronized和lock 锁实现线程安全单例

    单例实现方式一,锁机制 public class Singleton { private static Singleton singleton=null; public Singleton() { } ...

  8. spring中的单例和多例

    单例 对象在整个系统中只有一份,所有的请求都用一个对象来处理,如service和dao层的对象一般是单例的. 为什么使用单例:因为没有必要每个请求都新建一个对象的时候,浪费CPU和内存. 多例 对象在 ...

  9. Spring IOC 容器源码分析 - 创建单例 bean 的过程

    1. 简介 在上一篇文章中,我比较详细的分析了获取 bean 的方法,也就是getBean(String)的实现逻辑.对于已实例化好的单例 bean,getBean(String) 方法并不会再一次去 ...

  10. Spring单例Bean和线程安全

    Spring的bean默认都是单例的,这些单例Bean在多线程程序下如何保证线程安全呢?例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,引入Spring框 ...

随机推荐

  1. MARKDOWN操作

    我是中国人 我是中国人 字体 Hello,World! Hello,World! 引用 选择狂神说 分割线 图片 图片2 超链接 点击转到链接 列表 A B C D 表格               ...

  2. SQL开窗函数用法

    开窗函数分类: 根据使用的目的,开窗函数可以分为两类:聚合开窗函数和排序开窗函数. 下面主要解析四种常用的排序开窗函数: 1.ROW_NUMBER() OVER () : 对相等的值不进行区分,序号连 ...

  3. global 函数

    x = 15 # 全局变量Gdef func_a(): print(x)def func_b(): print(x)def func_c(): global x # 在定义函数内声明x为全局变量后,才 ...

  4. 转载-Shell脚本中字符串截取功能

    在Shell脚本编写中,有几个地方都是要用到字符串截取的功能,那将这块的内容进行下记录: 1.字符串变量的截取操作 对字符串变量的截取操作一般都是通过${操作符}的方式进行 1)从指定位置index截 ...

  5. 解决Delphi报Range check error错误

    没有深入研究,大体是Debug下编译的运行就报错,Release下编译的正常. 后来发现Debug模式下会打开越界检查. Project--> Option -->Delphi Compl ...

  6. 生成19位long型唯一数字id

    /** * 生成19位long型唯一数字id * @return */ public static long GetLong19UUID() { // String nanoRandom = Syst ...

  7. Keil51单片机解决数字显示不稳的问题

    Keil51单片机解决数字显示不稳的问题 数字显示不稳,就是我们人眼的特点决定的,0.1秒的残留现象,低于这个值人眼发现不了其中变化,大于这个值就会出现同一个数字闪烁的现象.解决的方法就是所有数字,第 ...

  8. Centos 8 安装zabbix 爬坑

    1.安装mininal 8 2.配置静态网络BOOTPROTO=staticIPADDR=192.168.2.1NETMASK=255.255.255.0GATEWAY=192.168.2.200DN ...

  9. HDMI基础知识

    小插曲: HDMI代表TV阵营(2002年发布,抢占市场先机) DP1.2/1.4/2.0代表电脑阵营(2006年发布,电脑厂家不想受制于TV) 由来: HDMI(High-Definition Mu ...

  10. ref、reactive、toRef、toRefs使用与区别

    reactive 传参:reactive(arg),arg只能是对象 arg为普通对象 返回响应式对象,不管层级多深,都能响应 使用:获取数据值的时候直接获取,不需要加.value 特点:解构.扩展运 ...