本周问题:
1. 如何实现一个最简单的RPC和MVC框架?
2. synchronized关键字、ReentrantLock、ReentrantReadWriteLock、StampedLock如何选择?
1. 如何实现一个最简单的RPC和MVC框架?
RPC:远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的思想。
客户端通过代理将要调用的远程方法通过socket发送到服务端
服务端通过反射调用本地方法通过socket将结果返回给客户端
MVC:
1. 写一个DispatcherServlet继承HttpServlet重写init方法
声明注解Controller,Service,RequestMapping, Autowired
1) 定义一个List<String>(扫描添加了注解的类并添加到list里面)
2) 定义一个Map容器(存放实例化对象)遍历list,实例化扫描到的对象存放入map(key为className,value为实例化对象)
3) 依赖注入:遍历Map容器,设置属性依赖注入
4) 定义一个Map(存放url和 方法的映射关系)遍历Map容器将存放url和具体method
2. 重写doGet(),doPost()方法根据请求url从4)中获取method通过反射调用方法
如下:
2. Synchronized关键字、ReentrantLock、ReentrantReadWriteLock、StampedLock如何选择?
Synchronized:可重入锁,基于jvm实现的,分为:偏向锁(对象头保存持有锁的线程ID),轻量级锁(CAS实现), 重量级锁(基于monitor实现)
ReentrantLock:是Lock接口的实现类,通过构造一个基于阻塞的CLH队列容纳所有的阻塞线程,而对该队列的操作均通过Lock-Free(CAS)操作,但对已经获得锁的线程而言,ReentrantLock实现了偏向锁的功能。使用起来略显复杂,但是可以通过condition的设置实现一些逻辑的控制,性能中等。
ReentrantLock和Synchronized不同之处在于:
- 支持超时加锁,加锁可以响应中断,支持阻塞线程排队, 在唤醒线程时,相较于synchronized精细一些,是一个接一个唤醒,不像object.notify()随机唤醒阻塞线程和object.notifyAll()唤醒所有的线程
- 有公平锁和非公平锁之分,非公平锁的吞吐量高一点,但是有线程饥饿风险?,公平锁不会存在线程饥饿风险,但是吞吐量会低一点
- 性能和建议:性能中等,建议需要手动操作线程时使用。
ReentrantReadWriteLock:适用于通过读写分离解决内存计算读多写少的场景,特征是当没有写操作时,读锁是共享的,但是一旦出现写操作,则写锁独占并且阻塞读写操作,可能出现饥饿现象,适合读多写少的情况。可重入的读写锁对Lock的扩展,引入了read和write阻塞和并发机制,相对于ReentrantLock可以实现更复杂的锁机制,适合比较复杂任务的读写并发的情况
StampedLock:内部实现是基于CLH锁的,一种自旋锁,保证没有饥饿且FIFO(先进先出)。 控制锁有三种模式(排它写,悲观读,乐观读),一个StampedLock状态是由版本和模式两个部分组成,锁获取方法返回一个数字作为票据stamp( stamp是一个类似于时间戳的值,可以用来判断读的时候,加锁的内存区域是否被改写过,避免脏读。),它用相应的锁状态表示并发控制访问。在Lock的基础上,实现了可以满足乐观锁和悲观锁等一些读线程越来越多的业务场景,对吞吐量有很大的改进,适合轻量级任务的高并发情况
CLH锁原理:锁维护着一个等待线程队列,所有申请锁且失败的线程都记录在队列。一个节点代表一个线程,保存着一个标记位locked,用以判断当前线程是否已经释放锁。当一个线程试图获取锁时,从队列尾节点作为前序节点,循环判断所有的前序节点是否已经成功释放锁。