admin 管理员组文章数量: 887053
2024年1月17日发(作者:汇编语言编译器是什么平台)
基础篇
基本功
面向对象特征
final, finally, finalize 的区别int 和 Integer 有什么区别重载和重写的区别
抽象类和接口有什么区别说说反射的用途及实现
说说自定义注解的场景及实现
HTTP 请求的 GET 与 POST 方式的区别session 与 cookie 区别
JDBC 流程MVC 设计思想
equals 与 == 的区别
集合
List 和 Set 区别List 和 Map 区别
Arraylist 与 LinkedList 区别ArrayList 与 Vector 区别HashMap 和 Hashtable 的区别HashSet 和 HashMap 区别
HashMap 和 ConcurrentHashMap 的区别HashMap 的工作原理及代码实现ConcurrentHashMap 的工作原理及代码实现
线程
创建线程的方式及实现
sleep() 、join()、yield()有什么区别
说说 CountDownLatch 原理说说 CyclicBarrier 原理
说说 Semaphore 原理说说 Exchanger 原理
说说 CountDownLatch 与 CyclicBarrier 区别ThreadLocal 原理分析
讲讲线程池的实现原理
线程池的几种方式
线程的生命周期
锁机制
说说线程安全问题volatile 实现原理悲观锁 乐观锁CAS 乐观锁
ABA 问题
乐观锁的业务场景及实现方式
核心篇
数据存储
MySQL 索引使用的注意事项说说反模式设计
说说分库与分表设计
分库与分表带来的分布式困境与应对之策
说说SQL 优化之道
MySQL 遇到的死锁问题
存储引擎的 InnoDB 与 MyISAM
数据库索引的原理
为什么要用 B-tree
聚集索引与非聚集索引的区别
limit 20000 加载很慢怎么解决选择合适的分布式主键方案选择合适的数据存储方案 ObjectId
规则
聊聊 MongoDB 使用场景倒排索引
聊聊 ElasticSearch 使用场景缓存使用
Redis 有哪些类型Redis 内部结构
聊聊 Redis 使用场景Redis 持久化机制Redis 如何实现持久化Redis 集群方案与实现Redis 为什么是单线程的缓存奔溃
缓存降级
使用缓存的合理性问题
消息队列
消息队列的使用场景
消息的重发补偿解决思路消息的幂等性解决思路消息的堆积解决思路
自己如何实现消息队列
如何保证消息的有序性
框架篇
Spring
BeanFactory 和 ApplicationContext 有什么区别
Spring Bean 的生命周期Spring IOC 如何实现
说 说 Spring AOP
Spring AOP 实现原理
动态代理(cglib 与 JDK)
Spring 事务实现方式Spring 事务底层原理
如何自定义注解实现功能Spring MVC 运行流程Spring MVC 启动流程Spring 的单例实现原理
Spring 框架中用到了哪些设计模式
Spring 其他产品(Srping Boot、Spring Cloud、Spring Secuirity、Spring Data、Spring AMQP 等)
Netty
为什么选择 Netty
说说业务中,Netty 的使用场景
原生的 NIO 在 JDK 1.7 版本存在 epoll bug
什么是TCP 粘包/拆包TCP粘包/拆包的解决办法Netty 线程模型
说说 Netty 的零拷贝Netty 内部执行流程Netty 重连实现
微服务篇
微服务
前后端分离是如
微服务哪些框架
你怎么理解 RPC 框架说说 RPC 的实现原理说说 Dubbo 的实现原理你怎么理解 RESTful
说说如何设计一个良好的 API
如何理解 RESTful API 的幂等性如何保证接口的幂等性
说说 CAP 定理、 BASE 理论怎么考虑数据一致性问题
说说最终一致性的实现方案你怎么看待微服务
微服务与 SOA 的区别如何拆分服务
微服务如何进行数据库管理
如何应对微服务的链式调用异常对于快速追踪与定位问题
微服务的安全分布式
谈谈业务中使用分布式的场景
Session 分布式方案分布式锁的场景
分布式锁的实现方案分布式事务
集群与负载均衡的算法与实现
说说分库与分表设计
分库与分表带来的分布式困境与应对之策
安全&性能
安全问题
安全要素与 STRIDE 威胁防范常见的 Web 攻击
服务端通信安全攻防HTTPS 原理剖析HTTPS 降级攻击
授权与认证
基于角色的访问控制基于数据的访问控制
性能优化
性能指标有哪些如
何发现性能瓶颈
性能调优的常见手段
说说你在项目中如何进行性能调优
工程篇
需求分析
你如何对需求原型进行理解和拆分
说说你对功能性需求的理解
说说你对非功能性需求的理解
你针对产品提出哪些交互和改进意见
你如何理解用户痛点
设计能力
说说你在项目中使用过的 UML 图你如何考虑组件化
你如何考虑服务化你如何进行领域建模你如何划分领域边界
说说你项目中的
说说概要设计
设计模式
你项目中有使用哪些设计模式
说说常用开源框架中设计模式使用分析说说你对设计原则的理解
23种设计模式的设计理念
设计模式之间的异同,例如策略模式与状态模式的区别
设计模式之间的结合,例如策略模式+简单工厂模式的实践
设计模式的性能,例如单例模式哪种性能更好。
业务工程
你系统中的前后端分离是如何做的
说说你的开发流程
你和团队是如何沟通的
你如何进行代码评审
说说你对技术与业务的理解
说说你在项目中经常遇到的 Exception
说说你在项目中遇到感觉最难Bug,怎么解决的说说你在项目中遇到印象最深困难,怎么解决的你觉得你们项目还有哪些不足的地方
你是否遇到过 CPU 100% ,如何排查与解决你是否遇到过 内存 OOM ,如何排查与解决说说你对敏捷开发的实践
说说你对开发运维的实践
介绍下工作中的一个对自己最有价值的项目,以及在这个过程中的角色软实力
说说你的亮点
说说你最近在看什么书
说说你觉得最有意义的技术书籍工作之余做什么事情
基础篇
基本功
说说个人发展方向方面的思考
说说你认为的服务端开发工程师应该具备哪些能力说说你认为的架构师是什么样的,架构师主要做什么说说你所理解的技术专家
面向对象特征
封装,继承,多态和抽象
1. 封装
封装给对象提供了隐藏内部特性和行为的能力。对象提供一些能被其他对象访问的方法
来改
变它内部的数据。在 Java 当中,有 3 种修饰符: public, private 和 protected。每一种修饰符
给其他的位于同一个包或者不同包下面对象赋予了不同的访问权限。下面列出了使用封装的一些好处:
通过隐藏对象的属性来保护对象内部的状态。
提高了代码的可用性和可维护性,因为对象的行为可以被单独的改变或者是扩展。禁止对象之间的不良交互提高模块化
2. 继承
继承给对象提供了从基类获取字段和方法的能力。继承提供了代码的重用行,也可以在
不修改类的情况下给现存的类添加新特性。
3. 多态
多态是编程语言给不同的底层数据类型做相同的接口展示的一种能力。一个多态类型上
的操作可以应用到其他类型的值上面。
4. 抽象
抽象是把想法从具体的实例中分离出来的步骤,因此,要根据他们的功能而不是实现细
节来创建类。 Java 支持创建只暴漏接口而不包含方法实现的抽象的类。这种抽象技术的主要目的是把类的行为和实现细节分离开
final, finally, finalize 的区别
1. final
修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作
为父类被继承。因此一个类不能既被声明为 abstract的,又被声明为final的。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时
给定初值,而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使
用,不能重载。
2. finally
在异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的
catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。
3. finalize
方法名。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象
调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的。
int 和 Integer 有什么区别
int 是基本数据类型
Integer是其包装类,注意是一个类。为什么要提供包装类呢
一是为了在各种类型间转化,通过各种方法的调用。否则 你无法直接通过变量转化。比如,现在int要转为String
1 int a=0;
2 String result=ng(a);
3 1
4 2
在java中包装类,比较多的用途是用在于各种数据类型的转化中。我写几个demo
//通过包装类来实现转化的
1 int num=f("12");
2 int num2=nt("12");
3 double num3=f("12.2");
4 double num4=ouble("12.2");
5
//其他的类似。通过基本数据类型的包装来的valueOf和parseXX来实现String转为XX
6
String a=f("1234");//这里括号中几乎可以是任何类型
7 String b=f(true);
8
String c=new Integer(12).toString();//通过包装类的toString()也可以
9 String d=new Double(2.3).toString();
10 1
11 2
12 3
13 4
14 5
15 6
16 7
17 8
18 9
再举例下。比如我现在要用泛型
1 List
2
1
这里<>需要类。如果你用int。它会报错的。
重载和重写的区别
override(重写)
1. 方法名、参数、返回值相同。
2. 子类方法不能缩小父类方法的访问权限。
3. 子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。
4. 存在于父类和子类之间。
5. 方法被定义为final不能被重写。
overload(重载)
1. 参数类型、个数、顺序至少有一个不相同。
2. 不能重载只有返回值不同的方法名。
3. 存在于父类和子类、同类中。
区别点
英文
定义
权限
范围
重载
Overloading
方法名称相同,参数的类型或个数不同
对权限没要求
发生在一个类中
Overiding
重写(覆写)
方法名称、参数类型、返回值类型全部相同
被重写的方法不能拥有更严格的权限
发生在继承类中
抽象类和接口有什么区别
接口是公开的,里面不能有私有的方法或变量,是用于让别人使用的,而抽象类是可以有私
有方法或私有变量的,
另外,实现接口的一定要实现接口里定义的所有方法,而实现抽象类可以有选择地重写需要
用到的方法,一般的应用里,最顶级的是接口,然后是抽象类实现接口,最后才到具体类实
现。
还有,接口可以实现多重继承,而一个类只能继承一个超类,但可以通过继承多个接口实现
多重继承,接口还有标识(里面没有任何方法,如Remote接口)和数据共享(里面的变量
全是常量)的作用。
说说反射的用途及实现
Java反射机制主要提供了以下功能:在运行时构造一个类的对象;判断一个类所具有的成
员变量和方法;调用一个对象的方法;生成动态代理。反射最大的应用就是框架
Java反射的主要功能:
确定一个对象的类
取出类的modifiers,数据成员,方法,构造器,和超类.
找出某个接口里定义的常量和方法说明.
创建一个类实例,这个实例在运行时刻才有名字(运行时间才生成的对象).
取得和设定对象数据成员的值,如果数据成员名是运行时刻确定的也能做到.
在运行时刻调用动态对象的方法.
创建数组,数组大小和类型在运行时刻才确定,也能更改数组成员的值.
反射的应用很多,很多框架都有用到
spring 的 ioc/di 也 是 反 射 …
javaBean和jsp之间调用也是反射…
struts的 FormBean 和页面之间…也是通过反射调用…
JDBC 的 classForName()也是反射…
hibernate的 find(Class clazz) 也是反射…
反射还有一个不得不说的问题,就是性能问题,大量使用反射系统性能大打折扣。怎么使用
使你的系统达到最优就看你系统架构和综合使用问题啦,这里就不多说了。
来源:/blog/1423512
说说自定义注解的场景及实现
(此题自由发挥,就看你对注解的理解了!==)登陆、权限拦截、日志处理,以及各种Java
框架,如Spring,Hibernate,JUnit 提到注解就不能不说反射,Java自定义注解是通过运行时靠反射获取注解。实际开发中,例如我们要获取某个方法的调用日志,可以通过
AOP(动态代理机制)给方法添加切面,通过反射来获取方法包含的注解,如果包含日志
注解,就进行日志记录。
HTTP 请求的 GET 与 POST 方式的区别
GET方法会把名值对追加在请求的URL后面。因为URL对字符数目有限制,进而限制了用在
客户端请求的参数值的数目。并且请求中的参数值是可见的,因此,敏感信息不能用这种方
式传递。
POST方法通过把请求参数值放在请求体中来克服GET方法的限制,因此,可以发送的参数
的数目是没有限制的。最后,通过POST请求传递的敏感信息对外部客户端是不可见的。
参考:/wangli-66/p/
session 与 cookie 区别
cookie 是 Web 服务器发送给浏览器的一块信息。浏览器会在本地文件中给每一个 Web 服务
器存储 cookie。以后浏览器在给特定的 Web 服务器发请求的时候,同时会发送所有为该服务器存储的 cookie。下面列出了 session 和 cookie 的区别:
无论客户端浏览器的做设置,session都应该能正常工作。客户端可以选择禁用
cookie,
但是, session 仍然是能够工作的,因为客户端无法禁用服务端的 session。
JDBC 流程
1、 加载JDBC驱动程序:
在连接数据库之前,首先要加载想要连接的数据库的驱动到JVM(Java虚拟机),
这通过类的静态方法forName(String className)实现。
例如:
1 try{
2
//加载MySql的驱动类
3 e("") ;
4 }catch(ClassNotFoundException e){
5
n("找不到驱动程序类 ,加载驱动失败!");
6 tackTrace() ;
7 }
8 1
9 2
10 3
11 4
12 5
13 6
14 7
成功加载后,会将Driver类的实例注册到DriverManager类中。
2、 提供JDBC连接的URL
连接URL定义了连接数据库时的协议、子协议、数据源标识。书写形式:协议:子协议:数据源标识
协议:在JDBC中总是以jdbc开始 子协议:是桥连接的驱动程序或是数据库管理系统名称。数据源标识:标记找到数据库来源的地址与连接端口。
例如:
jdbc:mysql://localhost:3306/test?
useUnicode=true&characterEncoding=gbk;useUnicode=true;(MySql的连接URL)
表示使用Unicode字符集。如果characterEncoding设置为 gb2312或GBK,本参数必须设置
为true 。characterEncoding=gbk:字符编码方式。
3、创建数据库的连接
要连接数据库,需要向Manager请求并获得Connection对象, 该对象就代表一个数据库的连接。
使用DriverManager的getConnectin(String url , String username , String password )方法传入指定的欲连接的数据库的路径、数据库的用户名和 密码来获得。
例如: //连接MySql数据库,用户名和密码都是root
1 String url = "jdbc:mysql://localhost:3306/test" ;
2 String username = "root" ;
3 String password = "root" ;
4 try{
5 Connection con = nection(url , username , password
) ;
6 }catch(SQLException se){
7
n("数据库连接失败!");
8 tackTrace() ;
9 }
10 1
11 2
12 3
13 4
14 5
15 6
16 7
17 8
18 9
4、 创建一个Statement
•
要执行SQL语句,必须获得ent实例,Statement实例分为以下3 种类型:
1、执行静态SQL语句。通常通过Statement实例实现。
2、执行动态SQL语句。通常通过PreparedStatement实例实现。
3、执行数据库存储过程。通常通过CallableStatement实例实现。具体的实现方式:
Statement stmt = Statement() ; PreparedStatement pstmt =
eStatement(sql) ; CallableStatement cstmt = eCall("{CALL
demoSp(? , ?)}") ;
5、执行SQL语句
Statement接口提供了三种执行SQL语句的方法:executeQuery 、executeUpdate 和
execute
1、ResultSet executeQuery(String sqlString):执行查询数据库的SQL语句 ,返回一个结果集(ResultSet)对象。
2、int executeUpdate(String sqlString):用于执行INSERT、UPDATE或 DELETE语句以及SQL DDL语句,如:CREATE TABLE和DROP TABLE等
3、execute(sqlString):用于执行返回多个结果集、多个更新计数或二者组合的 语句。 具体实现的代码:
ResultSet rs = eQuery(“SELECT * FROM …”) ; int rows =
eUpdate(“INSERT INTO …”) ; boolean flag = e(String sql) ;
6、处理结果
两种情况:
1、执行更新返回的是本次操作影响到的记录数。
2、执行查询返回的结果是一个ResultSet对象。
•
ResultSet包含符合SQL语句中条件的所有行,并且它通过一套get方法提供了对这些 行中数据的访问。
•
使用结果集(ResultSet)对象的访问方法获取数据:
while(()){
String name = ing(“name”) ;
String pass = ing(1) ; // 此方法比较高效
}
(列是从左到右编号的,并且从列1开始)
7、关闭JDBC对象
操作完成以后要把所有使用的JDBC对象全都关闭,以释放JDBC资源,关闭顺序和声 明顺序相反:
1、关闭记录集
2、关闭声明
3、关闭连接对象
1
if(rs != null){ // 关闭记录集
2
3
4
5
6
7
}
8
if(stmt != null){ // 关闭声明
}
try{
() ;
}catch(SQLException
e){ tackTrace() ;
9
10
11
12
13
14 }
try{
() ;
}catch(SQLException
e){ tackTrace() ;
}
15 if(conn != null){ // 关闭连接对象
16
17
18
19
20
21 }
22 1
23 2
24 3
25 4
26 5
27 6
28 7
29 8
30 9
31 10
32 11
33 12
34 13
35 14
36 15
37 16
38 17
39 18
40 19
41 20
42 21
try{
() ;
}catch(SQLException e){
tackTrace() ;
}
MVC 设计思想
MVC就是M:Model 模型V:View 视图
C:Controller 控制器
模型就是封装业务逻辑和数据的一个一个的模块,控制器就是调用这些模块的(java中通常是
用Servlet来实现,框架的话很多是用Struts2来实现这一层),视图就主要是你看到的,比如JSP
等.
当用户发出请求的时候,控制器根据请求来选择要处理的业务逻辑和要选择的数据,再返回去
把结果输出到视图层,这里可能是进行重定向或转发等.
equals 与 == 的区别
值类型(int,char,long,boolean等)都是用==判断相等性。对象引用的话,判断引用所指的对 象是否是同一个。equals是Object的成员函数,有些类会覆盖(override)这个方法,用于判
断对象的等价性。例如String类,两个引用所指向的String都是"abc",但可能出现他们实际对应的对象并不是同一个(和jvm实现方式有关),因此用判断他们可能不相等,但用equals判断一定是相等的。
集合
List 和 Set 区别
List,Set都是继承自Collection接口
List特点:元素有放入顺序,元素可重复
Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉
(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其
位置其实是固定的,加入Set 的Object必须定义equals()方法 ,另外list支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得
想要的值。)
Set和List对比:
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引
起其他元素位置改变。
List 和 Map 区别
List是对象集合,允许对象重复。
Map是键值对的集合,不允许key重复。
Arraylist 与 LinkedList 区别
Arraylist:
优点:ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查
询操作效率会比较高(在内存里是连着放的)。
缺点:因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低。LinkedList:
优点:LinkedList基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等
一个连续的地址,对于新增和删除操作add和remove,LinedList比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景
缺点:因为LinkedList要移动指针,所以查询操作性能比较低。适用场景分析:
当需要对数据进行对此访问的情况下选用ArrayList,当需要对数据进行多次增加删除修改时
采用LinkedList。
ArrayList 与 Vector 区别
1
public ArrayList(int initialCapacity)//构造一个具有指定初始容量的空列表。
2
public ArrayList()//构造一个初始容量为10的空列表。
3
public ArrayList(Collection extends E> c)//构造一个包含指定
collection 的元素的列表
4 1
5 2
6 3
Vector有四个构造方法:
1
public Vector()//使用指定的初始容量和等于零的容量增量构造一个空向量。
2
public Vector(int initialCapacity)//构造一个空向量,使其内部数据数组的大
小,其标准容量增量为零。
3
public Vector(Collection extends E> c)//构造一个包含指定 collection 中
的元素的向量
4
public Vector(int initialCapacity,int capacityIncrement)//使用指定的初
始容量和容量增量构造一个空的向量
5 1
6
2
7 3
8
4
ArrayList和Vector都是用数组实现的,主要有这么三个区别:
1. Vector是多线程安全的,线程安全就是说多线程访问同一代码,不会产生不确定的结
果。而ArrayList不是,这个可以从源码中看出,Vector类中的方法很多有synchronized
进行修饰,这样就导致了Vector在效率上无法与ArrayList相比;
2. 两个都是采用的线性连续空间存储元素,但是当空间不足的时候,两个类的增加方式是
不同。
3. Vector可以设置增长因子,而ArrayList不可以。
4. Vector是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用。适用场景分析:
1. Vector是线程同步的,所以它也是线程安全的,而ArrayList是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用ArrayList效率比较高。
2. 如果集合中的元素的数目大于目前集合数组的长度时,在集合中使用数据量比较大的数
据,用Vector有一定的优势。
HashMap 和 Hashtable 的区别
1.
hashMap去掉了HashTable 的contains方法,但是加上了containsValue()和
containsKey()方法。
2.
hashTable同步的,而HashMap是非同步的,效率上逼hashTable要高。p允许空键值,而hashTable不允许。
注意:
TreeMap:非线程安全基于红黑树实现。TreeMap没有调优选项,因为该树总处于平衡状态。
Treemap:适用于按自然顺序或自定义顺序遍历键(key)。
参考:/qq_22118507/article/details/51576319
HashSet 和 HashMap 区别
set是线性结构,set中的值不能重复,hashset是set的hash实现,hashset中值不能重复是
用hashmap的key来实现的。
map是键值对映射,可以空键空值。HashMap是Map接口的hash实现,key的唯一性是通过key值hash值的唯一来确定,value值是则是链表结构。
他们的共同点都是hash算法实现的唯一性,他们都不能持有基本类型,只能持有对象
HashMap 和 ConcurrentHashMap 的区别
ConcurrentHashMap是线程安全的HashMap的实现。
(1)
ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上
都用lock锁进行保护,相对于HashTable的syn关键字锁的粒度更精细了一些,并发性能更
好,而HashMap没有锁机制,不是线程安全的。
(2)
HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。
HashMap 的工作原理及代码实现
参考:/2015/07/01/Java集合学习1:HashMap的实现原理/
ConcurrentHashMap 的工作原理及代码实现
HashTable里使用的是synchronized关键字,这其实是对对象加锁,锁住的都是对象整体,
当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时
间。
ConcurrentHashMap算是对上述问题的优化,其构造函数如下,默认传入的是16,0.75,
16。
1
public ConcurrentHashMap(int paramInt1, float paramFloat, int
paramInt2) {
2
//…
3
int i = 0;
4
int j = 1;
5
while (j < paramInt2) {
6
7
8
}
9
tShift = (32 ‐ i);
10
tMask = (j ‐ 1);
11
ts = ay(j);
12
//…
13
int k = paramInt1 / j;
++i;
j <<= 1;
14 if (k * j < paramInt1)
15 ++k;
16 int l = 1;
17 while (l < k)
18 l <<= 1;
19
20 for (int i1 = 0; i1 < ; ++i1)
21 ts[i1] = new Segment(l, paramFloat);
22 }
23 public V put(K paramK, V paramV) {
24 if (paramV == null)
25 throw new NullPointerException();
26 int i = hash(de()); //这里的hash函数和HashMap中的不一样
27 return ts[(i >>> tShift &
tMask)].put(paramK, i, paramV, false);
28 }
29 1
30 2
31 3
32 4
33 5
34 6
35 7
36 8
37 9
38 10
39 11
40 12
41 13
42 14
43 15
44 16
45 17
46 18
47 19
48 20
49 21
50 22
51 23
52 24
53 25
54 26
55
27
56
28
ConcurrentHashMap引入了分割(Segment),上面代码中的最后一行其实就可以理解为把一
个大的Map拆分成N个小的HashTable,在put方法中,会根据hash(de())来 决定具体存放进哪个Segment,如果查看Segment的put操作,我们会发现内部使用的同步 机制是基于lock操作的,这样就可以对Map的一部分(Segment)进行上锁,这样影响的只 是将要放入同一个Segment的元素的put操作,保证同步的时候,锁住的不是整个Map(HashTable就是这么做的),相对于HashTable提高了多线程环境下的性能,因此HashTable已经被淘汰了。
线程
创建线程的方式及实现
Java中创建线程主要有三种方式:
一、继承Thread类创建线程类
(1)
定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)
创建Thread子类的实例,即创建了线程对象。
(3)
调用线程对象的start()方法来启动该线程。
1
package ;
2
3
public class FirstThreadTest extends Thread{
4
int i = 0;
5
6
7
8
9
10
11
12
13
14
15
}
public static void main(String[] args)
{
for(int i = 0;i< 100;i++)
{
}
//重写run方法,run方法的方法体就是现场执行体public void run()
{
for(;i<100;i++){ n(getName()+" "+i);
16
n(tThread().getName()+" :
"+i);
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
if(i==20)
{
new FirstThreadTest().start();
new FirstThreadTest().start();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
上述代码中tThread()方法返回当前正在执行的线程对象。getName()方法返
回调用该方法的线程的名字。
二、通过Runnable接口创建线程类
(1)
定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)
创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)
调用线程对象的start()方法来启动该线程。
1 package ;
2
3 public class RunnableThreadTest implements Runnable
4 {
5
6
7
8
9
10
11
"+i);
12
13
14
15
16
17
18
"+i);
19
20
21
22
23
25
26
27 }
28
29 1
30 2
31 3
32 4
33 5
34 6
}
}
}
if(i==20)
{
RunnableThreadTest rtt = new RunnableThreadTest();
new Thread(rtt,"新线程1").start();
new Thread(rtt,"新线程2").start(); 24}
public static void main(String[] args)
{
for(int i = 0;i < 100;i++)
{
n(tThread().getName()+"
}
private int i;
public void run()
{
for(i = 0;i <100;i++)
{
n(tThread().getName()+"
35 7
36 8
37 9
38 10
39 11
40 12
41 13
42 14
43 15
44 16
45 17
46 18
47 19
48 20
49 21
50 22
51 23
52 24
53 25
54 26
55 27
56 28
三、通过Callable和Future创建线程
(1)
创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)
创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)
使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
1 package ;
2
3 import le;
4 import ionException;
5 import Task;
6
7 public class CallableThreadTest implements Callable
8 {
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
"+i);
39
40
41
42 }
43 1
44 2
45 3
46 4
47 5
}
}
return i;
}
@Override
public Integer call() throws Exception
{
int i = 0;
for(;i<100;i++)
{
n(tThread().getName()+"
{
}
tackTrace();
{
tackTrace();
} catch (ExecutionException e)
}
try
{
n("子线程的返回值:"+());
} catch (InterruptedException e)
public static void main(String[] args)
{
CallableThreadTest ctt = new CallableThreadTest();
FutureTask
for(int i = 0;i < 100;i++)
{
n(tThread().getName()+" 的循
环变量i的值"+i);
if(i==20)
{
}
new Thread(ft,"有返回值的线程").start();
48 6
49 7
50 8
51 9
52 10
53 11
54 12
55 13
56 14
57 15
58 16
59 17
60 18
61 19
62 20
63 21
64 22
65 23
66 24
67 25
68 26
69 27
70 28
71 29
72 30
73 31
74 32
75 33
76 34
77 35
78 36
79 37
80 38
81 39
82 40
83 41
84 42
创建线程的三种方式的对比
采用实现Runnable、Callable接口的方式创见多线程时,优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同
一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面
向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用tThread()方法。使用继承Thread类的方式创建多线程时优势是:
编写简单,如果需要访问当前线程,则无需使用tThread()方法,直接使用this
即可获得当前线程。
劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。
sleep() 、join()、yield()有什么区别
1、sleep()方法
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度
程序精度和准确性的影响。 让其他线程有机会继续执行,但它并不释放对象锁。也就是如
果有Synchronized同步块,其他线程仍然不能访问共享数据。注意该方法要捕获异常
比如有两个线程同时执行(没有Synchronized),一个线程优先级为MAX_PRIORITY,另一
个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完成后,低优先级
的线程才能执行;但当高优先级的线程sleep(5000)后,低优先级就有机会执行了。
总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的
线程有执行的机会。
2、yield()方法
yield()方法和sleep()方法类似,也不会释放“锁标志”,区别在于,它没有参数,即yield()方
法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态
后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也
和sleep()方法不同。
3、join()方法
Thread的非静态方法join()让一个线程B“加入”到另外一个线程A的尾部。在A执行完毕之前,
B不能工作。
1
Thread t = new MyThread();
();
();
2
3
4 1
5 2
6 3
保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有
存活,则当前线程不需要停止。
说说 CountDownLatch 原理
参考:
分析CountDownLatch的实现原理
什么时候使用CountDownLatch
Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
说说 CyclicBarrier 原理
参考:
JUC回顾之-CyclicBarrier底层实现和原理
说说 Semaphore 原理
JAVA多线程–信号量(Semaphore)
JUC回顾之-Semaphore底层实现和原理
说说 Exchanger 原理
ger应用范例与原理浅析
说说 CountDownLatch 与 CyclicBarrier 区别
CountDownLatch
减计数方式
计算为0时释放所线有程
CyclicBarrier
加计数方式
定值 时释放所有等待线程
计数为0时,无法重置
调用countDown()方法计数减一,调用await()
方法只进行阻塞,对计数没任何影响
不可重复利用
计数达到指定值时,计数置为0重新开始
调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞
可重复利用
尽量把CyclicBarrier和CountDownLatch的区别说通俗点
ThreadLocal 原理分析
Java并发编程:深入剖析ThreadLocal
讲讲线程池的实现原理
主要是ThreadPoolExecutor的实现原理
Java并发编程:线程池的使用
线程池的几种方式
newFixedThreadPool(int nThreads)
创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数
量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的
线程
newCachedThreadPool()
创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当
需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制
newSingleThreadExecutor()
这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会
创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行
newScheduledThreadPool(int corePoolSize)
创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。举个栗子
1 private static final Executor exec=edThreadPool(50);
2
3 Runnable runnable=new
4
Runnable(){ public void run(){
5
6
7 }
}
...
8 e(runnable);
9
10 Callable
11
12
14 };
15
16 Future future=(callable);
17 (); // 等待计算完成后,获取结果
18 (); // 如果任务已完成,则返回 true
19 elled(); // 如果在任务正常完成前将其取消,则返回 true
20 (true); // 试图取消对此任务的执行,true中断运行的任务,false
允许正在运行的任务运行完成
21 1
22 2
23 3
24 4
25 5
26 6
27 7
28 8
29 9
30 10
31 11
32 12
33 13
34 14
35 15
36 16
37 17
38 18
39 19
40 20
public Object call() throws Exception {
return null; 13}
参考:
创建线程池的几种方式
线程的生命周期
新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态
(1)生命周期的五种状态新建(new Thread)
当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
例如:Thread t1=new Thread();
就绪(runnable)
线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队
等候得到CPU资源。例如:();
运行(running)
线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有
优先级更高的线程进入,线程将一直运行到结束。
死亡(dead)
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态
等待执行。
自然终止:正常运行run()方法后终止
异常终止:调用**stop()**方法让一个线程终止运行堵塞(blocked)
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。
正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
正在等待:调用wait()方法。(调用motify()方法回到就绪状态)
被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)
参考:
线程的生命周期
锁机制
说说线程安全问题
线程安全是指要控程制对某个资源的有序访问或修改,而在这些线程之间没有产生冲突。
在Java里,线程安全一般体现在两个方面:
1、多个thread对同一个java实例的访问(read和modify)不会相互干扰,它主要体现在关
键字synchronized。如ArrayList和Vector,HashMap和Hashtable(后者每个方法前都有synchronized关键字)。如果你在interator一个List对象时,其它线程remove一个element,
问题就出现了。
2、每个线程都有自己的字段,而不会在多个线程之间共享。它主要体现在Local类,而没有Java关键字支持,如像static、transient那样。
volatile 实现原理
聊聊并发(一)——深入分析Volatile的实现原理
悲观锁 乐观锁
乐观锁 悲观锁
是一种思想。可以用在很多方面。比如数据库方面。
悲观锁就是for update(锁定查询的行)
乐观锁就是 version字段(比较跟上一次的版本号,如果一样则更新,如果失败则要重复读-
比较-写的操作。)
JDK方面:
悲观锁就是sync
乐观锁就是原子类(内部使用CAS实现)
本质来说,就是悲观锁认为总会有人抢我的。乐观锁就认为,基本没人抢。
CAS 乐观锁
乐观锁是一种思想,即认为读多写少,遇到并发写的可能性比较低,所以采取在写时先读出
当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重
复读-比较-写的操作。
CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。CAS顶多算是乐观锁写那一步操作的一种实现方式罢了,不用CAS自己加锁也是可以的。
ABA 问题
ABA:如果另一个线程修改V值假设原来是A,先修改成B,再修改回成A,当前线程的CAS
操作无法分辨当前V值是否发生过变化。
版权声明:本文标题:03-图灵面试宝典(进一般互联网公司必看)——33页 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/free/1705482610h486619.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论