首页 >> 大全

逐梦少年,看你能不能发现宝藏

2023-08-27 大全 23 作者:考证青年

​ 局部变量:方法体{}中,形参,或代码块中{}中 局部变量是没有默认值的,

​ 成员变量:类中方法外 成员变量是由默认值的。ex:int i; 默认i=0;

​ 类变量:有修饰的

​ 实例变量:没有修饰的

​ 2.对于不同变量可用的修饰符:

​ 局部变量:final

​ 成员变量:、、、final、、、(与线程有关系的 )、(关于序列化的)

​ 3.值存储的位置

​ 局部变量:存在 栈中

​ 实例变量:存在 堆中

​ 类变量:存在 方法区

​ 4.作用域

​ 局部变量:从声明处开始,到所属的 }结束

​ 实例变量:在当前类中“this”(有时this,可以缺省)。在其他类中“对象名. ”访问

​ 类变量:在当前类中“类名. ”(有时类名,可以省略),在其他类中“类名. ”或“对象名.”.访问

​ 5.生命周期

​ 局部变量:每一个线程,每一次调用执行的都是新的生命周期

​ 实例变量:随着对象的创建而初始化,随着对象的被回收而消亡,每一个对象的实例变量都是独立的。

​ 类变量:随着类的初始化而初始化,随着类的卸载而消亡,该类的所有对象的类变量都是共享的。

堆(Heap),此内存区域的唯一目的就是存放对象实例的,几乎所有的对象实例都在里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数字都要在堆上分配。

通常我们所说的栈(Stack),是指虚拟机栈。虚拟机用于存储局部变量表等。局部变量表存放了编译器可知长度的各种基本数据类型(、byte、char、short、int、float、long、、)对象引用(类型,它不等同于对象本身,是对象在堆内存的首地址)。方法执行完,自动释放。

方法区( Area)用于存储已被虚拟机加载的类的信息、常量、静态变量、即时编译器编译后的代码等数据。

当局部变量与成员变量或类变量重名时?如何区分?

1.局部变量与实例变量重名

​ 在实例变量前面加“this

2.局部变量与类变量重名

​ 在类变量前面加”类名.“

7.bean的作用域

​ 在中,可以在元素的属性里设置bean的作用域,以决定这个bean时单实例的,还是多实例的。

​ 默认情况下,只为每个在IOC容器里声明的bean创建唯一一个实例,整个ioc容器范围内都能共享该实例:所有后续的()调用和bean引用都将返回这个唯一的bean实例。该作用域称为,它是所有bean的默认作用域。

类型说明

在容器中仅存在一个Bean实例,Bean以单实例的方式存在

每次调用()时,都会返回一个新的实例。

每次HTTP请求都会创建以恶个新的Bean,该作用域仅适用于t环境

同一个HTTP 共享一个Bean,不同的HTTP 使用不同的Bean。该作用域仅适用于t环境

 public class Book {private String id;private String title;private String price;public Book() {System.out.println("Book对象被创建了");}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getPrice() {return price;}public void setPrice(String price) {this.price = price;}
}
public class SpringTest {public static void main(String[] args) {//创建IOC容器的对象ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/camel-context.xml");Book book = (Book)context.getBean("book");Book book2 = (Book)context.getBean("book");System.out.println(book == book2);}/*//创建IOC容器的对象ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/camel-context.xml");@Testpublic void testBook() {Book book = (Book)context.getBean("book");Book book2 = (Book)context.getBean("book");System.out.println(book == book2);}*/
}

8.请简单介绍支持的常用数据库事务传播属性和事务的隔离级别?

事务的属性:

​ *1.,用来设置事务的传播行为

​ 事务的传播行为:一个方法运行在了一个开启了事务的方法中时,当前方法时使用原来的事务,还是开启了一个新的事务;

​ -.,默认值,使用原来的事务

​ -.,将原来的事务挂起,开启一个新的事务

​ *2.,用来设置事务的隔离级别

​ -.,可重复读,MySQL默认的隔离级别

​ -.,已提交读,默认的隔离界别,开发时通常使用的隔离级别

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新的事务,并在自己的事务中运行。

事务的传播行为可以由传播属性指定。定义了7钟类传播行为

传播属性描述

如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行

当前的方法必须重新启动新事务,并在它自己的事务内运行,如果有事务正在运行,应该将它挂起

如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中。

当前的方法不应该运行在事务中,如果有运行的事务,将它挂起

当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常

NEVER

当前的方法不应该运行在事务中,如果由运行的事务,就抛出异常

如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行。

事务传播属性可以在@注解的属性中定义。

自己遇到的坑,要想使用事务生效,当输出库更改失败时,你记得要抛出异常,否则没有异常,事务不会生效的

@Service
public class BookShopServiceImpl implements BookShopService {@AutowiredBookShopDao bookShopDao;@Transactional(propagation = Propagation.REQUIRES_NEW)@Overridepublic void purchase(int userId, String isbn) {//1.获取要买书的图书价格String bookPrice = bookShopDao.getBookPriceByIsbn(isbn);//2.更新图书的库存int i = bookShopDao.updateBookStock(isbn);if (i <1) {throw new RuntimeException("库存扣减失败");}//3.更新用户的余额int i1 = bookShopDao.updateAccountBalance(userId, bookPrice);if (i1 <1) {throw new RuntimeException("余额扣减失败");}}
}
@Service
public class CashierImpl implements Cashier {@Autowiredprivate BookShopService bookShopService;@Transactional(propagation = Propagation.REQUIRED)//用spring的声明式事务,来给checkout开启事务@Overridepublic void checkout(int userId, List isbns) {for (String isbn : isbns) {//调用BookShopService中买东西的方法bookShopService.purchase(userId,isbn);}}
}
class ShopbookApplicationTests {@Autowiredprivate Cashier cashier;@Testpublic void contextLoads() {//创建listArrayList isbns = new ArrayList<>();isbns.add("1001");isbns.add("1002");System.out.println("ok");//去结账cashier.checkout(1, isbns);}}


UPDATE book_stock SET STOCK= STOCK - 1 WHERE STOCK > 0 AND ISBN = #{isbn}UPDATE Accomt SET BALANCE=BALANCE - #{bookPrice} WHERE ID = #{userid} and BALANCE - #{bookPrice}  >=0

说明:

1.传播行为

​ 当的()方法被另一个事务方法()调用时,它默认会在现有的事务内运行。这个默认的传播行为就是.因此在()方法的开始和终止边界内只有一个事务,这个事务只在()方法结束的时候被提交,结果用户一本书都买不了。

Tx1开始 Tx2结束

​ | |

​ ------------------|----------------------------------------------------------------------|-------------------------

​ () ()

​ | |

​ ++++++++++++++++++|++++++++++++++++++

​ ()

2.传播行为

​ 表示该方法必须启动一个新事物,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。

​ Tx1挂起 Tx1继续 Tx1挂起 Tx1继续

Tx1开始 Tx2开始 Tx2结束 Tx3开始 Tx3结束 Tx1结束

​ | |

​ ------------------|----------------------------------------------------------------------|-------------------------

​ () ()

​ | |

​ ++++++++++++++++++|++++++++++++++++++

​ ()

9.事务的隔离级别

​ 1.数据库事务的并发问题

​ 假如现在有两个事务:和并发执行。

​ 1) 脏读 当前事务读到了其他事务更新,但是还没有提交的值,就属于脏读

​ 将某条记录的AGE值从20修改为30.

​ 读取了更新后的值:30.

​ 回滚,AGE值恢复到了20.

​ 读取到的30就是一个无效得到值

​ 2)不可重复读

​ 读取了AGE值为20.

​ 将AGE值修改为30.

​ 再次读取AGE值为30,和第一次读取不一致

​ 3)幻读

​ 读取了表中的一部分数据。

​ 向表中插入了新的行。

	   Transation01		读取了student表,多出了一些行数据

隔离级别

​ 数据库系统必须具有隔离并发运行各个事务的能力,使得他们不会相互影响。避免各种并发问题。**一个事务与其他事务隔离的程度称为隔离级别。**SQL标准中规定了多种事务隔离级别,不同的隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

​ 1)读未提交:READ

​ 允许 读取 未提交的修改。

​ **2)读已提交:READ **=============》》》》开发过程中常用。

​ 要求 只能读取 已提交的修改。

​ **3)可重复读: READ **

​ 确保 可以多次从一个字段中读取到相同的值,即 执行期间禁止其他事务对这个字段进行更新。

​ **4)串行化: **

​ 确保 可以多次的从一个表中读取到相同的行,在 执行期间,禁止其他事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但是性能十分低下。

各个隔离界别解决并发问题的能力见下表

脏读不可重复读幻读

READ

READ

READ

各类数据库产品对事务隔离级别的支持程度 yes是可支持,no是直接不支持的

READ

no

yes

READ

yes(默认)

yes

READ

no

yes(默认)

yes

yes

mysql的默认隔离级别是可重复读,就是在一个方法中你从数据库里面查一个东西,第一次查是50块钱,即时数据库里面的值对应的字段改成了300,你这个方法没有停止,你再从代码里面走sql语句,还是50块钱。

10. Mvc 中,如何解决请求POST中文乱码问题,GET的又如何处理呢?

针对于SSM的项目,post请求乱码问题 以下方案只能解决post请求

在web.xml里面配置一下过滤器即可
------------------------------------------------------------------------------------------------------------------------------
SetCharacterEncodingorg.springframework.web.filter.CharacterEncodingFilterencodingUTF-8forceEncodingtrueSetCharacterEncoding/*

对于get请求时没有效果

​ 1.最简单的解决方式

​ 就是容器中,找到.xml里面的第一个标签。里面加上 =“UTF-8”

11.简单的谈一下的工作流程?

​ **处理模型数据方式一: **

​ 将方法的返回值设置为

​ 1.创建对象

​ 2.设置模型数据,最终会放到域中

​ 3.设置视图(这个试图是跳jsp模板的)

​ **处理模型数据方式二: **

​ 方法的返回值仍是类型,在方法的入参中传入Map、Model或者

​ 不管将处理器方法的返回值设置为还是在方法的入参中传入Map、Modea或者

​ 都会转换为一个对象

​ 1.向map中添加模型数据,最终会自动放到域中。

//1.简单的谈一下SpringMvc的工作流程//处理模型数据方式一:将方法的返回值设置为ModelAndView@RequestMapping("/testModelAndView")public ModelAndView testModelAndView() {//1.创建ModelAndView对象ModelAndView mav = new ModelAndView();//2.设置模型数据,最终会放到request域中mav.addObject("user","admin");//3.设置视图mav.setViewName("success");return mav;}/** 	方法的返回值仍是String类型,在方法的入参中传入Map、Model或者ModelMap
​	    不管将处理器方法的返回值设置为ModelAndView还是在方法的入参中传入Map、Modea或者ModelMapspringMvc都会转换为一个ModelAndView对象* */@RequestMapping("/testMap")public String testMap(Map map) {//向map中添加模型数据,最终会自动放到request域中。map.put("user",new Person("12","1212","1212"));return "success";}

12.中当实体类中的属性名和表中的字段名不一样,怎么办?(这个问题有三种解决方案)

解决方案:
*        1.写sql语句的时候起别名
*        2.在MyBatis的全局配置文件中开启驼峰命名规则
*        #Mybatis----- 开启驼峰命名方法mybatis.configuration.map-underscore-to-camel-case=true例如:last_name可以映射为lastName    数据库里面必须是last+“_”+name   必须有下划线3.在Mapper映射文件中使用resultMap来自定义映射规则我们一般称这个为高级映射。

13.linux系统,常用服务类相关命令

()

​ 注册在系统中的标准化程序

​ 有方便统一的管理方式(常用的方法)

​ 服务名 start 例如: staus

​ 服务名 stop

​ 服务名

​ 服务名

​ 服务名

​ 查看服务的方法 /etc/init.d/服务名

​ 通过 命令设置自启动

​ **查看服务 --list|grep *** **

				chkconfig    --level   5   服务名    on

[root@localhost ~]# chkconfig --list注:该输出结果只显示 SysV 服务,并不包含
原生 systemd 服务。SysV 配置数据
可能被原生 systemd 配置覆盖。 要列出 systemd 服务,请执行 'systemctl list-unit-files'。查看在具体 target 启用的服务请执行'systemctl list-dependencies [target]'。netconsole      0:关    1:关    2:关    3:关    4:关    5:关    6:关
network         0:关    1:关    2:开    3:开    4:开    5:开    6:关
======================================================================一共有7个运行级别如下图

14.git分支相关命令,实际应用

15.redis持久化的两种方式?

1.RDB

​ 在即定的时间间隔内,将内存中的数据集快照写入磁盘,也就是行话讲的快照,它恢复时也是将快照文件直接读到内存里面。

备份时如何执行的呢?

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上一次持久化好的文件。整个过程中,主进程时不进行IO操作的,这就确保了极高的性能如果需要大规模的恢复,且对于恢复完整性不是非常敏感,那RDB方式要比AOF更加的高效,

RDB的缺点时最后一次持久化的数据可能会丢失

RDB优点

​ 节省磁盘空间

​ 恢复速度快

RDB缺点

​ 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能的。

​ 在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。

2.AOF

​ 以日志的形式来记录每个写操作,将Redis执行过的所有的指令记录下来(读操作不记录),只许追加文件但不可以改写文件,Redis启动之初会读取该文件重新构建数控,换言之,Redis重启的话就是根据日志文件的内容将写指令从前到后执行一次,已完成数据的恢复工作。

AOF的优点

​ 备份机制更稳健,丢失数据概率更低

​ 刻度的日志文本,通过操作AOF文件,可以处理错误操作。

AOF缺点

​ 比起RDB占用更多的磁盘空间。

​ 恢复备份速度更慢。

​ 每次读写都同步的话,有一定的性能压力。

​ 存在个别bug,造成恢复不全。

15.MySql什么时候适合建索引,什么时候不适合建索引?

​ 索引是什么?

​ 索引是帮助MySql高效获取数据的数据结构。可以得到索引的本质:索引是数据结构。

​ 优点:

​ 类似于大学图书馆建立目录索引,提高数据检查的效率,降低数据库的 IO 成本。

​ 通过所i你对数据进行排序,降低数据排序的成本,降低了CPU的消耗。

​ 劣势:

​ 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行、、和。

​ 因为更新表时,MySql不仅要保存数据,还要保存一下索引文件每次更新了索引列的字段。都会调整因为更新所带来的键值变化后的索引信息。

​ 实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的。

适合创建索引的情况:

1.主键自动建立唯一索引 主键,msql会默认创建索引的。

2.频繁作为查询条件的字段应该创建索引

3.查询中与其他表关联的字段,外键关系创建索引。

4.单肩/组合索引的选择问题,组合索引的性价比更高

5.查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度。

6.查询中统计或者分组字段。

哪些情况不需要创建索引?

1.表记录太少

2.经常增删改的表或者字段

3.where条件里面用不到的字段不创建索引

4.过滤性不好的不适合建索引。 例如:性别字段:男女

15.JVM垃圾回收机制,GC发生在JVM哪部分,有几种GC,他们的算法是什么?

GC垃圾回收机制,是发生在堆里面的

GC是什么?(分代收集算法)

​ 次数上是频繁收集Young区(就是年轻带)----------Minor GC

​ 次数上较少收集Old区(就是老年代) -------------Full GC

​ 基本不懂Perm区(永久区-----没有gc)

GC的垃圾回收算法

​ 1.引用技术法

​ 每次对象赋值时,均要维护引用计数器,且计数器本身也有一定的消耗

​ 较难处理循环引用

​ JVM的实现一般不采用这种方式

​ 2.复制算法()

​ 年轻代中使用的是Minor GC ,这种GC算法采用的是复制算法()。

​ 原理:

​ 从根集合(GC Root)开始,通过从From中找到存货的对象,拷贝到To中;

​ From、To交换身份,下次内存分配从To开始;

​ 优点:没有标记和清除的过程,效率高

​ 没有内存碎片,可以利用bump-the-实现快速内存分配

​ 缺点:需要双倍的空间

​ 3.标记清除(Mark-Sweep)

​ 老年代一般是由标记清除或者是标记清除与标记整理的混合实现。

​ 1.从根集合开始扫描,对存活的对象进行标记。

​ 2.清除(Sweep)

​ 扫描整个内存空间,回收未被标记的对象,使用free-list记录可以区域。

​ 缺点:两次扫描,耗时严重, 会产生内存碎片

​ 优点:不需要额外的空间

​ 4.标记压缩(Mark-)

​ 老年代一般是由标记清除或者是标记清除与标记整理的混合实现

​ 原理:1.先标记,这里的标记与“标记-清除”一样

​ 2.压缩:再次扫描,并往一端滑动存活对西昂

​ 优点:没有内存碎片,可以利用bump

​ 缺点:需要移动对象的成本

​ 5.标记清除压缩(Mark-Swep-)

老年代两种GC算法:标记清除,以及标记压缩,这两种技术是一起使用的,首先使用的是标记清除,当有大量的内存碎片式,就开始启用标记压缩,整理内存

16.Redis 在项目中的使用场景 数据类型使用场景

比如说,我想知道什么时候封锁一个ip地址。命令

Hash

存储用户信息【id,name,age】

Hset(key,field,value)

Hset(,name,admin)

Hset(,age,23)

Hget(,id)

List

实现最新消息的排行,还可以利用List的push命令,将任务存在list集合中,同时使用另一个命令,将任务从集合中取出(pop)

Redis–List数据类型来模拟消息队列。【电商中的秒杀就可以采用这种方式,来完成一个秒杀活动】

Set

特殊之处:可以自动排重。比如说微博中将每个人的好友存在集合(Set)中。

这样求两个人的共同好友的操作,我们只需要求交集即可。set集合中有交集的命令

Zset

以某一个条件为权重进行排序。

京东:查看商品详情的时候,都会有一个总和排名,还可以按照价格进行排名,都可以用Zset进行排名

17.和solr的区别

**背景:**他们都是基于搜索服务器之上开发的,一款优秀的,高性能的企业级搜索服务器。(是因为他们都是基于分词技术构建的倒排索引的方式进行查询)

**开发语言:**都是java语言开发

诞生时间:

solr:2004年诞生

Es:2010年诞生

Es更新,功能越强大

区别:

​ 1.当实时建立索引的时候,solr会产生io的阻塞,es不会,所以es的查询性能要高于solr。

​ 2.在不断动态添加数据的时候,solr的检索效率会变得低下,而es则没有什么变化。

​ 3.solr利用进行分布式管理,而es自身带有分布式管理系统的功能。Solr一般都要部署到web服务器上,比如;而启动的时候

​ 。需要配置和solr的关联。【solr的本质是一个动态的web项目】

​ 4.solr支持更多的格式数据【xml,json,csv等】,而,es仅支持json文件格式

​ solr是传统搜索应用的有力解决方案,但是es更适用于新型的实时搜索应用。

​ 5.单纯的额对已有数据进行检索的时候,solr效率更好,高于es

​ 6.solr官网提供的功能更多,而es本身更注重于核心功能,一些高级功能的依靠与有第三方的插件。

18.单点登陆的实现过程

​ 什么是单点登陆?

​ 一处登陆,多处使用;

​ 前提是:单点登陆多使用在分布式系统中。

Demo:

​ 参观动物园的流程:

​ 检票员就相当于我们的认证中心

​ 1.我直接带着大家进动物园,则会被检票员拦住【看我们是否有门票】

​ 没有【则去售票处买票】 登陆就相当于买票

​ 2.我去买票【带着票,带着大家一起准备进入动物园】 检票员 check【有票】

​ 3.此时的这个票就相当于我们项目中的token=piao

​ 4.我们手中有票就可以任意观赏动物园的每处场景

京东:单点登陆,是将token放入到中的,

​ 案例:将浏览器的禁用,则在登陆京东则失败,无论如何都登陆不了!!!

19.购物车的实现过程

​ 1.购物车跟用户的关系

​ a、一个用户必须对应一个购物车【一个用户不管买多少商品,都会存在属于自己的购物车中。】

​ b、单点登陆一定在购物车之前。

​ 2.跟购物车有关的操作有哪些?

​ a、添加购物车

​ i、用户未登录状态;

​ 1.添加到什么地方?未登录,将数据保存到什么地方?

​ a、Redis?--------京东商城

​ b、?------自己开发项目的时候,可以存在中【如果浏览器禁用】

​ ii: 用户登陆状态

​ 1.Redis缓存中【读写速度快】

​ a、采用Hash存储:hset(key,field,value)

​ Key::cart

​ hset(Key,skuid,value)

​ 2.存在数据库中【、mysql】

​ b、展示购物车

​ i:未登录状态展示

​ 1.直接从中取得数据展示即可。

​ ii:登陆状态展示

​ 1.用户一旦登陆,必须显示数据库【redis】+中的购物车的数据

​ a、中有三条数据

​ b、Redis中有五条数据

​ c、真正展示的时候是八条记录

20.消息队列在项目中的使用

​ 消息队列产生的背景:

​ 在分布式系统中是如何处理高并发的?

​ 由于在高并发的环境下,来不及同步处理用户发送的请求,则会导致请求,发生阻塞

​ 比如说:大量的、之类的请求,同时到达数据库mysql,直接导致无数的行锁表锁,甚至会导致请求堆积很多

​ ,从而去触发too many 错误。使用消息队列,可以消息队列的【异步通信】解决

​ 这个排队就是使用的消息队列的结果

下面是队列电商中的使用场景

消息队列的弊端

​ 消息的不确定性:启用延时队列,轮询结束来解决该问题即可!

​ 推荐大家使用!环境是java的的

21.第二季复习java------阳哥讲《互联网笔试题第二季》

1.JVM/GC的知识 2.JUC前提知识 3.超级熟悉java8以后的新特性 ( + +函数式接口+方法引用)

JUC编程

​ java1.8新加了三个包:java.util. 这个单词是并发的意思

​ java.util.. 这个单词是原子的意思

22.这个关键字作用?

​ 1、是java虚拟机提供的轻量级的同步机制。

​ 三大特性:1.保证可见性 2.不保证原子性 3.禁止指令重排

​ 2.谈谈你对JMM(不是jvm虚拟机,这个是java内存模型)的理解:

​ JMM规范包括:1.可见性 2.原子性 3.有序性

​ JMM(java内存模型java Model,简称JMM)本身是一种抽象的概念并不是真实存在的。它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。

​ JMM关于同步的规定:

​ 1.线程解锁前,必须把共享变量的值刷新回主内存

​ 2.线程加锁前,必须读取主内存的最新值到自己的工作内存

​ 3.加锁解锁是同一把锁

​ 由于JVM运行程序的实体是线程,而每个线程创建时,JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存时每个线程的私有数据区域,而java内存模型中规定所有的变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问。但是线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作空间没然后对变量进行操作,操作完成后将变量协会主内存,不能直接操作主内存中的变量,哥哥线程中的工作内存中存储主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,其简要访问过程如下图

class MyData{  //MyData.java  ==>MyData.class ==>JVM字节码volatile int number = 0;public void addT060() {this.number = 60;}//请注意,此时前面是加了volitile关键字修饰的public /*synchronized*/ void  addPlusPlus() {number++;}AtomicInteger atomicInteger = new AtomicInteger(0);public void addAtomic() {atomicInteger.getAndDecrement();}
}
/*
*1.验证volatile的可见性
* 1.1  假如int number = 0;number变量之前根本没有添加volatile关键字修饰。也就是没有可见性。
* 1.2  添加了volatile可以解决可见性问题。
*
* 2.验证volative不保证原子性
* 2.1原子性是什么意思?
*       不可分割,完整性,即某个线程做某个任务的时候,中间不可以被加塞或者分割,需要整体完整,要么同时成功,要么同时失败。
* 2.2volitile是否可以保证原子性操作?
*   number++  在多线程下是非线程安全的,如何不加synchronized解决这个问题呢?
* 2.3 why?
* 2.4 如何解决原子性?
*       *加sync
*       *使用我们的juc的automicInterger
* */
public class VolatileDemo {public static void main(String[] args) throws InterruptedException {//main是一切方法的运势入口MyData myData = new MyData();for (int i =  0; i < 20; i++) {new Thread(()->{for (int i1 = 1; i1 <= 1000; i1++) {myData.addAtomic();}},String.valueOf(i)).start();}//需要等待上面20个线程券都计算完成后,再用main线程取得最终的结果值看是多少?//TimeUnit.SECONDS.sleep(5);//一个是main线程,一个是GC线程while (Thread.activeCount()>2) {Thread.yield();}System.out.println(Thread.currentThread().getName()+"\t finally number value:"+myData.atomicInteger);//     seeOkVolatile();}//volatile可以保证可见性,及时通知其他线程,著物理内存的值已经被修改。public static void seeOkVolatile() {MyData myData = new MyData();new Thread(()->{System.out.println(Thread.currentThread().getName()+"\t come in");try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}myData.addT060();System.out.println(Thread.currentThread().getName()+"\t update number value"+myData.number);},"AAA").start();//第二个线程是我们的main线程while (myData.number ==  0) {//mian线程一直再这里寻寻啊,直到number值不在等于0}System.out.println(Thread.currentThread().getName()+"\t mission is over");}
}

计算机在执行程序时,为了提高性能,编译器和处理器的常常会对指令做重排序,一般分为以下3种:

​ 原代码----》编译器优化的重排----------》指令并行的重排---------》内存系统的重排------------》最终执行的指令

单线程环境里面确保程序最终执行结果和代码顺序的执行结果一致。

处理器在惊醒重排序时,必须要考虑指令之间的数据依赖性。

多线程中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果是无法预测的。

实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象。

​ 先了解一个概念,内存屏障( )又称内存栅栏,是一个CPU指令,他的作用有两个:一个是保证特定操作的执行顺序。

​ 二是保证某些变量的内存可见性(利用该特性实现的内存可见性)。

由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条 则会告诉编译器和CPU,不管什么指令都不能和这条 指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读到这些数据的最新版本。

工作内存与主内存同步延迟现象导致的可见性问题。

可以使用或关键字解决,他们都可以使一个线程修改后的变量立即对其他线程可见。

对于指令重排导致的可见性问题和有序性问题。

可以利用关键字解决,因为的另外一个作用就是禁止重排序的优化。

23.你在哪些地方用到过?

​ 1.单例模式DCL代码

​ DCL(双端检索)机制不一定线程安全,原因是有指令重排序的存在,加入可以禁止指令重排。

​ 原因在于某一个线程执行到第一次检测,读取到的不为null时,的应用对象可能没有完成初始化。

= new (),可以分为以下3步完成(伪代码)

//下面是名副其实

= ();//1.分配对象内存空间

()//2.初始化对象

= ; //设置指向刚分配的内存地址,此时!= null;

步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中没有改变,因此这种重排优化时;

//下面是有名无实

= ();//1.分配对象内存空间

= ; //设置指向刚分配的内存地址,此时!= null;但是对象没有初始化完成。

()//2.初始化对象

但是指令重排只会保证串行语义的执行一致性(单线程),但是并不关心多线程间的语义一致性。

所以当一条线程访问不为null时,由于实例未必已初始化完成,也就造成了线程安全问题。

​ 2.单例模式分析

//单机版下的单例模式。
public class SingletonDemo {private static volatile SingletonDemo instance = null;private SingletonDemo() {System.out.println(Thread.currentThread().getName()+"\t 我是构造方法SingletonDemo()");}//DCL模式    (Double Check Lock 双端检索机制,就是在加锁前后进行判断。推荐使用,这样可以不用锁方法。)//public static synchronized SingletonDemo getInstance() {public static  SingletonDemo getInstance() {if (instance == null) {synchronized (SingletonDemo.class) {if (instance == null) {instance = new SingletonDemo();}}}return instance;}public static void main(String[] args) {//单线程(main线程的操作动作。。。。。。。。。。。)/*System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());System.out.println("========================================================");*/for (int i = 0; i < 10; i++) {new Thread(()->{SingletonDemo.getInstance();}).start();}}
}

24.CAS您知道么?讲一讲,为什么要用CAS而不是?CAS底层原理?如果知道,谈谈你对的理解

CAS的全程为-And-Swap,它是一条CPU并发原语。

它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。

CAS并发原语体现在JAVA语言中就是.类中的哥哥方法。调用类中的CAS方法,JVM会帮助我们实现CAS汇编指令。这是一种完全依赖于硬件的功能。通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于从做系统用语范畴,是由若干条指令组成的。用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。意思就是线程安全。

/*** Atomically decrements by one the current value.** @return the previous value*/public final int getAndDecrement() {return unsafe.getAndAddInt(this, valueOffset, -1);}

内存偏移地址就好比是一个坐标

假设线程A和线程B两个线程同时执行操作(分别在不同的Cpu上);

1.里面的value原始值为3,即主内存中的value为3,根据JMM模型,线程A和线程B各自持有一份价值为3的valie的副本分别到各自的工作内存。

2.线程A通过(var 1,var 2)拿到value值3,这时线程A被挂起。

3.线程B也通过(var 1,var 2)方法获取到value值3,此时刚好线程B没有被挂起并执行方法,比较内存值也为3,成功修改内存值为4,线程B打完收工,一切OK。

4.这时线程A恢复,执行方法比较,发现自己手里的值数字3和主内存的值数字4不一致,说明该值已经被其它线程抢先一步修改过了,那A线程本次修改失败,只能重新读取重新来一遍了。

5.线程A重现获取value值,因为变量value值被修饰,所以其它线程对它的修改,线程A总是能够看到,线程A继续执行 进行比较替换,直到成功。

小总结:

​ CAS()比较 当前工作内存中的值,和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止。

​ CAS应用,CAS有3个操作数,内存值V,旧的预期值A,要修改的更新值B。

​ 当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

--》 CAS–》–》CAS底层思想–》ABA–》原子引用更新–》如何规避ABA问题

CAS的缺点:

​ 1.循环时间长开销大(我们可以看到方法执行时,有个do while),

 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;}

如果CAS失败,会一直尝试,如果CAS长时间一直不成功,可能会给CPU带来很大的开销。

​ 2.只能保证一个共享变量的原子操作

​ 对于一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操纵,

​ 但是,

​ 对多个共享变量操作时(像加锁,我们可以保证一段代码),循环CAS就无法保证操作的原子性,这个时候就可以用锁 来保证原子性。

​ 3.引来ABA问题?(原子类的ABA问题谈谈?原子更新引用知道么?)

​ ABA:狸猫换太子

​ CAS会导致“ABA问题”。

​ CAS算法势下一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个事件差类会导致数据的变化。

​ 比如说一个线程one从内存位置V取出A,这个时候,另一个线程two也曾那个内存中取出A,并且线程two进行了一些操作将值变成了B,然后线程

​ two又将V位置的数据变成A,这时候线程one女性CAS操作发现内存中仍然是A,然后线程one操作成功。

​ 尽管线程one的CAS操作成功,但不代表这个线程就是没有问题的。

解决ABA问题???理解原子引用 + 新增一种机制,那就是修改版本号(类似时间戳)

​ 原子引用

@Getter
@ToString
@AllArgsConstructor
class User {String userName;int    age;}public class AtomicReferenceDemo {public static void main(String[] args) {User z3 = new User("z3",22);User li4 = new User("li4",25);AtomicReference atomicReference = new AtomicReference<>();atomicReference.set(z3);System.out.println(atomicReference.compareAndSet(z3, li4)+"\t"+atomicReference.get().toString());System.out.println(atomicReference.compareAndSet(z3, li4)+"\t"+atomicReference.get().toString());}
}==================================================
true	User(userName=li4, age=25)
false	User(userName=li4, age=25)

​ 时间戳的原子引用

//ABA问题的解决     AtomicstampedRefence
public class ABADemo {static AtomicReference reference= new  AtomicReference(100);static AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>(100,1);public static void main(String[] args) {System.out.println("=====================以下时ABA问题的产生====================");new Thread(()->{reference.compareAndSet(100,101);reference.compareAndSet(101,100);},"t1").start();new Thread(()->{//暂停一秒钟t2线程,保证t1下次呢很难过完成了1次ABA操作。try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(reference.compareAndSet(100, 2018)+"\t"+reference.get());},"t2").start();System.out.println("=====================以下时ABA问题的解决====================");new Thread(()->{int stamp = atomicStampedReference.getStamp();System.out.println( Thread.currentThread().getName()+"\t第一次版本号" +atomicStampedReference.getStamp());//暂停一秒钟t3线程try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);System.out.println( Thread.currentThread().getName()+"\t第二次版本号" +atomicStampedReference.getStamp());atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);System.out.println( Thread.currentThread().getName()+"\t第三次版本号" +atomicStampedReference.getStamp());},"t3").start();new Thread(()->{int stamp = atomicStampedReference.getStamp();System.out.println( Thread.currentThread().getName()+"\t第一次版本号" +stamp);//暂停一秒钟t3线程try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}boolean b = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);System.out.println(Thread.currentThread().getName()+"\t修改成功否:"+b+"当前最新时间版本号"+atomicStampedReference.getStamp());System.out.println(Thread.currentThread().getName()+"\t当前实际最新值:"+atomicStampedReference.getReference());},"t4").start();}
}
====================================================================
=====================以下时ABA问题的产生====================
=====================以下时ABA问题的解决====================
t3	第一次版本号1
t4	第一次版本号1
t3	第二次版本号2
true	2018
t3	第三次版本号3
t4	修改成功否:false当前最新时间版本号3
t4	当前实际最新值:100

25.我们知道不安全,请编写一个不安全的案例并给出解决方案?

/*
* 集合类不安全的问题
*   ArrayList
* */
public class ContainerNotSafeDemo {public static void main(String[] args) {//new ArrayList();//构建一个空的arraylist,伴随着是一个空的长度为10的数组;//List list = Arrays.asList("a","b","c");//List list = Collections.synchronizedList(new ArrayList<>());List list = new CopyOnWriteArrayList<>();//List list = new ArrayList<>();list.forEach(System.out::println);for (int i = 1; i <= 30; i++) {new Thread(()->{list.add(UUID.randomUUID().toString().substring(0,8));System.out.println(list);},String.valueOf(i)).start();}//java.util.ConcurrentModificationException/** 1.故障现象*       java.util.ConcurrentModificationException* 2.导致原因*       并发争抢修改导致,参考我们的花名册签名情况。*       一个人则会那个在写入,另外一个同学过来争夺,导致数据不一致异常,并发修改异常,* 3.解决方案*       1.new Vector<>();*       2.List list = Collections.synchronizedList(new ArrayList<>());*       3.new CopyOnWriteArrayList<>();* 4.优化建议(同样的错误不犯第2次)** *//** 笔记*       写时复制* CopyOnWrite容器即写时复制的容器,往一个容器添加元素的时候,不直接往当前容器object[]添加,而是先将当前容器object[]进行Copy,复制出一个新的容器Object[]*  newElement,然后新的容器object[] newElements里添加元素,添加完元素之后,再将复原容器的引用指向新的容器setArray(newElements);这样做的好处是可以对CopyOnWrite*  容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素,所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器*  public boolean add(E e) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;Object[] newElements = Arrays.copyOf(elements, len + 1);newElements[len] = e;setArray(newElements);return true;} finally {lock.unlock();}}* */}
}

/*
* 集合类不安全的问题
*   HashSet也是一个不安全的集合,和ArrayList解决线程不安全的问题一样
*   hashset的底层是hashmap。
*   那为什么hashset.set(6);可以直接添加一个呢?
*   因为它的add方法底层也是调用的hashmap的put方法,put的value是一个常量object,这个对象是null。
* */

也是不安全的,juc包里面有个可以解决并发问题。

集合工具类里面也是有api对应的方法使得线程安全。

26.公平锁/非公平锁/可重入锁/递归锁/自旋锁/ 谈谈你的理解?请手写一个自旋锁。

​ 1.公平锁:是指多个县城按照申请的顺序来获取锁,类似排队打饭,先来后到。

​ 2.非公平锁: 是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能造成优先级反转 或饥饿现象。

​ 公平锁与非公平锁的区别:

​ 公平锁/非公平锁,并发包中的创建可以指定构造函数的类型来得到公平锁或非公平锁,默认是非公平锁。

​ 关于两者的区别:

​ **公平锁: a fair lock in the order in which they it **

​ 公平锁,就是很公平,在并发环境中,每个线程在获取锁时,会先查看此锁维护的等待队列,如果为空,或者当前线程时等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照Fifo的规则从队列中取到自己

​ 非公平锁:非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁的那种方式。

​ **java 而言,通过构造函数指定锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。 **

​ 对于而言,也是一种非公平锁。

3.可重入锁(又名递归锁)

​ 是指同一线程外层函数获得锁之后,内层递归函数仍然获取该锁的代码。在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。

​ 也就是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。

​ /就是一个典型的可重入锁。

​ 可重入锁最大做哟个是避免死锁。

/** 可重入锁(也叫递归锁)* 线程操纵资源类* case 1.证明sychronized是可重入锁* 下面是打印内容* 12	 invoked sendSMS()
12	 ################### invoked sendEmail()
13	 invoked sendSMS()
13	 ################### invoked sendEmail()** ==========================================* case  2.证明ReentrantLock也是一个可重入锁** */
class Phone implements Runnable {public synchronized void sendSMS() throws Exception {System.out.println(Thread.currentThread().getId()+"\t invoked sendSMS()");sendEmail();}public synchronized void sendEmail() throws Exception {System.out.println(Thread.currentThread().getId()+"\t ################### invoked sendEmail()");}Lock lock = new ReentrantLock();@Overridepublic void run() {get();}public void get() {lock.lock();try{System.out.println(Thread.currentThread().getId()+"\t ################### invoked get()");set();}finally {lock.unlock();}}public void set() {lock.lock();try{System.out.println(Thread.currentThread().getId()+"\t ################### invoked set()");}finally {lock.unlock();}}
}
public class ReenterLockDemo {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{try {phone.sendSMS();} catch (Exception e) {e.printStackTrace();}},"t1").start();new Thread(()->{try {phone.sendSMS();} catch (Exception e) {e.printStackTrace();}},"t2").start();//==================================================Thread t3 = new Thread(new Phone());Thread t4 = new Thread(new Phone());t3.start();t4.start();}
}

4.自旋锁:

​ 自旋锁()

​ 是指尝试取锁的线程不会立即阻塞,而是采用循环的方式尝试取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU

/*
* 题目:实现/手写一个自旋锁
* 自旋锁的好处:循环比较获取直到成功为止,没有类似wait的阻塞。
*
* 通过CAS操作完成自旋锁,A线程先进来调用mylock方法自己持有锁5秒钟,B随后进来发现当前有线程持有锁
* ,不是null,所以只能通过自选等待,直到A释放锁后B随后抢到。
* 原子整形类:AtomicInteger
* 原子引用类(引用就是对象的意思):
*
* */
public class SpinLockDemo {//原子引用线程AtomicReference atomicReference = new AtomicReference<>();public void myLock() {Thread thread = Thread.currentThread();System.out.println(Thread.currentThread().getName()+"\t come in OOOO");while (!atomicReference.compareAndSet(null,thread)) {}}public void myUnlock() {Thread thread = Thread.currentThread();atomicReference.compareAndSet(thread,null);System.out.println(Thread.currentThread().getName()+"\t invoked myUnlock()");}public static void main(String[] args) throws Exception {SpinLockDemo spinLockDemo = new SpinLockDemo();new Thread(()->{spinLockDemo.myLock();try {System.out.println("线程名称:"+ Thread.currentThread().getName()+"即将睡5秒钟");Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}spinLockDemo.myUnlock();},"AA").start();System.out.println("线程名称:"+ Thread.currentThread().getName()+"即将睡3秒钟");Thread.sleep(3000);new Thread(()->{spinLockDemo.myLock();try {System.out.println("线程名称:"+ Thread.currentThread().getName()+"即将睡5秒钟");Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}spinLockDemo.myUnlock();},"BB").start();}
}
=========================================================================
线程名称:main即将睡3秒钟
AA	 come in OOOO
线程名称:AA即将睡5秒钟
BB	 come in OOOO
AA	 invoked myUnlock()
线程名称:BB即将睡5秒钟
BB	 invoked myUnlock()

27.独占锁(写锁)/共享锁(读锁)/互斥锁

独占锁:指该锁一次只能被一个线程所持有。对于和而言都是独占锁。

共享锁:指该锁可被多个线程所持有。

对于ck其读锁是共享锁,其写锁是独占锁。

读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。

锁的演变过程:sync —> Lock ------>ck

class MyCahe {//资源类private volatile Map map = new HashMap<>();//private Lock lock = new ReentrantLock();private ReentrantReadWriteLock lock =   new ReentrantReadWriteLock();public void put(String key, String value) throws InterruptedException {lock.writeLock().lock();try{System.out.println(Thread.currentThread().getName()+"\t 正在写入:"+key);Thread.sleep(300);map.put(key,value);System.out.println(Thread.currentThread().getName()+"\t 写入完成");}catch(Exception e){e.printStackTrace();}finally{lock.writeLock().unlock();}}public void get(String key) throws InterruptedException {lock.readLock().lock();try{System.out.println(Thread.currentThread().getName()+"\t 正在读取:"+key);Thread.sleep(300);Object result = map.get(key);System.out.println(Thread.currentThread().getName()+"\t 读取完成");}catch(Exception e){e.printStackTrace();}finally{lock.readLock().unlock();}}
}
/*
* 多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。
* 但是
* 如果有一个线程想去写共享资源来,就不应该再有其他线程可以对该资源进行读或写
* 小总结:
*           读-读能共存
*           读-写不能共存
*           写-写不能共存
*
*           写操作: 原子 + 独占,整个过程必须是一个完整的统一体,中间不许被分割,被打断。
* */
public class ReentrantReadWriteLockDemo {public static void main(String[] args) {MyCahe myCahe = new MyCahe();for (int i = 0; i < 5; i++) {final int tempInt = i;new Thread(()->{try {myCahe.put(tempInt+"", tempInt+"");} catch (InterruptedException e) {e.printStackTrace();}},i+"").start();}for (int i = 0; i < 5; i++) {final int tempInt = i;new Thread(()->{try {myCahe.get(tempInt + "");} catch (InterruptedException e) {e.printStackTrace();}},i+"").start();}}
}
===================================================================================
0	 正在写入:0
0	 写入完成
1	 正在写入:1
1	 写入完成
2	 正在写入:2
2	 写入完成
3	 正在写入:3
3	 写入完成
4	 正在写入:4
4	 写入完成
0	 正在读取:0
1	 正在读取:1
2	 正在读取:2
3	 正在读取:3
4	 正在读取:4
3	 读取完成
4	 读取完成
1	 读取完成
2	 读取完成
0	 读取完成

28.//使用过么?—JUC关键的包

枚举小技巧,长见识了。。。----》让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒,主要有两个方法,当一个或多个线程调用方法会将计数器减1(调用方法的线程不会阻塞),当计数器的值变为零时,因调用await()方法被阻塞的线程就会被唤醒,继续执行

/*** 枚举是1.5出的新的特性*/
public enum CountryEnum {ONE(1, "齐"), TWO(2, "楚"), THREE(3, "燕"), FOUR(4, "赵"), FIVE(5, "魏"), SIX(6, "韩");CountryEnum(Integer retCode, String retMessage) {this.retCode = retCode;this.retMessage = retMessage;}@Getterprivate Integer retCode;@Getterprivate String retMessage;public static CountryEnum forEach_CountryEnum(int index) {CountryEnum[] values = CountryEnum.values();for (CountryEnum value : values) {if (index == value.getRetCode()) {return value;}}return null;}}
====================================================================================
public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {CountDownLatch countDownLatch = new CountDownLatch(6);for (int i = 1; i <= 6; i++) {new Thread(()->{System.out.println(Thread.currentThread().getName()+"\t 国,被灭");countDownLatch.countDown();},CountryEnum.forEach_CountryEnum(i).getRetMessage()).start();}countDownLatch.await();System.out.println(Thread.currentThread().getName()+"\t **************秦帝国,一统华夏");
/*楚	 国,被灭
赵	 国,被灭
齐	 国,被灭
魏	 国,被灭
燕	 国,被灭
韩	 国,被灭
main	 **************秦帝国,一统华夏*/}private static void closerDoor() throws InterruptedException {CountDownLatch countDownLatch = new CountDownLatch(6);for (int i = 0; i < 6; i++) {new Thread(()->{System.out.println(Thread.currentThread().getName()+"\t 上完自习离开教室");countDownLatch.countDown();},String.valueOf(i)).start();}countDownLatch.await();System.out.println(Thread.currentThread().getName()+"\t 班长最后关门走人。");
/*1	 上完自习离开教室
4	 上完自习离开教室
3	 上完自习离开教室
2	 上完自习离开教室
0	 上完自习离开教室
5	 上完自习离开教室
main	 班长最后关门走人。*/}
}

的字面意思是可循环()使用的屏障()。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程达到一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障的才会开门,所有被屏障拦截的线程才会继续肝活,线程进入屏障通过的await()方法。

public class CyclicBarrierDemo {public static void main(String[] args) {//CyclicBarrier(int parties,Runnable barrierAction)CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {System.out.println("********7棵龙珠已集齐***********召唤神龙**********");});for (int i = 1; i <= 7; i++) {final int tempInt = i;new Thread(()->{System.out.println(Thread.currentThread().getName()+"\t 收集到第:"+tempInt+"龙珠");try {cyclicBarrier.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}},String.valueOf(i)).start();}}
}
===============================================================
1	 收集到第:1龙珠
4	 收集到第:4龙珠
5	 收集到第:5龙珠
6	 收集到第:6龙珠
2	 收集到第:2龙珠
7	 收集到第:7龙珠
3	 收集到第:3龙珠
********7棵龙珠已集齐***********召唤神龙**********

信号量主要有两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。

public class SemaphoreDemo {public static void main(String[] args) {Semaphore semaphore = new Semaphore(3);//模拟三个停车位for (int i = 1; i <= 6; i++) {//模拟6个车子,抢3个车位new Thread(()->{try {//下面意思是抢到线程semaphore.acquire();System.out.println(Thread.currentThread().getName()+"\t 抢到车位");try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println(Thread.currentThread().getName()+"\t 停车三秒后离开车位");} catch (InterruptedException e) {e.printStackTrace();}finally {//下面意思是释放线程semaphore.release();}},String.valueOf(i)).start();}}=========================================================================
1	 抢到车位
3	 抢到车位
2	 抢到车位
1	 停车三秒后离开车位
3	 停车三秒后离开车位
2	 停车三秒后离开车位
5	 抢到车位
4	 抢到车位
6	 抢到车位
4	 停车三秒后离开车位
6	 停车三秒后离开车位
5	 停车三秒后离开车位

29.阻塞队列你知道么?

/*
* 1.ArrayBlockingQueue: 是一个基于数组结构的有界阻塞队列,此队列FIFO(先进先出),原则对元素进行排序。
* 2.LinkedBlockingQueue: 是一个基于链表结构的阻塞队列,此队列按照FIFO(先进先出)排序元素,吞吐量通常要高于ArrayBlockingQueue
* 3.SynchronousQueue: 是一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue
*
* 1 队列 :先进先出,栈: 先进后出
* 阻塞队列,顾名思义,首先它是一个队列,而一个阻塞队列在数据结构钟所起的大致作用
*
*       Thread1                                       Thread2
*           |                                            |
*           ------Put----》BlockingQueue《-------take-----
*           线程1往阻塞队列钟添加元素,而线程2从阻塞队列钟移除元素
* 当阻塞队列是空时,从队列中**获取**元素的操作将会被阻塞。
* 当阻塞队列是满时,往队列里**添加**元素的操作将会被阻塞
*
* 试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。
* 同样
* 试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他线程从列中移除一个或多个元素或者完全清空队列
* 后使队列重新边得空闲起来并后续新增
*
* 2 阻塞队列
*   2.1 阻塞队列有没有好的一面?
*   在多线程领域: 所谓阻塞,在某些情况下会**挂起**线程(即阻塞),一旦条件满足,被挂起的线程又会自动**被唤醒**
*
*   为什么需要BlockingQueue
*       好处是我们不需要关心,什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了
*   在concurrent包发布以前,在多线程环境下,**我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,**
*   而这会让我们的程序带来不小的复杂度。
*
*   2.2 不得不阻塞,你如何管理?
* */

队列的种类分析:

​ : 由数组结构组成的有界阻塞队列。

​ : 由链表结构组成的有界(但大小默认值为.//这个值太大了,大约21亿//)阻塞队列

​ e : 支持优先级排序的无界队列

​ : 使用优先级队列实现的延迟无界阻塞队列

​ :不存储元素的阻塞队列,也即单个元素的队列。 专属定制版—》生产一个,消费一个。。。

​ : 由链表结构组成的无界阻塞队列。

​ : 由链表结构组成的双向阻塞队列。

方法类型抛出异常特殊值阻塞超时

插入

add(e)

offer(e)

put(e)

offer(e,time,unit)

移除

()

poll()

take()

poll(time,unit)

检查

()

peek()

不可用

不可用

抛出异常当阻塞队列满时,再往队列里add插入一个元素会抛出n:Queue full

当阻塞队列空时,再往队列里面移除元素会抛出on

特殊值

插入方法,成功true失败false

移除方法,成功返回出队列的元素,队列里面没有就返回null

一直阻塞

当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞生产线程直到put数据or相应中断退出。

当阻塞队列空时,消费者线程试图从队列里take元素,队列会一直阻塞消费者线程直到队列可用。

超时退出

当阻塞队列满时,队列会阻塞生产者线程一定时间,超过时间后限时后生产者线程会退出

public class BlockingQueueDemo {public static void main(String[] args) {//List list = new ArrayList();BlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);blockingQueue.offer("a",2L ,TimeUnit.SECONDS);blockingQueue.offer("a",2L ,TimeUnit.SECONDS);blockingQueue.offer("a",2L ,TimeUnit.SECONDS);blockingQueue.offer("a",2L ,TimeUnit.SECONDS);/*blockingQueue.put("a");blockingQueue.put("a");blockingQueue.put("a");System.out.println("=====================================");blockingQueue.put("x");*/
/*        System.out.println(blockingQueue.add("a"));System.out.println(blockingQueue.add("b"));System.out.println(blockingQueue.add("c"));System.out.println(blockingQueue.element());System.out.println(blockingQueue.remove());System.out.println(blockingQueue.remove());System.out.println(blockingQueue.remove());*/System.out.println(blockingQueue.offer("a"));System.out.println(blockingQueue.offer("a"));System.out.println(blockingQueue.offer("a"));System.out.println(blockingQueue.offer("a"));System.out.println(blockingQueue.peek());System.out.println(blockingQueue.poll());System.out.println(blockingQueue.poll());System.out.println(blockingQueue.poll());System.out.println(blockingQueue.poll());}
}

:没有容量。与其他不同,是一个不存储元素的.每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。

/*
* 阻塞队列SynchronousQueue演示*/
public class SynchronousQueueDemo {public static void main(String[] args) {SynchronousQueue queue = new SynchronousQueue<>();new Thread(()->{try {System.out.println(Thread.currentThread().getName() + "\t put 1");queue.put("1");System.out.println(Thread.currentThread().getName() + "\t put 2");queue.put("2");System.out.println(Thread.currentThread().getName() + "\t put 3");queue.put("3");} catch (InterruptedException e) {e.printStackTrace();}},"AAA").start();new Thread(()->{try {try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println(queue.take());  try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println(queue.take());  try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println(queue.take());} catch (InterruptedException e) {e.printStackTrace();}},"BBB").start();}
}
===============================================================================
AAA	 put 1
1
AAA	 put 2
2
AAA	 put 3
3

阻塞队列用在哪里?====》对应着生产者和消费者。一种是传统版本的生产者消费者,一种是阻塞队列版生产者消费者

上面,左边的是老的生产者消费者模式,右边是对应的新的lock锁的,生产者消费者模式。===》都是传统的写法

class ShareData {//资源类private int number = 0;private Lock lock =  new ReentrantLock();private Condition condition = lock.newCondition();public void increment()throws Exception {lock.lock();try{//1.判断while (number != 0) {//等待,不能生产condition.await();}//2.干活number++;System.out.println(Thread.currentThread().getName()+"\t" +number);//3.通知唤醒condition.signalAll();}catch(Exception e){e.printStackTrace();}finally{lock.unlock();}}public void decrement()throws Exception {lock.lock();try{//1.判断while (number == 0) {//等待,不能生产condition.await();}//2.干活number--;System.out.println(Thread.currentThread().getName()+"\t" +number);//3.通知唤醒condition.signalAll();}catch(Exception e){e.printStackTrace();}finally{lock.unlock();}}}/*
* 题目:一个初始值为零的变量,两个线程对其交替操作,一个加1一个减1,来5轮
*
* 1     线程      操作(方法)          资源类
* 2     判断      干活          通知
* 3     防止虚假唤醒机制
* */public class ProdConsumer {public static void main(String[] args) {ShareData shareData = new ShareData();new Thread(()->{for (int i = 1; i <= 5; i++) {try {shareData.increment();} catch (Exception e) {e.printStackTrace();}}},"AAA").start();new Thread(()->{for (int i = 1; i <= 5; i++) {try {shareData.decrement();} catch (Exception e) {e.printStackTrace();}}},"BBB").start();}
}

30.和Lock有什么区别?用新的lock有什么好处?请举例说明。

1.原始构成

​ 是关键字属于JVM层面。

​ 底层是(进入)(底层是通过对象来完成的,其实wait/等方法也依赖于对象只有在同步块或方法中才能调用wait/等方法)

拓展:注意当你使用关键字的时候,底层的编译里面是只有一个,但却有两个,为什么呢?,因为是可重入锁,两个,当发生异常时,一个退出起作用,最后相当于再放开一次,确保对象的释放,这样避免产生死锁。

​ Lock是具体类(java.util..Locks.lock)是api层面的锁。

2.使用方法

​ 不需要用户去手动释放锁,当代码执行完后系统会让线程释放对象锁的占用。

​ 则需要用户去手动释放锁若没有主动释放锁,就有可能导致出现死锁现象。

​ 需要Lock()和()方法配合try/语句块来完成。

3.等待是否可中断

​ 不可中断,除非抛出异常或者正常运行完成

​ 可中断。1.设置超时方法(long , unit)

​ 2.()放代码块中,调用()方法可中断。

4.加锁是否公平

​ 非公平锁

​ 两者都可以,默认非公平锁,构造方法可以传入值,true为公平锁,false为非公平锁

锁绑定多个条件

没有

用了实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像要么随机唤醒一个线程,要么唤醒全部线程。

/** 题目: 多线程之间按顺序调用,实现A -》 B -》 C三个线程启动,要求如下:* AA打印5次,BB打印10次,CC打印15次* 紧接着* AA打印5次,BB打印10次,CC打印15次* 。。* */
class ShareResoure {private int number = 1;//A:1,B:2,C:3private Lock lock = new ReentrantLock();private Condition c1 = lock.newCondition();private Condition c2 = lock.newCondition();private Condition c3 = lock.newCondition();public void print5() {lock.lock();try {//1.判断,为了避免虚假唤醒,的用while进行判断while (number != 1) {c1.await();}//2.干活for (int i = 1; i <= 5; i++) {System.out.println(Thread.currentThread().getName() + "\t" + i);}//3.通知B线程,number = 2;c2.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public void print10() {lock.lock();try {//1.判断,为了避免虚假唤醒,的用while进行判断while (number != 2) {c2.await();}//2.干活for (int i = 1; i <= 10; i++) {System.out.println(Thread.currentThread().getName() + "\t" + i);}//3.通知B线程,number = 3;c3.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public void print15() {lock.lock();try {//1.判断,为了避免虚假唤醒,的用while进行判断while (number != 3) {c3.await();}//2.干活for (int i = 1; i <= 10; i++) {System.out.println(Thread.currentThread().getName() + "\t" + i);}//3.通知B线程,number = 1;c1.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}}public class SyncAndReentrantLockDemo {public static void main(String[] args) {ShareResoure shareResoure = new ShareResoure();new Thread(() -> {for (int i = 1; i <= 10; i++) {shareResoure.print5();}}, "A").start();new Thread(() -> {for (int i = 1; i <= 10; i++) {shareResoure.print10();}}, "B").start();new Thread(() -> {for (int i = 1; i <= 10; i++) {shareResoure.print15();}}, "C").start();}
}
====================================================================
A	1
A	2
A	3
A	4
A	5
B	1
B	2
B	3
B	4
B	5
B	6
B	7
B	8
B	9
B	10
C	1
C	2
C	3
C	4
C	5
C	6
C	7
C	8
C	9
C	10
。。。。。。。。。。。。。。。。。。。。

3.0版本的生产者消费者,根本不用让线程等待,唤醒,都是自动操作的,代码如下:

/** volatile/CAS/automicInteger/BlockQueue/线程交互/原子引用* */
class MyResource {private volatile boolean FLAG = true;//默认开启,进行生产+消费private AtomicInteger atomicInteger = new AtomicInteger();BlockingQueue blockingQueue = null;//高手一定传接口。一定不会传类public MyResource(BlockingQueue blockingQueue) {this.blockingQueue = blockingQueue;System.out.println(blockingQueue.getClass().getName());}public void myProd() throws Exception {String data = null;boolean retValue;while (FLAG) {data = atomicInteger.incrementAndGet() + "";retValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);if (retValue) {System.out.println(Thread.currentThread().getName() + "\t 插入队列" + data + "成功");} else {System.out.println(Thread.currentThread().getName() + "\t 插入队列" + data + "失败");}TimeUnit.SECONDS.sleep(1);}System.out.println(Thread.currentThread().getName() + "\t 大老板叫停了,表示Flag = false,生产动作结束");}public void myConsumer() throws Exception {String result = null;while (FLAG) {result = blockingQueue.poll(2L, TimeUnit.SECONDS);if(null == result || "".equals(result)){FLAG = false;System.out.println(Thread.currentThread().getName()+"\t 超过2秒钟没有取到蛋糕,消费退出");return;}System.out.println(Thread.currentThread().getName() + "\t 消费队列蛋糕"+result+"成功");}}public void stop()throws Exception {FLAG = false;}
}public class ProdConsumer_BlockQueueDemo {public static void main(String[] args) throws Exception {MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));new Thread(()->{System.out.println(Thread.currentThread().getName()+"\t 生产线程启动");try {myResource.myProd();} catch (Exception e) {e.printStackTrace();}},"Prod").start();new Thread(()->{System.out.println(Thread.currentThread().getName()+"\t 消费线程启动");try {myResource.myConsumer();System.out.println();System.out.println();} catch (Exception e) {e.printStackTrace();}},"Consumer").start();//暂停一会线程try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }System.out.println();System.out.println();System.out.println();System.out.println("5秒钟时间到,大老板main线程叫停,活动结束");myResource.stop();}
}

下面代码是接口的调用

class MyThread implements Runnable {@Overridepublic void run() {}
}class MyThread2 implements Callable {@Overridepublic Integer call() throws Exception {System.out.println("**************************** come in");try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }return 1024;}
}
/*
* java
* 多线程中,第3种获取多线程的方式
* */
public class CallableDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {//两个线程,一个main线程,一个AA线程。FutureTask futureTask = new FutureTask<>(new MyThread2());new Thread(futureTask,"AAA").start();new Thread(futureTask,"BBB").start();int result02 = futureTask.get();System.out.println(Thread.currentThread().getName()+"******************************");int result01 = 100;/*  while (!futureTask.isDone()) {}*///建议放在最后,要求获得Callable线程的计算结果,如果没有计算完成就要去强求,会导致阻塞,直到计算完成System.out.println("********result"+(result01+result02));}
}

31.线程池用过么?谈谈你的理解?

线程池做的工作是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其他线程执行完毕。再从队列中取出任务来执行。

他的主要特点为: 线程复用:控制最大并发数:管理线程。

第一:降低资源消耗。通过重复利用已创建线程降低线程创建和销毁造成的消耗。

第二:提高响应的速度。当任务到达时,任务可以不需要的等到线程创建就能立刻执行。

第三:‘提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配,调优和监控。

常用的线程池:三种,实际一共5种,一种带线程调度的,还有一种是java8新特性带着一个线程池,底层都是类上进行的修改。

Executors.newFixedThreadPool(10);---》一池固定数线程,就代表10个线程,适用于执行长期任务
下面是对应着上一个的源码
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue());}-----------------------------------------------------------------------------------------------------------------------
Executors.newSingleThreadExecutor();---------》一池一线程
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue()));}-----------------------------------------------------------------------------------------------------------------------
Executors.newCachedThreadPool();-----------》一池多线程
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue());}=========================== 5种参数:  =============================================================int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue

创建固定线程数的线程池,特点如下:

​ 1.创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列种等待。执行长期的任务,性能好很多。

​ 2.创建的线程池和值是相等的,它使用的

创建单一线程数的线程池,特点如下:

​ 1.创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定的顺序执行。一个任务一个任务执行的场景。

​ 2.tor将和值都设置为1,它使用的

创建可缓存线程池特点如下:

​ 1.创建一个可缓存线程池,如果线程长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。适用于:执行很多短期异步的小程序或者负载较轻的服务器。

​ 2.,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程

小总结,都是在这个类上改造的,都需要用到阻塞队列。

常用的有5个参数,实际上传入的是7个参数。

1.: 线程池中的常驻核心线程数。在创建了线程池后,当有请求任务来之后。就会安排池中的线程去执行请求任务,近似理解为今日当值线程。 当线程中的线程数目达到后,就会把达到的任务放到缓存队列当中。

2.: 线程池能够容纳同时执行的最大线程数,此值必须是大于等于1的

3.:多余的空闲线程的存活时间。当前线程池数量超过时,当空闲时间达到值时,多余空闲线程会被销毁直到只剩下

个线程为止。

4.unit:的单位

5.: 表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可

6.:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数

7.: 任务队列,被提交但尚未被执行的任务。

以下重要、以下重要、以下重要

1.在创建了线程后,等待提交过来的任务请求。

2.当调用()方法添加一个请求任务时,线程池会做如下判断:

​ 2.1 如果正在运行的线程数量小于,那么马上创建线程运行这个任务;

​ 2.2如果正在运行的线程数量大于或等于,那么将这个任务放入消息队列;

​ 2.3如果这时候队列满了且正在运行的线程数量还小于,那么还是要创建非核心线程立刻运行这个任务;

​ 2.4如果队列满了且正在运行的线程数量大于或等于,那么线程池会启动饱和和拒绝策略来执行。

3.当一个线程完成任务时,它只会从队列中取下一个任务来 执行。

4.当一个线程无事可做超过一定的时间()时,线程池会判断;

​ 如果当前运行的线程数大于,那么这个线程就会停掉。

​ 所以线程池的所有任务完成后它最终会收缩到的大小。

32.线程池用过吗?生产上你如何设置合理参数?

​ 谈谈你对拒绝策略的理解?

​ 它是什么?等待队列也已经排满了,再也塞不下新任务了,

​ 同时,线程池中的max线程也达到了,无法继续为新任务服务。

​ 这时候,我们就需要拒绝策略机制合理的处理这个问题。

​ JDK内置的4种拒绝策略:

​ (默认):直接抛出异常阻止系统正常运行。

​ :“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。

​ :抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前事务。

​ :直接丢弃任务,不给予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。

​ 以上内置拒绝策略均实现了dler接口

/*
*
* 手写线程池。
* */
public class MyThreadPoolDemo2 {public static void main(String[] args) {//6核12线程System.out.println(Runtime.getRuntime().availableProcessors());//Cpu密集的意思是该任务需要大量的运算,而没有阻塞,cpu一直全速运行。//Cpu密集任务只有在真正的多喝Cpu上才可能得到加速(通过多线程)。//而在单核Cpu上(悲剧把?),无论你开几个模拟的多线程该任务都不可能得到加速,因为Cpu的运算能力就那些。//Cpu密集型任务配置尽可能少的线程数量//一般公式CPu核数+1个线程的线程池//由于io密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数的两倍//io密集型,即该任务需要大量的io,即大量的阻塞。//在单线程上运行IO密集型的任务会导致浪费大量的cpu运算能力浪费在等待。//所以在io密集型任务中使用多线程可以大大的加速程序运行,即使在单核cpu上,这种加速主要是利用了被浪费掉的阻塞时间。//io密集型时。大部分线程都阻塞,故需要多配置线程数://参考公式:cpu核数/1-阻塞系数       阻塞系数在0.8~0.9之间//比如8核Cpu:8/1 - 0.9= 80 个线程ExecutorService threadPool = new ThreadPoolExecutor(2,5,1L,TimeUnit.SECONDS,new LinkedBlockingQueue(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());//这个是默认的,直接抛出RejectedExecutionExecption异常阻止系统正常运行//new ThreadPoolExecutor.CallerRunsPolicy());“调用者运行”一种调节机制,该策略既不会放弃任务,也不会抛出异常,而是将某些任务回退给线程的调用者//new ThreadPoolExecutor.DiscardPolicy());抛弃队列中等待最久的任务,然后把当前任务加入队列种尝试再次提交当前任务。//new ThreadPoolExecutor.DiscardOldestPolicy());直接丢弃任务,不给予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。try {for (int i = 1; i <= 8; i++) {threadPool.execute(()->{System.out.println(Thread.currentThread().getName()+"\t 办理业务");});}} catch (Exception e) {e.printStackTrace();} finally {threadPool.shutdown();}}}

33.思索编码以及定位分析?

1.死锁是什么?产生死锁的主要原因?

死锁是指连哥哥或多个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那他们都将无法推进下去,如果系统资源重组,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。

死锁的主要原因:1.系统资源不足2.进程运行推进的顺序不合适。3.资源分配不当·

34.JVM的相关知识?

GC的作用域:方法区和堆,这是线程共享区

常见的垃圾回收算法:

​ 1.引用计数:缺点:每次对象赋值时均要维护引用计数器,且计数器本身也有一定的消耗。较难处理循环引用。jvm的实现一般不采用这种方式。

​ 2.复制:

​ 3.标记清除

​ 40标记整理

1.JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC RooTS?

​ 什么是垃圾?

​ 简单的说,就是内存中不再被使用到的空间就是垃圾。

​ 要进行垃圾回收,如何判断一个对象是否可以被回收?

​ 1.引用计数法

​ 2.枚举根节点做可达性分析(根据索路径)

以下可以作为GC ROOTS的对象

​ 1.虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。

​ 2.方法区中的类静态属性引用的对象。

​ 3.方法区中常量引用的对象。

​ 4.本地方法栈中JNI( )—本地方法栈中JHI(即一般说的方法中)引用的对象

35.jvm调优和参数配置,请问如何盘点查看jvm系统默认值。

​ Xms:初始堆空间的最小值

​ Xmx:初始化堆空间的最大值

Xms和Xmx最好初始值都一样,避免GC频繁的收集,忽高忽低

​ Xss:栈空间

调试一定是 from 从初始值 to 你的期望值

​ JVM的参数类型:

​ 1.标配参数:常用的例如:1.java - 2.java-help 3.java -

​ 2.x参数(了解)1.-Xint 解释执行 2.-Xcomp第一次使用就编译成本地代码 3.- 混合模式

​ 3.xx参数:常用的查看命令是jps -l 这个是查看java运行时,相关进程的, jinfo -flag 19436 查看运行进程的相关的配置的大小的

​ 类型:-XX:+ 或者 - 某个属性值 || +表示开启 -表示关闭

​ 举类:是否打印GC收集细节:-XX:- -XX:+

​ 是否使用串行垃圾回收器: -XX:- -XX:+

​ KV设值类型:公式: -XX:属性key=属性值value

​ 举类:-XX:=128m

​ 举类:-XX:=15 这个参数是默认回收15次,就会调用到养老区

​ jinfo举例,如何查看当前运行程序的配置

题外话(坑提)

​ case:两个经典参数:-Xms 和-Xmx

​ 这个你如何解释?

​ -Xms 就等价于 -XX: 等价于初始化的堆内存

​ -Xmx 就等价于 -XX: 等价于最大的堆内存

​ 第一种,查看参数

​ jps

​ jinfo -flag 具体参数 java进程编号

​ jinfo -flag java进程编号

​ 第二种,查看参数盘点家底

 bool UseParallelGC                            := true                              {product}bool UseParallelOldGC                       = true                               {product}bool UsePerfData                               = true                                {product}

​ 查看JVM默认值:-XX:+ 主要查看初始默认值 java -XX:+ -

​ -XX:+ 普通等于号,就是没被修改过的 这个冒号等于号,就是被人为修改过,或者是被jvm自己初始化动态加载的时候修改过。

​ 举例,运行java命令的同时打印出参数

​ -XX:+s 这个默认查看的是垃圾回收器相关的东西

常用参数:

​ -Xms:初始大小内存,默认为物理内存1/64

​ 等价于-XX:

​ -Xmx:最大分配内存,默认为物理内存1/4

​ 等价于-XX:

​ -Xss:设置单个线程栈的大小,一般默认为512k ~ 1024k

​ 等价于-XX:

​ -Xmn:设置年轻代大小

​ -XX::设置原空间大小:

​ 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。

​ 不过原空间与永久代之间的最大的区别在于:

​ 元空间并不在虚拟机中,而是使用本地内存

​ 因此,默认情况下,元空间的大小仅受本地内存限制

​ - - -XX:=1024m -XX:+

​ -XX:+ 串行垃圾回收器

​ -XX:+ 并行垃圾回收器

​ -XX::输出详细GC收集日志信息

​ GC:一般在yung区

​ :一般在养老区

-XX: 设置新生代中eden和S0/S1空间的比例

​ 默认

​ -XX:=8,Eden:S0:S1 = 8:1:1

​ 假如

​ -XX:=4,Eden:S0:S1 = 4:1:1

​ 值就是设置eden区的比例占多少,S0/S1相同。

-XX: 配置年轻代与老年代在堆的占比

​ 默认

​ -XX:=2新生代占1,老年代2,年轻代占整个堆的1/3

​ 假如

​ -XX:=4新生代占1,老年代4,年轻代占整个堆的1/5

​ 值就是设置老年代的占比,剩下的1给新生代

-XX:: 设置垃圾最大年龄

36.谈谈你对强引用、软引用、弱引用、虚引用分别是什么?

1.强引用默认支持模式

​ 当内存不足时,JVM开始垃圾回收,对于强引用的对象,就算是出现了OOM也不会对该对象进行回收,死都不收

​ 强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表明对象还“活着”,垃圾回收器不会碰这种对象。在JAVA中最常见的就是强引用,把一个对象赋值给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后豆不会被用到JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一。

​ 对于一个普通的对象,如果没有娶她的引用关系,只要超过了引用的作呕能够与或者显示地将相应(强)引用赋值为null,一般认为就是可以被垃圾收集的了(当然具体回收实际还是要看垃圾收集策略)。

2.软引用

​ 软引用是一种相对强引用弱化了一些的引用,需要用jav.lang.ref.类来实现,可以让对象豁免一些垃圾收集。

​ 对于只有软引用的对象来说,

​ 当系统内存充足时它 不会 被回收

​ 当系统内存不足时它 会 被回收

​ 软引用通常用字对内存敏感的程序中,比如高速缓存区就用到了软引用,内存够用的时候就保留,不够用就回收。

3.弱引用

​ 弱引用需要引用java.lang.ref.类来实现,它比软引用的生存期更短。

​ 对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。

假如有一个应用需要读取大量的本地图片:

​ 如果每次读取图片都是从硬盘读取则会严重影响性能。

​ 如果一次性全部加载到内存中又有可能造成内存溢出。

此时使用软引用可以解决这个问题。

​ 设计思路:用一个来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存的图片对象所占用的空间,从而有效地避免了ooM的问题。

​ 你知道弱引用的话,能谈谈吗?

就是当key的值为null的时候,只要被垃圾回收器回收了,那么它所对应的value,也就释放了。

虚引用(又称为幽灵引用)

​ 虚引用需要java.lang.ref.类来实现。

​ 顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。

​ 如果一个对象仅持有虚引用,那么它就和没有任何引用一样。在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象,虚引用必须和引用队列()联合使用。

​ 虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供了一种确保对象被以后,做某些事情的机制。

​ 的get方法总是返回null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入阶段,可以被gc回收,用来实现比机制更加灵活的回收操作。

​ 换句话说,设置虚引用关联的唯一目的,就是在这个对象被收集器回收的时候收到一个系统通知或后续添加进一步的处理。

​ java技术允许使用()方法在垃圾收集器将对象从内存中清除出去之前作必要的操作。

public class ReferenceQueueDemo {public static void main(String[] args) throws InterruptedException {Object o1 = new Object();ReferenceQueue referenceQueue = new ReferenceQueue<>();WeakReference weakReference = new WeakReference<>(o1,referenceQueue);System.out.println(o1);System.out.println(weakReference.get());System.out.println(referenceQueue.poll());System.out.println("==============================");o1 = null;System.gc();  Thread.sleep(500);System.out.println(o1);System.out.println(weakReference.get());System.out.println(referenceQueue.poll());}
}java.lang.Object@548c4f57
java.lang.Object@548c4f57
null
==============================
null
null
java.lang.ref.WeakReference@1218025c

/*
* java提供了4种引用类型,在垃圾回收的时候,都有自己各自的特点。
* ReferenceQueue是用来配合引用工作的,没有ReferenceQueue一样可以运行
*
* 创建引用的时候可以指定关联的队列。当GC释放对象内存的时候,会将引用加入到引用队列。
* 如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的内存被回收之前采取必要的行动
* 这相当于是一种通知机制。
*
* 当关联的引用队列中有数据的时候,意味着引用指向的堆内存中的对象被回收,通过这种方式,JVM允许我们在对象被销毁后,
* 做一些我们自己想做的事情。
* */
public class PhantomReferenceDemo {public static void main(String[] args) throws InterruptedException {Object o1 = new Object();ReferenceQueue referenceQueue = new ReferenceQueue<>();PhantomReference phantomReference = new PhantomReference<>(o1,referenceQueue);System.out.println(o1);System.out.println(phantomReference.get());System.out.println(referenceQueue.poll());System.out.println("================================");o1 = null;System.gc();Thread.sleep(500);System.out.println(o1);System.out.println(phantomReference.get());System.out.println(referenceQueue.poll());}
}java.lang.Object@548c4f57
null
null
================================
null
null
java.lang.ref.PhantomReference@1218025c

37.浅谈OOM的认识。

​ 报错类似于:;

​ :java heap space、GC limit 、 、 to new 、

1.java.lang.:产生这个错误,就是递归,方法调方法。

:java heap space、这两个属于错误,不是异常,这个是直接new一个大对象,内存不足,就会抱堆溢出。

GC limit :GC回收时间长时会抛出。过长的定义是,超过98%的时间用来做GC并且回收了不熬2%的堆内存,

连续多次GC,都只回收了不到2%的极端情况下才会抛出。加入不抛出GC limit 错误会发生什么情况呢?

那就是GC清理的这么点内存会很快再次填满,迫使GC再次执行,这样就形成恶行循环,

CPU使用率一直是100%,而GC却没有任何成果。

4. :导致原因:

​ 写NIO程序经常使用到来读取或者写入数据,这是一种基于通道()与缓存区()的I/O方式。

​ 它可以使用函数库直接分配堆外内存,然后通过一个存储在Java堆里面的对象作为这块内存的引用进行操作。

​ 这样能在一些场景中显著提高性能,因为避免了在Java堆和堆中来回复制数据。

​ .()第一种方式是分配JVM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对较慢。

​ .()第二种方式是分配OS本地内存,不属于GC管辖范围,由于不需要内存拷贝所以速度相对较快。

​ 但如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,对象们就不会被回收,这时候堆内存重组,但本地内存可能已经使用光了,再次尝试分配本地内存就会出现,那程序就直接崩溃了。

/*
* 配置参数:
* -Xms10m -Xmx10m  -XX:+PrintGCDetails   -XX:MaxDirectMemorySize=5m
* */
public class DirectBufferMemoryDemo {public static void main(String[] args) {System.out.println("配置的maxDirectMemory:"+(maxDirectMemory() / (double)1024 / 1024)+"MB");ByteBuffer bb = ByteBuffer.allocateDirect(6 * 1024 * 1024);}
}
====================================================================
[GC (Allocation Failure) [PSYoungGen: 2048K->504K(2560K)] 2048K->923K(9728K), 0.0008367 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
配置的maxDirectMemory:5.0MB
[GC (System.gc()) [PSYoungGen: 1582K->504K(2560K)] 2001K->1209K(9728K), 0.0008664 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 504K->0K(2560K)] [ParOldGen: 705K->999K(7168K)] 1209K->999K(9728K), [Metaspace: 3489K->3489K(1056768K)], 0.0066726 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memoryat java.nio.Bits.reserveMemory(Bits.java:693)at java.nio.DirectByteBuffer.(DirectByteBuffer.java:123)at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)at GC.DirectBufferMemoryDemo.main(DirectBufferMemoryDemo.java:20)

5. to new

​ 高并发请求服务器时,经常出现如下异常:java.lang.: to new

​ 准确的讲该 异常与对应的平台有关。

导致原因:

​ 1.你的应用创建了太多的线程了,一个应用进程创建多个线程,超过系统的承载极限

​ 2.你的服务器并不允许你的应用程序创建这么多线程,linux系统默认允许单个进程可以创建的线程数是1024个。

​ 你的应用创建超过了这个数量,就会报java.lang.: to new

解决办法:

​ 1.想办法降低你应用程序创建线程的数量,分析应用是否真的需要创建这么多线程,如果不是,改代码将线程数量降到最低

​ 2.对于有的应用,确实需要创建很多线程,远超过linux系统的默认的1024个线程的限制,可以通过修改linux服务器配置,扩大linux默认限制

6.java.lang.:

/*
* JVM参数
* -XX:MetaspaceSize=8m  -XX:MaxMetaspaceSize=8m
* Java  8及以后的版本使用Metaspace来代替永久代。
* Matespace是方法去在HotSpot中的实现,它与持久代最大的区别在于:Metaspace并不在虚拟机内存中而是使用本地内存
* 也即在java8中,class metadata(the virtual  machines  internal  presentation of Java  class)
*被存储在叫做Metaspace的native memory
* 永久代(java后被元空间Metaspace取代了),存放了以下的信息
* 虚拟机加载的类信息(例如:java.lang.String.....这些我们称之为元数据)
* 常量池
* 静态变量
* 即时编译后的代码
* 模拟Metaspace空间溢出,我们不断生成类王元空间灌,类占据的空间总是会超过Metaspace
* 指定的空间大小*  */
public class MetaspaceOOMTest {static class OOMTesT{}public static void main(String[] args) {int i = 0;//模拟计数多少次以后发生异常try {while (true) {i++;//动态生成静态类Enhancer enhancer = new Enhancer();enhancer.setSuperclass(OOMTesT.class);enhancer.setUseCache(false);enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {return methodProxy.invoke(o,args);}});enhancer.create();}} catch (Throwable e) {System.out.println("*********多少次后发生了异常"+i);e.printStackTrace();}}
}
=====================================================================
java.lang.OutOfMemoryError: Metaspaceat org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:530)at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:582)at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:131)at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319)at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:569)at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:384)at GC.MetaspaceOOMTest.main(MetaspaceOOMTest.java:46)

38.GC垃圾回收算法和垃圾收集器的关系?分别是什么请你谈谈?

GC算法(引用技术**/复制/标记/清除/标整**)是内存回收的方法论,垃圾收集器就是算法落地实现。

因为目前为止还没有完美的收集器出现,更加没有万能的收集器,只是针对具体应用最合适的收集器,进行分代收集

(例如:复制一般用在新生代,标记清除,标记整理一般用在old区)

4种主要的垃圾收集器(这是以前基本是这个4种)

​ (串行回收). (并行回收). CMS(并发). G1(G1回收)

​ 串行垃圾回收器():它为单线程环境设计且使用一个线程进行垃圾回收,会暂停所有的用户线程。所以不适合服务器环境。

​ 并行垃圾回收器():多个垃圾收集线程并行工作,此时用户线程是暂停的,适用于科学计算/大数据处理首台处理等弱交互场景

​ 并发垃圾回收器(GMS):用户线程和垃圾收集线程同事执行(不一定是并行,可能交替执行),不需要停顿用户线程,互联网公司多用它,适用对响应时间有要求的场景。

G1垃圾回收器:G1垃圾回收器将堆内存分割成不同的区域然后并发的对其进行垃圾回收。

39.怎么查看服务器默认的垃圾收集器是哪个?生产上如何配置垃圾收集器的?谈谈你对垃圾收集器的理解?

java -XX:+s - 这个是查看jvm初始化的配置参数

D:\深造一下把\源代码\activeMq\src\main\java\JUC>java -XX:+PrintCommandLineFlags  -version
-XX:InitialHeapSize=399983424 -XX:MaxHeapSize=6399734784 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePa
gesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)

默认的垃圾收集器有哪一些?:

​ ,, (CMS) ,, ,

英文缩写:

​ --------- New

​ ---------- Old

​ ------------- New

​ -------------

​ -------------------- Old

了解/模式分别是什么意思?

​ 1.适用范围:只需要掌握模式即可,模式基本不会用

​ 2.操作系统

​ 2.1 32位操作系统,不论硬件如何都是默认使用的JVM模式

​ 2.2 32位其他操作系统,2G内存同事有2个cpu已上用模式,低于该配置还是模式。

​ 2.3 64位 only 模式

​ 串行收集器():形象线程比拟1(新生代):1(老年代)

​ 一句话:一个单线程的收集器,在进行垃圾收集时候,必须暂停其他所有的工作线程知道它收集结束。

​ 串行收集器是最古老的,最稳定以及效率高的收集器,只使用一个线程去回收但其在进行垃圾收集的过程中可能会产生较长的停顿(Stop-状态)。虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个CPU环境来说,没有线程交互的开销可以获得最高的单线程垃圾收集效率,因此垃圾收集器依然是java虚拟机运行在模式下默认的 新生代 垃圾收集器。

​ 对应JVM参数是:-XX:+

​ 开启后会使用:(Young区用)+ Old(Old区用)的收集器组合

​ 表示:新生代、老年代都会使用串行回收收集器,新生代使用复制算法,拉年代使用标记-整理算法。

一般自己在idea中常配置的参数   -Xms10m   -Xmx10m -XX:+PrintGCDetails -XX:+UseSerialGC 
查看初始化启动参数:  -XX:+PrintCommandLineFlags

(并行)收集器:形象线程比拟 N(新生代):1(老年代)

一句话:使用多线程进行垃圾回收,在垃圾收集时,会Stop-The-World暂停其他所有的工作线程知道它收集结束。

收集器其实就是收集器新生代的并行多线程版本,最常见的应用场景是配合老年代的CMS GC工作,其余的行为和收集器完全一样,垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程,它是很多java虚拟机运行在模式下 新生代的默认垃圾收集器。

常用对应JVM参数:-XX:+,启用收集器,只影响新生代的收集不影响老年代。

开启上述参数后:会使用:(Young区用)+的收集器组合,新生代使用复制算法,老年代使用标记-整理算法

并行回收GC()/( )装上Java8默认开启的就是这个回收器

形象线程比拟:(新生代N):(老年代N)

收集器类似也是一个新生代垃圾回收器,使用复制算法,也是一个并行的多线程的垃圾收集器,俗称吞吐量优先收集器,一句话,串行收集器在新生代和老年代的并行化。

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了