关于JAVA多线程并发synchronized的测试与合理使用
在项目开发中, 或许会碰到JAVA的多线程处理, 为保证业务数据的正常, 必须加上锁机制, 常用的处理方法一般是加上synchronized关键字, 目前JDK版本对synchronized已经做了很好的优化, 我们不用再考虑其性能, 但在实际使用中, 往往由于处理不当, 导致系统性能的严重下降, 那么该如何合理的使用synchronized, 必须对其使用方式有个全面了解, 在网上搜寻的资料, 给出的是四种使用方式, 其实可总结为两种, 一个是同步代码块, 一个是同步方法体, 那么该如何使用, 请看下面的测试:
准备两个方法, 对同一个变量做加法, 再对每个方法, 分别开十个线程执行:
public class ThreadUnit{private int i = 0;private Object obj1 = new Object();private Object obj2 = new Object();public synchronized Integer doAdd1(Long start) throws Exception{Thread.sleep(100);++i;Thread.sleep(100);return i;}public Integer doAdd2(Long start) throws Exception{Thread.sleep(100);++i;Thread.sleep(100);return i;}}
相关代码:
// 十个线程同时执行方法2for (int i = 0; i < 10; i++){new Thread(new MessageThread(1, threadUnit)).start();}// 十个线程同时执行方法2for (int j = 0; j < 10; j++){new Thread(new MessageThread(2, threadUnit)).start();}线程处理:
public void run(){try{if (operate == 2){long start = System.currentTimeMillis();int i = threadUnit.doAdd2(start);long takeTime = System.currentTimeMillis() - start;System.out.println("doAdd2() => i=" + i + ", spendTime=" + takeTime + "ms");spendTime += takeTime;}else{long start = System.currentTimeMillis();int i = threadUnit.doAdd1(start);long takeTime = System.currentTimeMillis() - start;System.out.println("doAdd1() => i=" + i + ", spendTime=" + takeTime + "ms");spendTime += takeTime;}}catch (Exception e){e.printStackTrace();}}
运行结果:
1. 在两个方法体上都加上synchronized
public synchronized Integer doAdd1(Long start) throws Exception
public synchronized Integer doAdd2(Long start) throws Exception
执行结果:
doAdd1() => i=1, spendTime=203msdoAdd2() => i=2, spendTime=406msdoAdd2() => i=3, spendTime=609msdoAdd2() => i=4, spendTime=796msdoAdd2() => i=5, spendTime=1000msdoAdd2() => i=6, spendTime=1203msdoAdd2() => i=7, spendTime=1406msdoAdd2() => i=8, spendTime=1609msdoAdd2() => i=9, spendTime=1812msdoAdd2() => i=10, spendTime=2015msdoAdd2() => i=11, spendTime=2218msdoAdd1() => i=12, spendTime=2406msdoAdd1() => i=13, spendTime=2609msdoAdd1() => i=14, spendTime=2812msdoAdd1() => i=15, spendTime=3015msdoAdd1() => i=16, spendTime=3218msdoAdd1() => i=17, spendTime=3421msdoAdd1() => i=18, spendTime=3625msdoAdd1() => i=19, spendTime=3828msdoAdd1() => i=20, spendTime=4015ms花费时间:42226ms
都是有序执行, 变量值没有产生错乱, 但花费时间42226ms
2.在doAdd1方法上加上synchronized, doAdd2不加.
public synchronized Integer doAdd1(Long start) throws Exception
执行结果:
doAdd1方法加上synchronized:doAdd1() => i=9, spendTime=204msdoAdd2() => i=9, spendTime=188msdoAdd2() => i=9, spendTime=188msdoAdd2() => i=9, spendTime=188msdoAdd2() => i=9, spendTime=188msdoAdd2() => i=9, spendTime=188msdoAdd2() => i=9, spendTime=188msdoAdd2() => i=9, spendTime=188msdoAdd2() => i=9, spendTime=188msdoAdd2() => i=9, spendTime=188msdoAdd2() => i=9, spendTime=188msdoAdd1() => i=10, spendTime=391msdoAdd1() => i=11, spendTime=610msdoAdd1() => i=12, spendTime=813msdoAdd1() => i=13, spendTime=1016msdoAdd1() => i=14, spendTime=1219msdoAdd1() => i=15, spendTime=1422msdoAdd1() => i=16, spendTime=1610msdoAdd1() => i=17, spendTime=1813msdoAdd1() => i=18, spendTime=2016ms花费时间:12994ms
doAdd2方法瞬间执行完成, 之后doAdd1方法则是串行有序执行. 这时doAdd2方法获取的变量值已经错乱, doAdd1获取的正常. 花费时间:12994ms
3. 两个方法在都没使用synchronized前的情况:
执行结果:
doAdd1() => i=16, spendTime=203msdoAdd1() => i=16, spendTime=203msdoAdd1() => i=16, spendTime=203msdoAdd1() => i=16, spendTime=203msdoAdd1() => i=16, spendTime=203msdoAdd1() => i=16, spendTime=203msdoAdd1() => i=16, spendTime=203msdoAdd1() => i=16, spendTime=203msdoAdd1() => i=16, spendTime=203msdoAdd2() => i=16, spendTime=203msdoAdd2() => i=16, spendTime=203msdoAdd2() => i=16, spendTime=203msdoAdd1() => i=16, spendTime=203msdoAdd2() => i=16, spendTime=203msdoAdd2() => i=16, spendTime=203msdoAdd2() => i=16, spendTime=203msdoAdd2() => i=16, spendTime=203msdoAdd2() => i=16, spendTime=203msdoAdd2() => i=16, spendTime=203msdoAdd2() => i=16, spendTime=203ms花费时间:4060ms
可以看到, 两个方法的变量值获取已经错乱, 但花费时间最少4060ms
4. 使用同步块, 在两个方法内采用不同的对象锁:
doAdd1:
synchronized (obj1) { Thread.sleep(100); ++i; Thread.sleep(100); return i; }doAdd2:
synchronized (obj2){Thread.sleep(100);++i;Thread.sleep(100);return i;}执行结果:
doAdd1() => i=2, spendTime=203msdoAdd2() => i=2, spendTime=203msdoAdd1() => i=4, spendTime=406msdoAdd2() => i=4, spendTime=406msdoAdd1() => i=6, spendTime=609msdoAdd2() => i=6, spendTime=609msdoAdd1() => i=8, spendTime=812msdoAdd2() => i=8, spendTime=812msdoAdd1() => i=10, spendTime=1000msdoAdd2() => i=10, spendTime=1015msdoAdd1() => i=12, spendTime=1203msdoAdd2() => i=12, spendTime=1203msdoAdd1() => i=14, spendTime=1406msdoAdd2() => i=14, spendTime=1406msdoAdd1() => i=16, spendTime=1609msdoAdd2() => i=16, spendTime=1609msdoAdd1() => i=18, spendTime=1812msdoAdd2() => i=18, spendTime=1812msdoAdd1() => i=20, spendTime=2015msdoAdd2() => i=20, spendTime=2015ms花费时间:22165ms
两个方法有序交替执行, 互不影响, 花费时间:22165ms, 相对加锁同一对象执行的时间缩短.
5. 使用同步块, 使用方法参数作为对象锁:
public Integer doAdd1(Long start) throws Exception{synchronized (start){Thread.sleep(100);++i;Thread.sleep(100);return i;}}执行结果:
doAdd1() => i=15, spendTime=203msdoAdd1() => i=15, spendTime=203msdoAdd1() => i=15, spendTime=203msdoAdd1() => i=15, spendTime=203msdoAdd1() => i=15, spendTime=203msdoAdd1() => i=15, spendTime=203msdoAdd1() => i=15, spendTime=203msdoAdd2() => i=15, spendTime=203msdoAdd2() => i=15, spendTime=203msdoAdd1() => i=15, spendTime=203msdoAdd1() => i=15, spendTime=203msdoAdd2() => i=15, spendTime=203msdoAdd2() => i=15, spendTime=203msdoAdd2() => i=15, spendTime=203msdoAdd2() => i=15, spendTime=203msdoAdd2() => i=15, spendTime=203msdoAdd2() => i=15, spendTime=203msdoAdd2() => i=15, spendTime=203msdoAdd1() => i=15, spendTime=203msdoAdd2() => i=15, spendTime=203ms花费时间:4060ms
执行效果和第三种情况相同, 每个参数作为不同的对象, 即便加上synchronized也不能起到锁的效果.
6. 把调用的类改为静态类, 只在一个方法上加锁:
加锁doAdd1方法:
public static synchronized Integer doAdd1(Long start) throws Exception
执行结果:
doAdd1() => i=9, spendTime=203msdoAdd2() => i=9, spendTime=203msdoAdd2() => i=9, spendTime=203msdoAdd2() => i=9, spendTime=203msdoAdd2() => i=9, spendTime=203msdoAdd2() => i=9, spendTime=203msdoAdd2() => i=9, spendTime=203msdoAdd2() => i=9, spendTime=203msdoAdd2() => i=9, spendTime=203msdoAdd2() => i=9, spendTime=203msdoAdd2() => i=9, spendTime=203msdoAdd1() => i=10, spendTime=406msdoAdd1() => i=11, spendTime=609msdoAdd1() => i=12, spendTime=812msdoAdd1() => i=13, spendTime=1015msdoAdd1() => i=14, spendTime=1218msdoAdd1() => i=15, spendTime=1406msdoAdd1() => i=16, spendTime=1609msdoAdd1() => i=17, spendTime=1812msdoAdd1() => i=18, spendTime=2015ms花费时间:13135ms
和第二种情形类似, 没有加锁的doAdd2方法瞬间执行完成, doAdd1方法则是串行有序执行.
总结:
1. synchronized关键在于锁定的对象, 如果是同一对象, 那么所有执行线程, 必须等待对象锁释放后才能执行, 如果是不同对象, 那么只对各对象所关联的线程生效.
2. synchronized若加在方法体上, 默认锁定的是对象本身. 对于所有加锁的方法, 都是按照串行规则有序执行, 对于没有加锁的方法, 不受任何影响, 静态类同理.
3. 合理使用synchronized, 既要保证稳定性, 又要保证性能, 需要在两者间作出权衡, 尽量把synchronized范围细度化, 合理控制业务处理流程; 对象操作原子化, 减少锁的使用;
不要盲目在方法体上加synchronized关键字, 如果每个方法负责处理的是不同业务, 那么尽量采用第四种情形, 使用不同的对象锁处理, 而不是锁定整个对象.