首页 文章 精选 留言 我的

精选列表

搜索[面试],共5090篇文章
优秀的个人博客,低调大师

27道高频Spring面试题,你能答对几个?

1、什么是Spring框架,Spring框架有哪些主要模块Spring框架是一个为Java应用程序开发提供综合、广泛的基础性支持的Java平台。 Spring帮助开发者解决了开发中基础性的问题,使得开发人员可以专注于应用程序的开发。 Spring框架本身也是按照设计模式精心打造的,这使得我们可以在开发环境中安心地集成Spring框架,不必担心Spring是如何在后台工作的。2、使用Spring框架能带来哪些好处下面列举了一些使用Spring框架带来的主要好处。(1)Dependency Injection(DI)使得构造器和JavaBean properties文件中的依赖关系一目了然。 (2)与EJB容器相比较,IoC容器更加趋向于轻量级。这样一来使用IoC容器在有限的内存和CPU资源的情况下进行应用程序的开发和发布就变得十分有利。 (3)Spring并没有闭门造车,Spring利用了已有的技术,比如ORM框架、logging框架、J2EE、Quartz和JDK Timer,以及其他视图技术。 (4)Spring框架是按照模块的形式来组织的。由包和类的编号就可以看出其所属的模块,开发者只需选用需要的模块即可。 (5)要测试一个用Spring开发的应用程序十分简单,因为测试相关的环境代码都已经囊括在框架中了。更加简单的是,利用JavaBean形式的POJO类,可以很方便地利用依赖注入来写入测试数据。 (6)Spring的Web框架也是一个精心设计的Web MVC框架,为开发者在Web框架的选择上提供了一个除主流框架(比如Struts)和过度设计的、不流行Web框架以外的选择。 (7)Spring提供了一个便捷的事务管理接口,适用于小型的本地事务处理(比如在单DB的环境下)和复杂的共同事务处理(比如利用JTA的复杂DB环境)。3、什么是控制反转(IoC),什么是依赖注入(1)控制反转是应用于软件工程领域的,在运行时被装配器对象用来绑定耦合对象的一种编程技巧,对象之间的耦合关系在编译时通常是未知的。在传统的编程方式中,业务逻辑的流程是由应用程序中早已被设定好关联关系的对象来决定的。在使用控制反转的情况下,业务逻辑的流程是由对象关系图来决定的,该对象关系图由装配器负责实例化,这种实现方式还可以将对象之间的关联关系的定义抽象化。绑定的过程是通过“依赖注入”实现的。 (2)控制反转是一种以给予应用程序中目标组件更多控制为目的设计范式,并在实际工作中起到了有效的作用。 (3)依赖注入是在编译阶段尚未知所需的功能是来自哪个的类的情况下,将其他对象所依赖的功能对象实例化的模式。这就需要一种机制来激活相应的组件以提供特定的功能,所以依赖注入是控制反转的基础。否则如果在组件不受框架控制的情况下,框架又怎么知道要创建哪个组件呢?4、在Java中依赖注入有哪些方式(1)构造器注入。 (2)Setter方法注入。 (3)接口注入。5、BeanFactory和ApplicationContext有什么区别BeanFactory可以理解为含有Bean集合的工厂类。BeanFactory 包含了Bean的定义,以便在接收到客户端请求时将对应的Bean实例化。 BeanFactory还能在实例化对象时生成协作类之间的关系。此举将Bean自身从Bean客户端的配置中解放出来。BeanFactory还包含Bean生命周期的控制,调用客户端的初始化方法(Initialization Method)和销毁方法(Destruction Method)。 从表面上看,ApplicationContext如同BeanFactory一样具有Bean定义、Bean关联关系的设置及根据请求分发Bean的功能。但ApplicationContext在此基础上还提供了其他功能。 (1)提供了支持国际化的文本消息。 (2)统一的资源文件读取方式。 (3)已在监听器中注册的Bean的事件。 以下是三种较常见的 ApplicationContext 实现方式。 (1)ClassPathXmlApplicationContext:从ClassPath的XML配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得。 ApplicationContext context = new ClassPathXmlApplicationContext(“application.xml”); (2)FileSystemXmlApplicationContext :由文件系统中的XML配置文件读取上下文。 ApplicationContext context = new FileSystemXmlApplicationContext(“application.xml”); (3)XmlWebApplicationContext:由Web应用的XML文件读取上下文。6、Spring提供几种配置方式来设置元数据Spring提供以下三种配置方式来设置元数据:(1)基于XML的配置。 (2)基于注解的配置。 (3)基于Java的配置。7、如何使用XML配置方式配置Spring在Spring框架中,依赖和服务需要专门的配置文件实现,一般用XML格式的配置文件。这些配置文件的格式采用公共的模板,由一系列的Bean定义和专门的应用配置选项组成。 Spring XML配置的主要目的是使所有的Spring组件都可以用XML文件的形式来进行配置。这意味着不会出现其他的Spring配置类型(比如声明配置方式或基于Java Class的配置方式)。 Spring的XML配置方式是使用被Spring命名空间所支持的一系列的XML标签来实现的。Spring主要的命名空间有context、beans、jdbc、tx、aop、mvc和aso。例如: <beans> <!-- JSON Support --> <bean name="viewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver"/> <bean name="jsonTemplate" class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/> <bean id="restTemplate" class="org.springframework.web.client.RestTemplate"/> </beans> 下面这个web.xml仅配置了DispatcherServlet,最简单的配置便能满足应用程序配置运行时组件的需求。 <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app> 8、Spring提供哪些配置形式Spring对Java配置的支持是由@Configuration注解和@Bean注解来实现的。由@Bean注解的方法将会实例化、配置和初始化一个新对象,这个对象将由Spring的IoC容器来管理。@Bean声明所起到的作用与元素类似。被@Configuration所注解的类则表示这个类的主要目的是作为Bean定义的资源。被@Configuration声明的类可以通过在同一个类内部调用@bean方法来设置嵌入Bean的依赖关系。最简单的@Configuration 声明类请参考下面的代码: @Configuration publicclass AppConfig{ @Bean public MyService myService() { return new MyServiceImpl(); } } 与上面的@Beans配置文件相同的XML配置文件如下: <beans> <bean id="myService" class="com.gupaoedu.services.MyServiceImpl"/> </beans> 上述配置方式的实例化方式如下: publicstaticvoid main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); } 要使用组件扫描,仅需用@Configuration进行注解即可: @Configuration @ComponentScan(basePackages = "com.gupaoedu") publicclass AppConfig { } 在上面的例子中,com.gupaoedu包首先会被扫描到,然后在容器内查找被@Component 声明的类,找到后将这些类按照Spring Bean定义进行注册。如果你要在Web应用开发中选用上述配置方式,需要用AnnotationConfigWebApplicationContext类来读取配置文件,可以用来配置Spring的Servlet监听器ContrextLoaderListener或者Spring MVC的DispatcherServlet。例如: <web-app> <context-param> <param-name>contextClass</param-name> <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value> </context-param> <context-param> <param-name>contextConfigLocation</param-name> <param-value>com.gupaoedu.AppConfig</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextClass</param-name> <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value> </init-param> <init-param> <param-name>contextConfigLocation</param-name> <param-value>com.gupaoedu.web.MVCConfig</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/web/*</url-pattern> </servlet-mapping> </web-app> 9、怎样用注解的方式配置SpringSpring在2.5版本以后开始支持用注解的方式配置依赖注入。可以用注解的方式来替代XML方式的Bean描述,可以将Bean描述转移到组件类的内部,只需要在相关类上、方法上或者字段声明上使用注解即可。注解注入将会被容器在XML注入之前处理,所以后者会覆盖前者对于同一个属性的处理结果。注解装配在Spring中是默认关闭的,需要在Spring文件中进行配置才能使用基于注解的装配模式。如果你想要在应用程序中使用注解的方式,请参考如下配置: <beans> <context:annotation-config/> </beans> 配置完成以后,就可以用注解的方式在Spring中向属性、方法和构造方法中自动装配变量。下面是几种比较重要的注解类型。(1)@Required:该注解应用于设值方法。 (2)@Autowired:该注解应用于设值方法、非设值方法、构造方法和变量。 (3)@Qualifier:该注解和@Autowired注解搭配使用,用于消除特定Bean自动装配的歧义。 (4)JSR-250 Annotations:Spring支持基于JSR-250 注解的注解,即@Resource、@PostConstruct和@PreDestroy。10、请解释Spring Bean的生命周期Spring Bean的生命周期简单易懂。在一个Bean实例被初始化时,需要执行一系列初始化操作以使其达到可用的状态。同样,当一个Bean不再被调用时需要进行相关的析构操作,并从Bean容器中移除。 Spring Bean Factory 负责管理在Spring容器中被创建的Bean的生命周期。Bean的生命周期由两组回调方法组成。(1)初始化之后调用的回调方法。 (2)销毁之前调用的回调方法。Spring提供了以下4种方式来管理Bean的生命周期事件:(1)InitializingBean和DisposableBean回调接口。 (2)针对特殊行为的其他Aware接口。 (3)Bean配置文件中的customInit()方法和customDestroy()方法。 (4)@PostConstruct和@PreDestroy注解方式。使用customInit()和 customDestroy()方法管理Bean生命周期的代码样例如下: <beans> <bean id="demoBean" class="com.gupaoedu.task.DemoBean" init-Method="customInit" destroy-Method="customDestroy"></bean> </beans> 11、Spring Bean作用域的区别是什么Spring容器中的Bean可以分为5个作用域。所有作用域的名称都是自说明的,但是为了避免混淆,还是让我们来解释一下。(1)singleton:这种Bean作用域是默认的,这种作用域确保不管接收到多少个请求,每个容器中只有一个Bean实例,单例模式由Bean Factory自身来维护。 (2)prototype:prototype作用域与singleton作用域相反,为每一个Bean请求提供一个实例。 (3)request:在请求Bean作用域内为每一个来自客户端的网络请求创建一个实例,在请求完成以后,Bean会失效并被垃圾回收器回收。 (4)Session:与request作用域类似,确保每个Session中有一个Bean实例,在Session过期后,Bean会随之失效。(5)global-session:global-session和Portlet应用相关。当应用部署在Portlet容器中时,它包含很多Portlet。如果想让所有的Portlet共用全局存储变量,那么这个全局存储变量需要存储在global-session中。全局作用域与Servlet中的Session作用域效果相同。12、什么是Spring Inner Bean在Spring中,无论何时,当Bean仅被调用了一个属性时,一个明智的做法是将这个Bean声明为内部Bean。内部Bean可以用setter注入“属性”和用构造方法注入“构造参数”的方式来实现。 比如,在应用程序中一个Customer类引用了一个Person类,我们要创建一个Person类的实例,然后在Customer内部使用。 public class Customer { private Person person; } public class Person { private String name; private String address; private int age; } 内部Bean的声明方式如下: <bean id="CustomerBean" class="com.gupaoedu.common.Customer"> <property name="person"> <bean class="com.gupaoedu.common.Person"> <property name="name" value="lokesh"/> <property name="address" value="India"/> <property name="age" value="34"/> </bean> </property> </bean> 13、Spring中的单例Bean是线程安全的吗Spring并没有对单例Bean进行任何多线程的封装处理。关于单例Bean的线程安全和并发问题需要开发者自行解决。但实际上,大部分Spring Bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上,Spring的单例Bean是线程安全的。如果你的Bean有多种状态(比如View Model对象),就需要自行保证线程安全。最容易的解决办法就是将多态Bean的作用域由“singleton”变更为“prototype”。14、请举例说明如何在Spring中注入一个Java集合Spring提供了以下4种集合类的配置元素:(1)标签用来装配可重复的list值。(2)标签用来装配没有重复的set值。(3)标签用来注入键和值,可以为任何类型的键值对。(4)标签支持注入键和值都是字符串类型的键值对。下面看一个具体的例子: <beans> <bean id="javaCollection" class="com.gupaoedu.JavaCollection"> <property name="customList"> <list> <value>INDIA</value> <value>Pakistan</value> <value>USA</value> <value>UK</value> </list> </property> <property name="customSet"> <set> <value>INDIA</value> <value>Pakistan</value> <value>USA</value> <value>UK</value> </set> </property> <property name="customMap"> <map> <entry key="1" value="INDIA"/> <entry key="2" value="Pakistan"/> <entry key="3" value="USA"/> <entry key="4" value="UK"/> </map> </property> <property name="customProperies"> <props> <prop key="admin">admin@gupaoedu.com</prop> <prop key="support">support@gupaoedu.com</prop> </props> </property> </bean> </beans> 15、如何向Spring Bean中注入java.util.Properties第一种方法是使用如下代码所示的标签: <bean id="adminUser" class="com.gupaoedu.common.Customer"> <property name="emails"> <props> <prop key="admin">admin@gupaoedu.com</prop> <prop key="support">support@gupaoedu.com</prop> </props> </property> </bean> 也可用“util:”命名空间从Properties文件中创建一个Properties Bean,然后利用setter方法注入Bean的引用。16、请解释Spring Bean的自动装配在Spring框架中,在配置文件中设定Bean的依赖关系是一个很好的机制,Spring容器还可以自动装配合作关系Bean之间的关联关系。这意味着Spring可以通过向BeanFactory中注入的方式自动搞定Bean之间的依赖关系。自动装配可以设置在每个Bean上,也可以设置在特定的Bean上。下面的XML配置文件表明了如何根据名称将一个Bean设置为自动装配模式: <bean id="employeeDAO" class="com.gupaoedu.EmployeeDAOImpl" autowire="byName" /> 除了Bean配置文件中提供的自动装配模式,还可以使用@Autowired注解来自动装配指定的Bean。在使用@Autowired注解之前需要按照如下的配置方式在Spring配置文件中进行配置: <context:annotation-config /> 也可以通过在配置文件中配置AutowiredAnnotationBeanPostProcessor 达到相同的效果: <bean class ="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/> 配置好以后就可以使用@Autowired来标注了: @Autowired public EmployeeDAOImpl(EmployeeManager manager){ this.manager=manager; } 17、自动装配有哪些局限性自动装配有如下局限性。• 重写:你仍然需要使用< property>设置指明依赖,这意味着总要重写自动装配。 • 原生数据类型:你不能自动装配简单的属性,如原生类型、字符串和类。 • 模糊特性:自动装配总是没有自定义装配精确,因此如果可能尽量使用自定义装配。18、请解释各种自动装配模式的区别在Spring中共有5种自动装配模式,让我们逐一分析。(1)no:这是Spring的默认设置,在该设置下自动装配是关闭的,开发者需要自行在Bean定义中用标签明确地设置依赖关系。 (2)byName:该模式可以根据Bean名称设置依赖关系。当向一个Bean中自动装配一个属性时,容器将根据Bean的名称自动在配置文件中查询一个匹配的Bean。如果找到就装配这个属性,如果没找到就报错。 (3)byType:该模式可以根据Bean类型设置依赖关系。当向一个Bean中自动装配一个属性时,容器将根据Bean的类型自动在配置文件中查询一个匹配的Bean。如果找到就装配这个属性,如果没找到就报错。 (4)constructor:和byType模式类似,但是仅适用于有与构造器相同参数类型的Bean,如果在容器中没有找到与构造器参数类型一致的Bean,那么将会抛出异常。 (5)autodetect:该模式自动探测使用constructor自动装配或者byType自动装配。首先会尝试找合适的带参数的构造器,如果找到就是用构造器自动装配,如果在Bean内部没有找到相应的构造器或者构造器是无参构造器,容器就会自动选择byType模式。19、请举例解释@Required注解在产品级别的应用中,IoC容器可能声明了数十万个Bean,Bean与Bean之间有着复杂的依赖关系。设值注解方法的短板之一就是验证所有的属性是否被注解是一项十分困难的操作。可以通过设置“dependency-check”来解决这个问题。在应用程序的生命周期中,你可能不大愿意花时间验证所有Bean的属性是否按照上下文文件正确配置,或者你宁可验证某个Bean的特定属性是否被正确设置。即使用“dependency-check”属性也不能很好地解决这个问题,在这种情况下需要使用@Required 注解。可用如下的方式来标明Bean的设值方法: public class EmployeeFactoryBean extends AbstractFactoryBean<Object> { private String designation; public String getDesignation() { return designation; } @Required public void setDesignation(String designation) { this.designation = designation; } } RequiredAnnotationBeanPostProcessor是Spring中的后置处理器,用来验证被@Required 注解的Bean属性是否被正确设置了。在使用RequiredAnnotationBeanPostProcesso验证Bean属性之前,要在IoC容器中对其进行注册: <bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor" /> 但是如果没有属性被用@Required注解过,后置处理器会抛出一个BeanInitializationException异常。20、请举例说明@Qualifier注解@Qualifier注解意味着可以在被标注Bean的字段上自动装配。@Qualifier注解可以用来取消Spring不能取消的Bean应用。21、构造方法注入和设值注入有什么区别请注意以下明显的区别:(1)设值注入支持大部分依赖注入,如果我们仅需要注入int、string和long型的变量,不要用设值方法注入。对于基本类型,如果没有注入,可以为基本类型设置默认值。构造方法注入不支持大部分依赖注入,因为在调用构造方法时必须传入正确的构造参数,否则会报错。 (2)设值注入不会重写构造方法的值。如果我们对同一个变量同时使用了构造方法注入和设值注入,那么构造方法将不能覆盖设值注入的值。很明显,因为构造方法只在对象被创建时被调用。 (3)在使用设值注入时还不能保证某种依赖是否已经被注入,也就是说,这时对象的依赖关系有可能是不完整的。而在另一种情况下,构造器注入则不允许生成依赖关系不完整的对象。 (4)在设值注入时如果对象A和对象B互相依赖,在创建对象A时Spring会抛出ObjectCurrentlyInCreationException异常,因为在对象B被创建之前对象A是不能被创建的,反之亦然。Spring用设值注入解决了循环依赖问题,因为对象的设值方法是在对象被创建之前被调用的。22、Spring中有哪些不同类型的事件Spring的ApplicationContext 提供了支持事件和代码中监听器的功能。我们可以创建Bean来监听在ApplicationContext 中发布的事件。对于ApplicationEvent类和在ApplicationContext接口中处理的事件,如果一个Bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,Bean会自动被通知。 public class AllApplicationEventListener implements ApplicationListener<ApplicationEvent> { @Override public void onApplicationEvent(ApplicationEvent applicationEvent) { //process event } } Spring 提供了以下5种标准的事件。(1)上下文更新事件(ContextRefreshedEvent):该事件会在ApplicationContext被初始化或者更新时发布。也可以在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。 (2)上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始或重新开始容器时触发该事件。 (3)上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。 (4)上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。 (5)请求处理事件(RequestHandledEvent):在Web应用中,当一个HTTP请求(Request)结束时触发该事件。除了上面介绍的事件,还可以通过扩展ApplicationEvent类来自定义事件: public class CustomApplicationEvent extends ApplicationEvent { public CustomApplicationEvent(Object source, final String msg) { super(source); System.out.println("Created a Custom event"); } } 为了监听这个事件,还需要创建一个监听器: public class CustomEventListener implements ApplicationListener<CustomApplicationEvent> { @Override public void onApplicationEvent(CustomApplicationEvent applicationEvent) { } } 之后通过ApplicationContext接口的publishEvent()方法来发布自定义事件: CustomApplicationEvent customEvent=new CustomApplicationEvent(applicationContext, "Test message"); applicationContext.publishEvent(customEvent 23、FileSystemResource和ClassPathResource有什么区别在FileSystemResource 中需要给出spring-config.xml文件在项目中的相对路径或者绝对路径。在ClassPathResource中Spring会在ClassPath中自动搜寻配置文件,所以要把ClassPathResource 文件放在ClassPath下。如果将spring-config.xml保存在了src目录下,只需给出配置文件的名称即可,因为src是默认的路径。简而言之,ClassPathResource在环境变量中读取配置文件,FileSystemResource在配置文件中读取配置文件。24、Spring中用到了哪些设计模式Spring中使用了大量的设计模式,下面列举了一些比较有代表性的设计模式。(1)代理模式:在AOP和remoting中被用得比较多。 (2)单例模式:在Spring配置文件中定义的Bean默认为单例模式。 (3)模板模式:用来解决代码重复问题,比如RestTemplate、JmsTemplate、JpaTemplate。 (4)委派模式:Spring提供了DispatcherServlet来对请求进行分发。 (5)工厂模式:BeanFactory用来创建对象的实例,贯穿于BeanFactory和ApplicationContext接口。 (6)代理模式:代理模式AOP思想的底层实现技术,Spring中采用JDK Proxy和CGLib类库。25、在Spring中如何更有效地使用JDBC使用Spring JDBC可以使得资源管理及错误处理的代价减小。开发人员只需通过statements和queries语句从数据库中存取数据。Spring通过模板类能更有效地使用JDBC,也就是所谓的JdbcTemplate。26、请解释Spring中的IoC容器Spring中的org.springframework.beans包和org.springframework.context包构成了Spring IoC容器的基础。BeanFactory接口提供了一个先进的配置机制,使得任何类型的对象的配置都成为可能。ApplicationContex接口对BeanFactory(是一个子接口)进行了扩展,在BeanFactory的基础上添加了其他功能,比如与Spring的AOP更容易集成,也提供了处理Message Resource的机制(用于国际化),以及事件传播及应用层的特别配置,比如针对Web应用的WebApplicationContext。27、在Spring中可以注入null或空字符串吗完全可以。 喜欢文章记得点个赞,感谢支持!

优秀的个人博客,低调大师

Java线程安全面试题,你真的了解吗?

多个线程不管以何种方式访问某个类,并且在主调代码中不需要进行同步,都能表现正确的行为。 线程安全有以下几种实现方式: 不可变不可变(Immutable)的对象一定是线程安全的,不需要再采取任何的线程安全保障措施。只要一个不可变的对象被正确地构建出来,永远也不会看到它在多个线程之中处于不一致的状态。多线程环境下,应当尽量使对象成为不可变,来满足线程安全。 不可变的类型: final 关键字修饰的基本数据类型String枚举类型Number 部分子类,如 Long 和 Double 等数值包装类型,BigInteger 和 BigDecimal 等大数据类型。但同为 Number 的原子类 AtomicInteger 和 AtomicLong 则是可变的。对于集合类型,可以使用 Collections.unmodifiableXXX() 方法来获取一个不可变的集合。 public class ImmutableExample { public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); Map<String, Integer> unmodifiableMap = Collections.unmodifiableMap(map); unmodifiableMap.put("a", 1); } }Exception in thread "main" java.lang.UnsupportedOperationException at java.util.Collections$UnmodifiableMap.put(Collections.java:1457) at ImmutableExample.main(ImmutableExample.java:9) Collections.unmodifiableXXX() 先对原始的集合进行拷贝,需要对集合进行修改的方法都直接抛出异常。 public V put(K key, V value) { throw new UnsupportedOperationException(); }互斥同步synchronized 和 ReentrantLock。 非阻塞同步互斥同步最主要的问题就是线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。 互斥同步属于一种悲观的并发策略,总是认为只要不去做正确的同步措施,那就肯定会出现问题。无论共享数据是否真的会出现竞争,它都要进行加锁(这里讨论的是概念模型,实际上虚拟机会优化掉很大一部分不必要的加锁)、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒等操作。 CAS随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略:先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步。 乐观锁需要操作和冲突检测这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,只能靠硬件来完成。硬件支持的原子性操作最典型的是:比较并交换(Compare-and-Swap,CAS)。CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时,只有当 V 的值等于 A,才将 V 的值更新为 B。 AtomicIntegerJ.U.C 包里面的整数原子类 AtomicInteger 的方法调用了 Unsafe 类的 CAS 操作。 以下代码使用了 AtomicInteger 执行了自增的操作。 private AtomicInteger cnt = new AtomicInteger(); public void add() { cnt.incrementAndGet(); }以下代码是 incrementAndGet() 的源码,它调用了 Unsafe 的 getAndAddInt() 。 public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }以下代码是 getAndAddInt() 源码,var1 指示对象内存地址,var2 指示该字段相对对象内存地址的偏移,var4 指示操作需要加的数值,这里为 1。通过 getIntVolatile(var1, var2) 得到旧的预期值,通过调用 compareAndSwapInt() 来进行 CAS 比较,如果该字段内存地址中的值等于 var5,那么就更新内存地址为 var1+var2 的变量为 var5+var4。 可以看到 getAndAddInt() 在一个循环中进行,发生冲突的做法是不断的进行重试。 public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; } ABA如果一个变量初次读取的时候是 A 值,它的值被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过。 J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题,它可以通过控制变量值的版本来保证 CAS 的正确性。大部分情况下 ABA 问题不会影响程序并发的正确性,如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效。 无同步方案要保证线程安全,并不是一定就要进行同步。如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性。 栈封闭多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的。 public class StackClosedExample { public void add100() { int cnt = 0; for (int i = 0; i < 100; i++) { cnt++; } System.out.println(cnt); } }public static void main(String[] args) { StackClosedExample example = new StackClosedExample(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(() -> example.add100()); executorService.execute(() -> example.add100()); executorService.shutdown(); }100100 线程本地存储(Thread Local Storage)如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行。如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题。 符合这种特点的应用并不少见,大部分使用消费队列的架构模式(如“生产者-消费者”模式)都会将产品的消费过程尽量在一个线程中消费完。其中最重要的一个应用实例就是经典 Web 交互模型中的“一个请求对应一个服务器线程”(Thread-per-Request)的处理方式,这种处理方式的广泛应用使得很多 Web 服务端应用都可以使用线程本地存储来解决线程安全问题。 可以使用 java.lang.ThreadLocal 类来实现线程本地存储功能。 对于以下代码,thread1 中设置 threadLocal 为 1,而 thread2 设置 threadLocal 为 2。过了一段时间之后,thread1 读取 threadLocal 依然是 1,不受 thread2 的影响。 public class ThreadLocalExample { public static void main(String[] args) { ThreadLocal threadLocal = new ThreadLocal(); Thread thread1 = new Thread(() -> { threadLocal.set(1); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(threadLocal.get()); threadLocal.remove(); }); Thread thread2 = new Thread(() -> { threadLocal.set(2); threadLocal.remove(); }); thread1.start(); thread2.start(); } }1为了理解 ThreadLocal,先看以下代码: public class ThreadLocalExample1 { public static void main(String[] args) { ThreadLocal threadLocal1 = new ThreadLocal(); ThreadLocal threadLocal2 = new ThreadLocal(); Thread thread1 = new Thread(() -> { threadLocal1.set(1); threadLocal2.set(1); }); Thread thread2 = new Thread(() -> { threadLocal1.set(2); threadLocal2.set(2); }); thread1.start(); thread2.start(); } }它所对应的底层结构图为: 每个 Thread 都有一个 ThreadLocal.ThreadLocalMap 对象。 /* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null; 当调用一个 ThreadLocal 的 set(T value) 方法时,先得到当前线程的 ThreadLocalMap 对象,然后将 ThreadLocal->value 键值对插入到该 Map 中。 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }get() 方法类似。 public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }ThreadLocal 从理论上讲并不是用来解决多线程并发问题的,因为根本不存在多线程竞争。 在一些场景 (尤其是使用线程池) 下,由于 ThreadLocal.ThreadLocalMap 的底层数据结构导致 ThreadLocal 有内存泄漏的情况,应该尽可能在每次使用 ThreadLocal 后手动调用 remove(),以避免出现 ThreadLocal 经典的内存泄漏甚至是造成自身业务混乱的风险。 可重入代码(Reentrant Code)这种代码也叫做纯代码(Pure Code),可以在代码执行的任何时刻中断它,转而去执行另外一段代码(包括递归调用它本身),而在控制权返回后,原来的程序不会出现任何错误。 可重入代码有一些共同的特征,例如不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数中传入、不调用非可重入的方法等。

优秀的个人博客,低调大师

死磕 java原子类之终结篇(面试题)

概览 原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程上下文切换。 原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分,将整个操作视作一个整体是原子性的核心特征。 在java中提供了很多原子类,笔者在此主要把这些原子类分成四大类。 原子更新基本类型或引用类型 如果是基本类型,则替换其值,如果是引用,则替换其引用地址,这些类主要有: (1)AtomicBoolean 原子更新布尔类型,内部使用int类型的value存储1和0表示true和false,底层也是对int类型的原子操作。 (2)AtomicInteger 原子更新int类型。 (3)AtomicLong 原子更新long类型。 (4)AtomicReference 原子更新引用类型,通过泛型指定要操作的类。 (5)AtomicMarkableReference 原子更新引用类型,内部使用Pair承载引用对象及是否被更新过的标记,避免了ABA问题。 (6)AtomicStampedReference 原子更新引用类型,内部使用Pair承载引用对象及更新的邮戳,避免了ABA问题。 这几个类的操作基本类似,底层都是调用Unsafe的compareAndSwapXxx()来实现,基本用法如下: private static void testAtomicReference() { AtomicInteger atomicInteger = new AtomicInteger(1); atomicInteger.incrementAndGet(); atomicInteger.getAndIncrement(); atomicInteger.compareAndSet(3, 666); System.out.println(atomicInteger.get()); AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1); atomicStampedReference.compareAndSet(1, 2, 1, 3); atomicStampedReference.compareAndSet(2, 666, 3, 5); System.out.println(atomicStampedReference.getReference()); System.out.println(atomicStampedReference.getStamp()); } 原子更新数组中的元素 原子更新数组中的元素,可以更新数组中指定索引位置的元素,这些类主要有: (1)AtomicIntegerArray 原子更新int数组中的元素。 (2)AtomicLongArray 原子更新long数组中的元素。 (3)AtomicReferenceArray 原子更新Object数组中的元素。 这几个类的操作基本类似,更新元素时都要指定在数组中的索引位置,基本用法如下: private static void testAtomicReferenceArray() { AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10); atomicIntegerArray.getAndIncrement(0); atomicIntegerArray.getAndAdd(1, 666); atomicIntegerArray.incrementAndGet(2); atomicIntegerArray.addAndGet(3, 666); atomicIntegerArray.compareAndSet(4, 0, 666); System.out.println(atomicIntegerArray.get(0)); System.out.println(atomicIntegerArray.get(1)); System.out.println(atomicIntegerArray.get(2)); System.out.println(atomicIntegerArray.get(3)); System.out.println(atomicIntegerArray.get(4)); System.out.println(atomicIntegerArray.get(5)); } 原子更新对象中的字段 原子更新对象中的字段,可以更新对象中指定字段名称的字段,这些类主要有: (1)AtomicIntegerFieldUpdater 原子更新对象中的int类型字段。 (2)AtomicLongFieldUpdater 原子更新对象中的long类型字段。 (3)AtomicReferenceFieldUpdater 原子更新对象中的引用类型字段。 这几个类的操作基本类似,都需要传入要更新的字段名称,基本用法如下: private static void testAtomicReferenceField() { AtomicReferenceFieldUpdater<User, String> updateName = AtomicReferenceFieldUpdater.newUpdater(User.class, String.class,"name"); AtomicIntegerFieldUpdater<User> updateAge = AtomicIntegerFieldUpdater.newUpdater(User.class, "age"); User user = new User("tong ge", 21); updateName.compareAndSet(user, "tong ge", "read source code"); updateAge.compareAndSet(user, 21, 25); updateAge.incrementAndGet(user); System.out.println(user); } private static class User { volatile String name; volatile int age; public User(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "name: " + name + ", age: " + age; } } 高性能原子类 高性能原子类,是java8中增加的原子类,它们使用分段的思想,把不同的线程hash到不同的段上去更新,最后再把这些段的值相加得到最终的值,这些类主要有: (1)Striped64 下面四个类的父类。 (2)LongAccumulator long类型的聚合器,需要传入一个long类型的二元操作,可以用来计算各种聚合操作,包括加乘等。 (3)LongAdder long类型的累加器,LongAccumulator的特例,只能用来计算加法,且从0开始计算。 (4)DoubleAccumulator double类型的聚合器,需要传入一个double类型的二元操作,可以用来计算各种聚合操作,包括加乘等。 (5)DoubleAdder double类型的累加器,DoubleAccumulator的特例,只能用来计算加法,且从0开始计算。 这几个类的操作基本类似,其中DoubleAccumulator和DoubleAdder底层其实也是用long来实现的,基本用法如下: private static void testNewAtomic() { LongAdder longAdder = new LongAdder(); longAdder.increment(); longAdder.add(666); System.out.println(longAdder.sum()); LongAccumulator longAccumulator = new LongAccumulator((left, right)->left + right * 2, 666); longAccumulator.accumulate(1); longAccumulator.accumulate(3); longAccumulator.accumulate(-4); System.out.println(longAccumulator.get()); } 问题 关于原子类的问题,笔者整理了大概有以下这些: (1)Unsafe是什么? (3)Unsafe为什么是不安全的? (4)Unsafe的实例怎么获取? (5)Unsafe的CAS操作? (6)Unsafe的阻塞/唤醒操作? (7)Unsafe实例化一个类? (8)实例化类的六种方式? (9)原子操作是什么? (10)原子操作与数据库ACID中A的关系? (11)AtomicInteger怎么实现原子操作的? (12)AtomicInteger主要解决了什么问题? (13)AtomicInteger有哪些缺点? (14)ABA是什么? (15)ABA的危害? (16)ABA的解决方法? (17)AtomicStampedReference是怎么解决ABA的? (18)实际工作中遇到过ABA问题吗? (19)CPU的缓存架构是怎样的? (20)CPU的缓存行是什么? (21)内存屏障又是什么? (22)伪共享是什么原因导致的? (23)怎么避免伪共享? (24)消除伪共享在java中的应用? (25)LongAdder的实现方式? (26)LongAdder是怎么消除伪共享的? (27)LongAdder与AtomicLong的性能对比? (28)LongAdder中的cells数组是无限扩容的吗? 关于原子类的问题差不多就这么多,都能回答上来吗?点击下面的链接可以直接到相应的章节查看: 死磕 java魔法类之Unsafe解析 死磕 java原子类之AtomicInteger源码分析 死磕 java原子类之AtomicStampedReference源码分析 杂谈 什么是伪共享(false sharing)? 死磕 java原子类之LongAdder源码分析 彩蛋 原子类系列源码分析到此就结束了,虽然分析的类比较少,但是牵涉的内容非常多,特别是操作系统底层的知识,比如CPU指令、CPU缓存架构、内存屏障等。 下一章,我们将进入“同步系列”,同步最常见的就是各种锁了,这里会着重分析java中的各种锁、各种同步器以及分布式锁相关的内容。 欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

优秀的个人博客,低调大师

一文彻底搞懂面试中常问的各种“锁”

前言 锁,顾名思义就是锁住一些资源,当只有我们拿到钥匙的时候,才能操作锁住的资源。在我们的Java,数据库,还有一些分布式的环境中,总是充斥着各种各样的锁让人头疼,例如“公平锁”、“自旋锁”、“读写锁”、“分布式锁”等等。 其实真实的情况是,锁并没有那么多,很多概念只是从不同的功能特性,设计,以及锁的状态这些不同的侧重点来说明的,因此我们可以根据不同的分类来搞明白为什么会有这些“锁”?坐稳扶好了,准备开车。 正文 “公平锁”与“非公平锁” 公平锁:指线程在等待获取同一个锁的时候,是严格按照申请锁的时间顺序来进行的,这就意味着在程序正常运作的时候,不会有线程执行不到,而被“饿死”,但是也需要额外的机制来维护这种顺序,所以效率相对于非公平锁会差点。 非公平锁:概念跟“公平锁”恰恰相反,随机线程获取锁,相率相对高。 newReentrantLock();//默认非公平锁newReentrantLock(true);//公平锁 “重入锁(递归锁)”与“不可重入锁(自旋锁)” 这里要注意了,重入/递归,不可重入/自旋,虽然名字不同,但是确实是同一种锁,只是从锁的表现跟实现方式的角度来命名而已。 重入锁:当一个线程获取了A锁以后,若后续方法运行被A锁锁住的话,当前线程也是可以直接进入的。 publicclassDemo{ private Lock lockA; publicDemo(LockLock){this.lockA=lock; } publicvoidmethodA(){ lockA.lock(); methodB(); lockA.unlock(); } publicvoidmethodB(){ lockA.lock();//dosm lockA.unlock(); } } 当我们运行methodA()的时候,线程获取了lockA,然后调用methodB()的时候发现也需要lockA,由于这是一个可重入锁,所以当前线程也是可以直接进入的。在java中,synchronized跟ReetrantLock都是可重入锁。 不可重入锁:以上面的代码实例来说明,就是methodA进入methodB的时候不能直接获取锁,必须先调用unLock释放锁。才能执行下去,那实现不可重入锁有什么方式呢?那就是自旋,所以会有一个小名叫做自旋锁。​​​​​​​ public class SpinLock { private AtomicReference<Thread> sign =new AtomicReference<>(); public void lock(){ Thread current = Thread.currentThread(); while(!sign .compareAndSet(null, current)){ } } public void unlock (){ Thread current = Thread.currentThread(); sign .compareAndSet(current, null); }} “悲观锁”与“乐观锁” 这两种锁呢,其实是一个很宏观的分类,它不是一种具体的锁,而是泛指看待并发的程度。 悲观锁:有一个“悲观”的心态,既每次取数据的时候,都会认为该数据会被修改,所以必须加一把锁才安心。 乐观锁:乐观的孩子,认为同一个数据不会发生并发操作的行为,所以取的时候不会加锁,只有在更新的时候,会通过例如版本号之类的来判断是否数据被修改了。 Java中各种锁其实都是悲观锁的实现,既操作的数据的都会被获取锁的线程锁住,而乐观锁的话,一般是通过cas(compare and swap)的思想来实现,例如一些原子类AtomicInteger使用自旋来原子更新。 “共享锁”与“排他锁” 这两种锁的概念比较多的出现在数据库的事务当中。 共享锁:也称读锁或S锁。如果事务对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排它锁。获准共享锁的事务只能读数据,不能修改数据。在java中的ReetrantReadWriteLock()也是如此。 排它锁:也称独占锁、写锁或X锁。如果事务对数据A加上排它锁后,则其他事务不能再对A加任何类型的锁。获得排它锁的事务即能读数据又能修改数据。 分布式锁 我们上面聊的这些锁,都是在单个程序上面的不同线程之间来实现的,那么当我们的不同程序需要去竞争同一块资源的时候,这就需要分布式锁了,我们可以通过redis、zookeeper等中间件来实现分布式锁。 对于锁来说,其实还有偏向锁,轻量级锁等,但是这里涉及到的内容就比较多,这里就不在展开篇幅介绍了,有兴趣的同学可自行研究,如果你能搞懂上面介绍的这些锁,那基本上在绝大部分的公司关于“锁”的问题都可以迎刃而解。 喜欢的话,麻烦大家点个赞~关注一下微信公众号《深夜里的程序猿》,每天分享最干的干货~

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册