HashCode
下面这段话摘自Effective Java一书:
- 在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。
- 如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。
- 如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数。
两个对象的 hashCode()相同,则 equals()不一定为true。正常情况下,因为equals()方法比较的就是对象在内存中的值,如果值相同,那么Hashcode值也应该相同。但是如果不重写hashcode方法,就会出现不相等的情况。
Java中的基本类及其包装类
基础的数据类型有
int,short,long,char,boolean,float,double,byte,
对应的基本类型的包装类:
Integer,Short,Long,Character,Float,Double,Byte
抽象类\普通类\ 接口
抽象类 普通类
1)抽象类不能被实例化
2)抽象类可以有构造函数,被继承时子类必须继承父类一个构造方法,抽象方法不能被声明为静态
3)抽象方法只需声明,无需实现,可以允许普通方法有主体
4)含抽象方法的类必须声明为抽象类
5)抽象的子类必须实现抽象类中所有抽象方法,否则这个子类也是抽象类
接口 抽象类
1)抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象
2)抽象类要被子类继承,接口要被类实现
3)接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
4)接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量
5)抽象类中的抽象方法必须全部被子类实现,如果子类不能实现全部父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如果不能全部实现接口方法,那么该类也只能为抽象类
6)抽象方法只能申明,不能实现,接口是设计的结果,抽象类是重构的接果
7)抽象类里可以没有抽象的方法
8) 如果一个类里又抽象方法,那么这个类只能是抽象类
9)抽象方法要被实现,不能是静态的,也不能是私有的
10)接口可以继承接口,并可多继承接口,但类只能单根继承
BIO、NIO、AIO 有什么区别?
同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写)。
异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API)。
阻塞 : ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回)。
非阻塞 : 柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)。
Java对BIO、NIO、AIO的支持:
Java BIO (blocking I/O): 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
Java NIO (non-blocking I/O): 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
Java AIO(NIO.2) (Asynchronous I/O) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理,
BIO、NIO、AIO适用场景分析:
BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
List、Set、Map 之间的区别是什么?
- list:有序可以重复,可以插入多个null元素
- Set:无序不可以重复,只允许有一个null元素
- map:不是collection的子接口或者实现类,推行键值对,一个key一个value
HashMap 和 Hashtable、 有什么区别?
HashMap是我们非常常用的数据结构,由数组和链表组合构成的数据结构
1 HashMap由数据和链表组成
2 链表主要是解决哈希冲突而存在的 对于查找,添加等操作很快
3 通过key对象的equals方法逐一比对查找,HashMAp中的链表出现越少,性能才会越好
使用头插会改变链表的上的顺序,但是如果使用尾插,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了
1)HashMap和HashTable都实现了map的接口,主要区别在于线程安全性、同步以及速度,Hashtable 继承了 Dictionary类,而 HashMap 继承的是 AbstractMap 类
2)Hashmap是非同步的,而hashtable是同步的,意味着hashtable是线程安全的,多个线程可以共享一个hashtable
3)HashMap 中的 Iterator 迭代器是 fail-fast 的,而 Hashtable 的 Enumerator 不是 fail-fast 的
4)Hashtable在单线程的环境下要比hashmap要慢;如果不需要同步,在单线程环境下,使用hashmap性能比hashtable好
5)Hashmap不能保证随着时间的推移map中的元素次序是不变的
6)HashMap 的初始容量为:16,Hashtable 初始容量为:11,两者的负载因子默认都是:0.75
7)当现有容量大于总容量 * 负载因子时,HashMap 扩容规则为当前容量翻倍,Hashtable 扩容规则为当前容量翻倍 + 1。
8)Hashtable 是不允许键或值为 null 的,HashMap 的键值则都可以为 null。
Tips
**快速失败(fail—fast)**是java集合中的一种机制, 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
他的原理是啥?
迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。
集合在被遍历期间如果内容发生变化,就会改变modCount的值。
每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
Tip:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。
因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
说说他的场景?
java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)算是一种安全机制吧。
**安全失败(fail—safe)**大家也可以了解下,java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
HashMap默认初始化大小是多少?为啥是这么多?为啥大小都是2的幂?
我们在创建HashMap的时候,阿里巴巴规范插件会提醒我们最好赋初值,而且最好是2的幂。
这样是为了位运算的方便,位与运算比算数计算的效率高了很多,之所以选择16,是为了服务将Key映射到index的算法。
我前面说了所有的key我们都会拿到他的hash,但是我们怎么尽可能的得到一个均匀分布的hash呢?
是的我们通过Key的HashCode值去做位运算。
ConcurrentHashMap、Collections.synchronizedMap(Map)、Hashtable
Hashmap多线程环境下可以采用concurrent并发包下的concurrentHashMap
使用**Collections.synchronizedMap(Map)**创建线程安全的map集合;
Hashtable
ConcurrentHashMap
Collections.synchronizedMap(Map)保证的安全性都是通过很多的synchronized来实现的
同理、HashTable也是在操作的时候有很多synchronized,所以虽然保证了线程的安全性、却导致效率低下
ConcurrentHashMap
ConcurrentHashMap在jdk1.8之后采用CAS + synchronized 来保证并发安全性
跟HashMap很像,也把之前的HashEntry改成了Node,但是作用不变,把值和next采用了volatile去修饰,保证了可见性,并且也引入了红黑树,在链表大于一定值的时候会转换(默认是8)。
ConcurrentHashMap在进行put操作的还是比较复杂的,大致可以分为以下步骤:
根据 key 计算出 hashcode 。
判断是否需要进行初始化。
即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。
如果都不满足,则利用 synchronized 锁写入数据。
如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。
ConcurrentHashMap的get操作又是怎么样子的呢?
根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。
如果是红黑树那就按照树的方式获取值。
就不满足那就按照链表的方式遍历获取值。
ArrayList和linkList
ArrayList底层是数组
LinkList底层是链表
数组和链表。数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。
** runnable 和 callable 有什么区别?**
最大的不同点:实现Callable接口的任务线程能返回执行结果,而实现runable接口的任务线程不能返回结果
Callable接口的call()方法允许抛出异常,而Runable接口的run()方法的异常只能在内部消化,不能继续上抛.
线程池中 submit()和 execute()方法有什么区别?
execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执行成功,并且可以通过 Future 的 get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
1.接收的参数不一样
2.submit有返回值,而execute没有
3.submit方便Exception处理
Synchronized 和 Lock 有什么区别?
synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。在这里插入代码片
尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能!
*首先synchronized是java内置关键字,在jvm层面,lock是个java类
*synchronized无法判断是否获取锁的状态,lock可以判断是否获取到锁
*synchronized会自动释放锁,lock需在finally中手工释放锁,否则容易造成线程死锁
*用sychronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2等待。如果线程1阻塞,线程2会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了
*synchronized的锁可重入、不可中断、非公平,而Lock锁皆可实行
*Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题
Synchronized 和 ReentrantLock 区别是什么?
这两种方式最大区别就是对于Synchronized来说他是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而reemntrantLock它是jDK1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。
① 两者都是可重入锁
两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API
synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
③ ReenTrantLock 比 synchronized 增加了一些高级功能
相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)
ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。 ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。
synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。
如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。
④ 性能已不是选择标准
在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具体表示为:synchronized 关键字吞吐量随线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。JDK1.6 之后,synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的!JDK1.6之后,性能已经不是选择synchronized和ReenTrantLock的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作。
深拷贝和浅拷贝区别是什么?
浅克隆只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝
深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这就是深拷贝
TCP三次握手
TCP发起建立连接的一方不会一直等待对方的回复,如果超时,他再次发起这个请求直至放弃,然后释放资源。假设最后一次的请求送达到了对方B,而此时请求方A已经释放连接,而B是不知道的。本来这是一个早已失效的报文段。但B收到此失效的连接请求报文段后,就误认为A发出了一次新的连接请求。于是又向A发出确认报文段,同意建立连接。假定不采用三次握手,那么只要B发出确认,新的连接就建立了。 由于现在A已经释放连接,因此不会理会B的确认,也不会向B发送数据。 但B却以为新的运输连接已经建立了,并一直等待A发来数据。 B的许多资源就这样浪费了。采用三次握手的方法可以防止上述异常现象的发生。
说一下 Spring MVC 运行流程?
1)用户发送请求到DispatchServlet
2)DispatchServlet根据请求路径查询具体的Handler
3)HandlerMapping返回一个HandlerExcutionChain给DispatchServlet
4)DispatchServlet调用HandlerAdapter适配器
5)HandlerAdapter调用具体的Handler处理业务
6)Handler处理结束返回一个具体的ModelAndView给适配器
7)适配器将ModelAndView给DispatchServlet
8)DispatchServlet把视图名称给viewResolver视图解析器
9)viewREsource返回一个具体的视图给DispatchServlet
10)渲染视图
11)展示给用户
Spring Cloud 的核心组件有哪些?
服务发现—Netflix EureKa
客服端负载均衡—Netflix Ribbon
断路器—Netflix Hystrix
服务网关—Netflix Zuul
分布式配置—Spring Cloud Config
Mysql的锁
锁是在执行多线程时用于强行限制资源访问的同步机制,即用于在并发控制中保证对互斥要求的满足。在DBMS中,可以按照锁的粒度把数据库锁分为行级锁(INNODB引擎)、表级锁(MYISAM引擎)和页级锁(BDB引擎 )
行锁
锁定整个行数据,开销大,加锁慢,会出现死锁。锁定粒度小,发生锁冲突的概率低,并发度高。
表锁
锁定整个表数据,开销小,加锁快,不会出现死锁。锁定粒度大,发生锁冲突概率高,并发度低。
悲观锁
每次取数据时都认为别人会修改,所以每次取数据的时候都会上锁,这样别人想拿这个数据就会被阻塞,直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。适用于“写多读少”的场景。
乐观锁
每次取数据时都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。适用于“读多写少”的场景,这样可以提高吞吐量。数据库中的write_condition机制就是乐观锁。
共享锁
共享锁指的是对于多个不同的事务,对同一个资源共享同一个锁。相当于对于同一把门,它拥有多个钥匙一样。共享锁也属于悲观锁的一种,通过在执行语句后面加上lock in share mode就加上共享锁了。
MyISAM引擎和InnoDB引擎的区别
MyISAM:支持全文索引;不支持事务;它是表级锁;会保存表的具体行数.
InnoDB:5.6以后才有全文索引;支持事务;它是行级锁;不会保存表的具体行数.
一般:不用事务的时候,count计算多的时候适合myisam引擎。对可靠性要求高就是用innodby引擎。推荐用InnoDB引擎.
加了索引之后能够大幅度的提高查询速度,但是索引也不是越多越好,一方面它会占用存储空间,另一方面它会使得写操作变得很慢。通常我们对查询次数比较频繁,值比较多的列才建索引。
例如:select * from user where sex = “女”, 这个就不需要建立索引,因为性别一共就两个值,查询本身就是比较快的。
select * from user where user_id = 1995 ,这个就需要建立索引,因为user_id的值是非常多的。
B+Tree的特性
(1)由图能看出,单节点能存储更多数据,使得磁盘IO次数更少。
(2)叶子节点形成有序链表,便于执行范围操作。
(3)聚集索引中,叶子节点的data直接包含数据;非聚集索引中,叶子节点存储数据地址的指针。
接口的幂等性
最后总结
1.同步锁(单线程,集群可能会失效)
2.分布式锁如redis(实现复杂)
2.业务字段加唯一约束(简单)
3.令牌表+唯一约束(简单推荐)
4.mysql的insert ignore或者on duplicate key update(简单)
5.共享锁+普通索引(简单)
6.利用MQ或者Redis扩展(排队)
7.其他方案如多版本控制MVCC 乐观锁 悲观锁 状态机等。。。
对客户端请求排队或者单线程都可以处理幂等问题,需要根据具体业务选择合适的方案但必须前后端一起做,前端做了可以提升用户体验,后端则可以保证数据安全。