Java高并发情况下的锁机制优化
本文主要讲并行优化的几种方式, 其结构如下:

锁优化
减少锁的持有时间
例如避免给整个方法加锁
1 public synchronized void syncMethod(){
2 othercode1();
3 mutextMethod();
4 othercode2();
5 }
改进后

1 public void syncMethod2(){
2 othercode1();
3 synchronized(this){
4 mutextMethod();
5 }
6 othercode2();
7 }

减小锁的粒度
将大对象,拆成小对象,大大增加并行度,降低锁竞争. 如此一来偏向锁,轻量级锁成功率提高.
一个简单的例子就是jdk内置的ConcurrentHashMap与SynchronizedMap.
Collections.synchronizedMap
其本质是在读写map操作上都加了锁, 在高并发下性能一般.

ConcurrentHashMap
内部使用分区Segment来表示不同的部分, 每个分区其实就是一个小的hashtable. 各自有自己的锁.
只要多个修改发生在不同的分区, 他们就可以并发的进行. 把一个整体分成了16个Segment, 最高支持16个线程并发修改.
代码中运用了很多volatile声明共享变量, 第一时间获取修改的内容, 性能较好.
读写分离锁替代独占锁
顾名思义, 用ReadWriteLock将读写的锁分离开来, 尤其在读多写少的场合, 可以有效提升系统的并发能力.
- 读-读不互斥:读读之间不阻塞。
- 读-写互斥:读阻塞写,写也会阻塞读。
- 写-写互斥:写写阻塞。
锁分离
在读写锁的思想上做进一步的延伸, 根据不同的功能拆分不同的锁, 进行有效的锁分离.
一个典型的示例便是LinkedBlockingQueue,在它内部, take和put操作本身是隔离的,
有若干个元素的时候, 一个在queue的头部操作, 一个在queue的尾部操作, 因此分别持有一把独立的锁.


1 /** Lock held by take, poll, etc */
2 private final ReentrantLock takeLock = new ReentrantLock();
3
4 /** Wait queue for waiting takes */
5 private final Condition notEmpty = takeLock.newCondition();
6
7 /** Lock held by put, offer, etc */
8 private final ReentrantLock putLock = new ReentrantLock();
9
10 /** Wait queue for waiting puts */
11 private final Condition notFull = putLock.newCondition();

锁粗化
通常情况下, 为了保证多线程间的有效并发, 会要求每个线程持有锁的时间尽量短,
即在使用完公共资源后, 应该立即释放锁. 只有这样, 等待在这个锁上的其他线程才能尽早的获得资源执行任务.
而凡事都有一个度, 如果对同一个锁不停的进行请求 同步和释放, 其本身也会消耗系统宝贵的资源, 反而不利于性能的优化
一个极端的例子如下, 在一个循环中不停的请求同一个锁.

1 for(int i = 0; i < 1000; i++){
2 synchronized(lock){
3
4 }
5 }
6
7 // 优化后
8 synchronized(lock){
9 for(int i = 0;i < 1000; i++){
10
11 }
12 }

锁粗化与减少锁的持有时间, 两者是截然相反的, 需要在实际应用中根据不同的场合权衡使用.
JDK中各种涉及锁优化的并发类可以看之前的博文: 并发包总结
ThreadLocal
除了控制有限资源访问外, 我们还可以增加资源来保证对象线程安全.
对于一些线程不安全的对象, 例如SimpleDateFormat, 与其加锁让100个线程来竞争获取,
不如准备100个SimpleDateFormat, 每个线程各自为营, 很快的完成format工作.
示例

1 public class ThreadLocalDemo {
2
3 public static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal();
4
5 public static void main(String[] args){
6 ExecutorService service = Executors.newFixedThreadPool(10);
7 for (int i = 0; i < 100; i++) {
8 service.submit(new Runnable() {
9 @Override
10 public void run() {
11 if (threadLocal.get() == null) {
12 threadLocal.set(new SimpleDateFormat("yyyy-MM-dd"));
13 }
14
15 System.out.println(threadLocal.get().format(new Date()));
16 }
17 });
18 }
19 }
20 }

原理
对于set方法, 先获取当前线程对象, 然后getMap()获取线程的ThreadLocalMap, 并将值放入map中.
该map是线程Thread的内部变量, 其key为threadlocal, vaule为我们set进去的值.

1 public void set(T value) {
2 Thread t = Thread.currentThread();
3 ThreadLocalMap map = getMap(t);
4 if (map != null)
5 map.set(this, value);
6 else
7 createMap(t, value);
8 }

对于get方法, 自然是先拿到map, 然后从map中获取数据.

1 public T get() {
2 Thread t = Thread.currentThread();
3 ThreadLocalMap map = getMap(t);
4 if (map != null) {
5 ThreadLocalMap.Entry e = map.getEntry(this);
6 if (e != null)
7 return (T)e.value;
8 }
9 return setInitialValue();
10 }

内存释放
- 手动释放: 调用threadlocal.set(null)或者threadlocal.remove()即可
- 自动释放: 关闭线程池, 线程结束后, 自动释放threadlocalmap.

1 public class StaticThreadLocalTest {
2
3 private static ThreadLocal tt = new ThreadLocal();
4 public static void main(String[] args) throws InterruptedException {
5 ExecutorService service = Executors.newFixedThreadPool(1);
6 for (int i = 0; i < 3; i++) {
7 service.submit(new Runnable() {
8 @Override
9 public void run() {
10 BigMemoryObject oo = new BigMemoryObject();
11 tt.set(oo);
12 // 做些其他事情
13 // 释放方式一: 手动置null
14 // tt.set(null);
15 // 释放方式二: 手动remove
16 // tt.remove();
17 }
18 });
19 }
24 // 释放方式三: 关闭线程或者线程池
25 // 直接new Thread().start()的场景, 会在run结束后自动销毁线程
26 // service.shutdown();
27
28 while (true) {
29 Thread.sleep(24 * 3600 * 1000);
30 }
31 }
32
33 }
34 // 构建一个大内存对象, 便于观察内存波动.
35 class BigMemoryObject{
36
37 List<Integer> list = new ArrayList<>();
38
39 BigMemoryObject() {
40 for (int i = 0; i < 10000000; i++) {
41 list.add(i);
42 }
43 }
44 }

内存泄露
内存泄露主要出现在无法关闭的线程中, 例如web容器提供的并发线程池, 线程都是复用的.
由于ThreadLocalMap生命周期和线程生命周期一样长. 对于一些被强引用持有的ThreadLocal, 如定义为static.
如果在使用结束后, 没有手动释放ThreadLocal, 由于线程会被重复使用, 那么会出现之前的线程对象残留问题,
造成内存泄露, 甚至业务逻辑紊乱.
对于没有强引用持有的ThreadLocal, 如方法内变量, 是不是就万事大吉了呢? 答案是否定的.
虽然ThreadLocalMap会在get和set等操作里删除key 为 null的对象, 但是这个方法并不是100%会执行到.
看ThreadLocalMap源码即可发现, 只有调用了getEntryAfterMiss后才会执行清除操作,
如果后续线程没满足条件或者都没执行get set操作, 那么依然存在内存残留问题.

1 private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal key) {
2 int i = key.threadLocalHashCode & (table.length - 1);
3 ThreadLocal.ThreadLocalMap.Entry e = table[i];
4 if (e != null && e.get() == key)
5 return e;
6 else
7 // 并不是一定会执行
8 return getEntryAfterMiss(key, i, e);
9 }
10
11 private ThreadLocal.ThreadLocalMap.Entry getEntryAfterMiss(ThreadLocal key, int i, ThreadLocal.ThreadLocalMap.Entry e) {
12 ThreadLocal.ThreadLocalMap.Entry[] tab = table;
13 int len = tab.length;
14
15 while (e != null) {
16 ThreadLocal k = e.get();
17 if (k == key)
18 return e;
19 // 删除key为null的value
20 if (k == null)
21 expungeStaleEntry(i);
22 else
23 i = nextIndex(i, len);
24 e = tab[i];
25 }
26 return null;
27 }

最佳实践
不管threadlocal是static还是非static的, 都要像加锁解锁一样, 每次用完后, 手动清理, 释放对象.
无锁
与锁相比, 使用CAS操作, 由于其非阻塞性, 因此不存在死锁问题, 同时线程之间的相互影响,
也远小于锁的方式. 使用无锁的方案, 可以减少锁竞争以及线程频繁调度带来的系统开销.
例如生产消费者模型中, 可以使用BlockingQueue来作为内存缓冲区, 但他是基于锁和阻塞实现的线程同步.
如果想要在高并发场合下获取更好的性能, 则可以使用基于CAS的ConcurrentLinkedQueue.
同理, 如果可以使用CAS方式实现整个生产消费者模型, 那么也将获得可观的性能提升, 如Disruptor框架.
关于无锁, 这边不再赘述, 之前博文已经有所介绍, 具体见: Java高并发之无锁与Atomic源码分析
原文链接:https://www.cnblogs.com/xdecode/p/9137804.html
-END-

Java高并发情况下的锁机制优化的更多相关文章
- c# redis 利用锁(StackExchange.Redis LockTake)来保证数据在高并发情况下的正确性
之前有写过一篇介绍c#操作redis的文章 http://www.cnblogs.com/axel10/p/8459434.html ,这篇文章中的案例使用了StringIncrement来实现了高并 ...
- (转载)java高并发:CAS无锁原理及广泛应用
java高并发:CAS无锁原理及广泛应用 版权声明:本文为博主原创文章,未经博主允许不得转载,转载请注明出处. 博主博客地址是 http://blog.csdn.net/liubenlong007 ...
- Jackson高并发情况下,产生阻塞
情况:在高并发情况下,查看线程栈信息,有大量的线程BLOCKED. 从线程栈得知,线程栈中出现了阻塞,锁在了com.fasterxml.jackson.databind.ser.SerializerC ...
- 高并发情况下分布式全局ID
1.高并发情况下,生成分布式全局id策略2.利用全球唯一UUID生成订单号优缺点3.基于数据库自增或者序列生成订单号4.数据库集群如何考虑数据库自增唯一性5.基于Redis生成生成全局id策略6.Tw ...
- 关于WCF服务在高并发情况下报目标积极拒绝的异常处理
最近弄了个wcf的监控服务,偶尔监控到目标服务会报一个目标积极拒绝的错误.一开始以为服务停止了,上服务器检查目标服务好好的活着.于是开始查原因. 一般来说目标积极拒绝(TCP 10061)的异常主要是 ...
- WCF服务在高并发情况下报目标积极拒绝的异常处理 z
http://www.cnblogs.com/kklldog/p/5037006.html wcf的监控服务,偶尔监控到目标服务会报一个目标积极拒绝的错误.一开始以为服务停止了,上服务器检查目标服务好 ...
- Linux的虚拟内存管理-如何分配和释放内存,以提高服务器在高并发情况下的性能,从而降低了系统的负载
Linux的虚拟内存管理有几个关键概念: Linux 虚拟地址空间如何分布?malloc和free是如何分配和释放内存?如何查看堆内内存的碎片情况?既然堆内内存brk和sbrk不能直接释放,为什么不全 ...
- 小D课堂 - 新版本微服务springcloud+Docker教程_6-05 高级篇幅之高并发情况下
笔记 5.高级篇幅之高并发情况下接口限流特技 简介:谷歌guava框架介绍,网关限流使用 1.nginx层限流 2.网关层限流 开始 mysql最大的连接数就是3千多.如果想把应用搞好 ...
- Mysql在高并发情况下,防止库存超卖而小于0的解决方案
背景: 本人上次做申领campaign的PHP后台时,因为项目上线后某些时段同时申领的人过多,导致一些专柜的存货为负数(<0),还好并发量不是特别大,只存在于小部分专柜而且一般都是-1的状况,没 ...
随机推荐
- 396. Rotate Function 移动加权求和,取最大值
[抄题]: Given an array of integers A and let n to be its length. Assume Bk to be an array obtained by ...
- [leetcode]22. Generate Parentheses生成括号
Given n pairs of parentheses, write a function to generate all combinations of well-formed parenthes ...
- eclipse 安装lombok插件
下载lombok 下载地址:https://projectlombok.org/downloads/lombok.jar 或者访问官网下载 https://projectlombok.org/ 安装 ...
- autoMapper dotnetcore webapi 自动添加映射 abp
在ef的xxxxApplicationModule的Initialize方法中,已经添加了自动映射的配置,如下图: 写的很明白了,我们只需要写一个类继承Profile就可以了.如下图所示: 这样就可以 ...
- CSS学习总结4:派生选择器学习总结
派生选择器:通过依据元素在其位置的上下文关系来定义样式,你可以使标记更加简洁.派生选择器中一共分为三种:后代选择器.子元素选择器.相邻兄弟选择器. 1.初识派生选择器 实例:你希望列表中的 stron ...
- 大型互联网 b2b b2c o2o 电子商务微服务云平台
鸿鹄云商大型企业分布式互联网电子商务平台,推出PC+微信+APP+云服务的云商平台系统,其中包括B2B.B2C.C2C.O2O.新零售.直播电商等子平台. 分布式.微服务.云架构电子商务平台 java ...
- 2019.03.01 bzoj3075: [Usaco2013]Necklace(kmp+dp)
传送门 题意简述:给出S,TS,TS,T两个字串,∣S∣≤10000,∣T∣≤1000|S|\le10000,|T|\le1000∣S∣≤10000,∣T∣≤1000,问至少从SSS中删去几个字符能够 ...
- 实战C++对象模型之成员函数调用
先说结论:C++的类成员函数和C函数实质是一样的,只是C++类成员函数多了隐藏参数this. 通过本文的演示,可以看见这背后的一切,完全可C函数方式调用C++类普通成员函数和C++类虚拟成员函数. 为 ...
- C++基础笔记(string截取)
#include <iostream> #include <string> using namespace std; int main(int argc, char* argv ...
- iOS逆向之Reveal
Reveal是一个强大的UI分析工具,使用它可以查看各个界面的视图层级,在解决界面显示问题时非常有用.它最大的特点就是非常直观,查看UI布局的时候非常方便. 我们知道,Reveal官网提供的方法只能监 ...