About 5 min
一、java中如何实现锁
保持某一段代码的原子性,synchronized是如何实现的。通过jol可以进行实验(观察代码在内存中如何执行的,maven坐标: jol-core)
原子性:上锁之后的代码不可被打断(持有同样锁的其他代码,打断指的是同时运行)
二、锁对比
悲观 | 乐观 | |
---|---|---|
描述 | 悲观的认为数据的竞争特别大,直接将数据锁起来 | 乐观的认为数据竞争小,不加锁 |
场景 | 并发大,临界区小 | 并发小,临界区大 |
例子 | 行锁,表锁, Synchronized等 | 版本号、CAS |
缺点 | 重量级、锁的开销比较大 | 并发高的乐观锁自旋会导致性能下降 |
- 自旋锁:从字面的意思看就是自我旋转,应用到乐观锁中比如带上版本号修改数据的时候,如果版本被其他线程修改了则重新获取新的版本号后再次修改,一直自旋到成功为止。
- 乐观锁:乐观的认为数据被修改的情况少,然后不加锁。在不加锁的情况下如果偶尔遇到了其他线程就可以用版本号+自旋锁去解决
- 悲观锁:悲观的认为数据被修改的情况多,直接将数据锁起来。并发高的时候必须等之前的线程处理完成之后才操作数据
- 互斥锁:线程在进入临界区的时候需要先获得锁,退出临界区需要释放锁。如果其他限制在使用锁则进入到阻塞状态直到被释放
三、整理锁
锁名称 | 描述 | 例子 |
---|---|---|
公平锁 | 按照申请锁的顺序获取锁 | |
非公平锁 | 获取锁的顺序不固定 | ReentrantLock、Synchronized |
可重入锁 | 在外层方法获取锁后在进入内层方法也自动获取锁 | ReentrantLock、Synchronized |
独享锁 | 只能被一个线程使用 | ReentrantLock、Synchronized |
共享锁 | 可以被多个线程使用 | ReadWriteLock(读是共享锁,写诗独享锁) |
互斥锁 | 访问前加锁访问后解锁(多人抢一个马桶) | ReentrantLock |
读写锁 | 是共享锁也是互斥锁,读的时候共享写的时候互斥 | ReadWriteLock |
分段锁 | 锁的设计,为了细化锁粒度。 | ConcurrentHashMap |
偏向锁 | 代码一直被一个线程使用则线程会自动获取锁 | Synchronized的前版本 |
轻量级锁 | 当偏向锁被另一个线程访问时则升级为轻量级锁,其他线程通过自旋获取锁 | |
重量级锁 | 轻量级锁自旋到一定值时会进入阻塞后膨胀为重量级锁 | |
自旋锁 | 线程不会进入阻塞状态,通过循环方式获取锁,减少上下午切换 | |
乐观锁 | 乐观的认为数据被修改的情况少,然后不加锁 | 版本号+自旋锁、CAS |
悲观锁 | 悲观的认为数据被修改的情况多,直接将数据锁起来 | 行锁、表锁、Synchronized |
四、提高系统性能
提高系统性能(系统性能的大部分问题都在数据库,数据只有一份但是并发操作的情况多。这种情况下在过滤无用请求后就可以考虑如何减少数据锁提高性能)。提高性能大致分为物理优化(机器选型)、配置优化(根据机器的选型进行各种配置调整)、程序优化(减少锁的使用)
4.1 乞丐版
- 用CDN的内容分发就近获取数据(服务区域化部署,提高用户访问速度)
- 前端页面的接口限制防止重复点击、页面懒加载(限制无效请求)
- 利用Nginx进行限流,防止单个ip重复请求,负载均衡(限制无效请求)
- 用Redis缓存读多写少的数据,减少数据库的访问(限制无效数据库请求)
- 利用消息队列去削峰填谷,服务去消费的时候就可以用乐观锁队列消费数据(将某一刻的高并发转化为异步队列处理)
- 数据库添加索引优化,历史数据使用不多可以进行归档,无法归档的数据可以分库分表(提高数据库执行效率)
- 服务器的选择,计算多的用CPU密集型,redis服务器用内存密集型。(物理优化)
- JVM优化、数据库优化等(配置优化)
4.2 升级版
- 网管层:
- 吞吐量和负载一定要保证
- ngnix调优
- 负载均衡
- 动静分离。
- 服务层:
- 服务层第一保证高质量的代码,算法优化。
- 服务拆分,一些可能出现超高并发的接口单独抽取出一个服务。
- 微服务结合容器技术达到弹性扩容。
- 缓存技术减少数据库的压力。
- 运用可靠消息+最终一致性的柔性事务方式解耦高并发服务和其他服务。
- 数据库:
- 按照业务场景选用合适的数据库引擎
- sql优化
- 海量数据是不是需要分表分库设计
- 数据库之前需不需要设计布隆过滤器等等。
- Java单体服务
- jvm参数调优
- 选用合适的垃圾收集器
- 内存合理分配。