About 5 min

一、java中如何实现锁

保持某一段代码的原子性,synchronized是如何实现的。通过jol可以进行实验(观察代码在内存中如何执行的,maven坐标: jol-core)

原子性:上锁之后的代码不可被打断(持有同样锁的其他代码,打断指的是同时运行)

二、锁对比

悲观乐观
描述悲观的认为数据的竞争特别大,直接将数据锁起来乐观的认为数据竞争小,不加锁
场景并发大,临界区小并发小,临界区大
例子行锁,表锁, Synchronized等版本号、CAS
缺点重量级、锁的开销比较大并发高的乐观锁自旋会导致性能下降
  1. 自旋锁:从字面的意思看就是自我旋转,应用到乐观锁中比如带上版本号修改数据的时候,如果版本被其他线程修改了则重新获取新的版本号后再次修改,一直自旋到成功为止。
  2. 乐观锁:乐观的认为数据被修改的情况少,然后不加锁。在不加锁的情况下如果偶尔遇到了其他线程就可以用版本号+自旋锁去解决
  3. 悲观锁:悲观的认为数据被修改的情况多,直接将数据锁起来。并发高的时候必须等之前的线程处理完成之后才操作数据
  4. 互斥锁:线程在进入临界区的时候需要先获得锁,退出临界区需要释放锁。如果其他限制在使用锁则进入到阻塞状态直到被释放

三、整理锁

锁名称描述例子
公平锁按照申请锁的顺序获取锁
非公平锁获取锁的顺序不固定ReentrantLock、Synchronized
可重入锁在外层方法获取锁后在进入内层方法也自动获取锁ReentrantLock、Synchronized
独享锁只能被一个线程使用ReentrantLock、Synchronized
共享锁可以被多个线程使用ReadWriteLock(读是共享锁,写诗独享锁)
互斥锁访问前加锁访问后解锁(多人抢一个马桶)ReentrantLock
读写锁是共享锁也是互斥锁,读的时候共享写的时候互斥ReadWriteLock
分段锁锁的设计,为了细化锁粒度。ConcurrentHashMap
偏向锁代码一直被一个线程使用则线程会自动获取锁Synchronized的前版本
轻量级锁当偏向锁被另一个线程访问时则升级为轻量级锁,其他线程通过自旋获取锁
重量级锁轻量级锁自旋到一定值时会进入阻塞后膨胀为重量级锁
自旋锁线程不会进入阻塞状态,通过循环方式获取锁,减少上下午切换
乐观锁乐观的认为数据被修改的情况少,然后不加锁版本号+自旋锁、CAS
悲观锁悲观的认为数据被修改的情况多,直接将数据锁起来行锁、表锁、Synchronized

四、提高系统性能

提高系统性能(系统性能的大部分问题都在数据库,数据只有一份但是并发操作的情况多。这种情况下在过滤无用请求后就可以考虑如何减少数据锁提高性能)。提高性能大致分为物理优化(机器选型)、配置优化(根据机器的选型进行各种配置调整)、程序优化(减少锁的使用)

4.1 乞丐版

  1. 用CDN的内容分发就近获取数据(服务区域化部署,提高用户访问速度)
  2. 前端页面的接口限制防止重复点击、页面懒加载(限制无效请求)
  3. 利用Nginx进行限流,防止单个ip重复请求,负载均衡(限制无效请求)
  4. 用Redis缓存读多写少的数据,减少数据库的访问(限制无效数据库请求)
  5. 利用消息队列去削峰填谷,服务去消费的时候就可以用乐观锁队列消费数据(将某一刻的高并发转化为异步队列处理)
  6. 数据库添加索引优化,历史数据使用不多可以进行归档,无法归档的数据可以分库分表(提高数据库执行效率)
  7. 服务器的选择,计算多的用CPU密集型,redis服务器用内存密集型。(物理优化)
  8. JVM优化、数据库优化等(配置优化)

4.2 升级版

  1. 网管层:
    1. 吞吐量和负载一定要保证
    2. ngnix调优
    3. 负载均衡
    4. 动静分离。
  2. 服务层:
    1. 服务层第一保证高质量的代码,算法优化。
    2. 服务拆分,一些可能出现超高并发的接口单独抽取出一个服务。
    3. 微服务结合容器技术达到弹性扩容。
    4. 缓存技术减少数据库的压力。
    5. 运用可靠消息+最终一致性的柔性事务方式解耦高并发服务和其他服务。
  3. 数据库:
    1. 按照业务场景选用合适的数据库引擎
    2. sql优化
    3. 海量数据是不是需要分表分库设计
    4. 数据库之前需不需要设计布隆过滤器等等。
  4. Java单体服务
    1. jvm参数调优
    2. 选用合适的垃圾收集器
    3. 内存合理分配。
Last update:
Contributors: gaoqisen