并发模型
悲观锁
悲观锁假设最坏的情况(如果你不锁门,那么捣蛋鬼就会闯入并搞得一团糟),并且只有在确保其他线程不会干扰(通过获取正确的锁)的情况下才能执行下去。
常见实现如独占锁等。
安全性更高,但在中低并发程度下的效率更低。
乐观锁
乐观锁借助冲突检查机制来判断在更新过程中是否存在其他线程的干扰,如果存在,这个操作将失败,并且可以重试(也可以不重试)。
常见实现如CAS等。
部分乐观锁削弱了一致性,但中低并发程度下的效率大大提高。
并发编程
Java中如何创建一个线程?
从面相接口的角度上讲,实际上只有一种方法实现Runable接口;但Thread类为线程操作提供了更多的支持,所以通常做法是实现Runable接口,实例化并传入Thread类的构造函数。
继承Thread,覆写run方法
实现Runable接口,覆写run方法
Vector(HashTable)如何实现线程安全?
通过synchronized关键字修饰每个方法。
依据synchronized关键字引申出以下问题。
synchronized修饰方法和修饰代码块时有何不同?
持有锁的对象不同:
修饰方法时:this引用的当前实例持有锁
修饰代码块时:要指定一个对象,该对象持有锁
从而导致二者的意义不同:
同步代码块在锁定的范围上可能比同步方法要小,一般来说锁的范围大小和性能是成反比的。
修饰代码块可以选择对哪个对象加锁,但是修饰方法只能给this对象加锁。
ConcurrentHashMap的如何实现线程安全?
ConcurrentHashMap的线程安全实现与HashTable不同:
可以将ConcurrentHashMap理解为,不直接持有一个HashMao,而是用多个Segment代替了一个HashMap。但实际实现的Map部分和HashMap的原理基本相同,对脚标取模来确定table[i]所属段,从而对不同的段获取不同的段锁。
每个Segment持有一个锁,通过分段加锁的方式,既实现了线程安全,又兼顾了性能。
缓存的面试问题
缓存的面试问题有很多,我个人总结大概有下面几点,你可以从这些方面作为面试参考。
1.为什么选用缓存?这个可以高性能高并发的方面回答面试官,由于缓存数据存储在内存中,其相对IO操作要快的多,而且内存天然就支撑高并发。
2.使用缓存做了什么?这个根据自己的项目的业务场景去回答,比如应对高并发访问热点数据,减轻数据库的压力;比如充当分布式锁,保证线程安全;比如充当消息队列;比如充当计数器;比如解决分布式应用中的共享Session问题。
3.缓存为什么快?可以从下面几点去回答:首先其数据存储在内存中,其次采用单线程避免上下文切换,还有使用多路I/O复用模型,非阻塞IO,以及一些底层的模型设计。
4.缓存的数据备份问题?这里可以有以下的相关问题,比如备份方式:Redis默认采用Rdb方式备份,符合设定的条件时redis会将内存中的所有数据自动生成一份副本保存到硬盘上,可以通过配置文件开启AOF备份。比如两种备份的区别:AOF持久化保存的数据更完整一些,但是同时会带来性能上的损耗。
5.缓存穿透问题?访问一个不存在的 key,缓存不起作用,请求会落到数据库上,如果请求量很大,数据库有可能会挂掉。解决方案:缓存空值,布隆过滤器,互斥锁排队。
6.缓存雪崩问题?缓存在同一时间内大量键过期(失效),接着来的一大波请求瞬间都落在了数据库中导致连接异常。解决方案:互斥锁排队,分散失效时间。