前言
今天 无聊闲想的时候, 突然想到了一下多线程相关的问题
1. 累增的问题, 也就是对于一个int多个[x]线程对其进行++操作, 每个线程操作N次
1.1 常规场景下面, 多线程进行处理, 得到的结果应该是小于x * N, 因为++并非原子操作
1.2 在常规的场景下面, 使用synchronized[Lock相关api], 进行处理, 虽然能够保证正确性, 开销似乎是太大了一些[悲观锁]
1.3 使用AtomicInteger就不一样了, CAS操作保证了线程的安全[乐观锁][忙等]
问题描述
对于一个整数, 多个线程进行多次累加操作, 确保正确性 !
思路
当然 这里确保正确性并非此帖核心问题, 因为 上面已经介绍了嘛, synchronized关键字, Lock相关api, Atomic相关api都可以实现
详细问题请往后看
参考代码
/**
* file name :
* created at : 下午6:23:41 2016年8月30日
* created by 970655147
*/
package .test01;
public class Test19IncInConcurrent {
// 并发场景下面的累增测试
public static void main(String[] args) throws Exception {
= null;
int[] res = {0, 0, 0, 0 };
IdxGenerator idx = new IdxGenerator(0);
res[()] = inc01();
res[()] = inc02();
res[()] = inc02Lock();
res[()] = inc03();
log(res);
}
// threadNums, synchronized on 'outer'
static int THREAD_NUM = 10;
// static boolean syncOuter = true;
static boolean syncOuter = false;
// inc per thread
static int INC_NUM = 1000000;
// case 1. not synchronized multi-threads
public static int inc01() throws Exception {
MyInteger res = new MyInteger(0);
Runnable[] runnables = new Runnable[THREAD_NUM];
for(int i=0; i<; i++) {
runnables[i] = new Case01(res);
}
doExec(runnables);
return ;
}
// case 2. synchronized multi-threads
public static int inc02() throws Exception {
MyInteger res = new MyInteger(0);
Runnable[] runnables = new Runnable[THREAD_NUM];
for(int i=0; i<; i++) {
runnables[i] = new Case02(res);
}
doExec(runnables);
return ;
}
// case 2Lock. synchronized multi-threads
public static int inc02Lock() throws Exception {
MyInteger res = new MyInteger(0);
Runnable[] runnables = new Runnable[THREAD_NUM];
for(int i=0; i<; i++) {
runnables[i] = new Case02Lock(res);
}
doExec(runnables);
return ;
}
// case 3. AtomicInteger
public static int inc03() throws Exception {
AtomicInteger res = new AtomicInteger();
Runnable[] runnables = new Runnable[THREAD_NUM];
for(int i=0; i<; i++) {
runnables[i] = new Case03(res);
}
doExec(runnables);
return ();
}
// doExec, run & join
private static void doExec(Runnable[] runnables) throws Exception {
long start = ();
Thread[] threads = new Thread[ ];
for(int i=0; i<; i++) {
threads[i] = new Thread(runnables[i]);
threads[i].start();
}
for(int i=0; i<; i++) {
threads[i].join();
}
long spent = (start);
log("spent " + spent + " ms !");
}
// case1's Runnable
static class Case01 implements Runnable {
private MyInteger i;
public Case01(MyInteger i) {
this.i = i;
}
@Override
public void run() {
for(int i=0; i<INC_NUM; i++) {
this. ++;
}
}
}
// case2's Runnable
static class Case02 implements Runnable {
private MyInteger i;
public Case02(MyInteger i) {
this.i = i;
}
@Override
public void run() {
if(syncOuter) {
synchronized () {
for(int i=0; i<INC_NUM; i++) {
this. ++;
}
}
} else {
for(int i=0; i<INC_NUM; i++) {
synchronized () {
this. ++;
}
}
}
}
}
// case2Lock's Runnable
static class Case02Lock implements Runnable {
private MyInteger i;
private static Lock lock = new ReentrantLock();
public Case02Lock(MyInteger i) {
this.i = i;
}
@Override
public void run() {
if(syncOuter) {
();
try {
for(int i=0; i<INC_NUM; i++) {
this. ++;
}
} finally {
();
}
} else {
for(int i=0; i<INC_NUM; i++) {
();
try {
this. ++;
} finally {
();
}
}
}
}
}
// case3's Runnable
static class Case03 implements Runnable {
private AtomicInteger i;
public Case03(AtomicInteger i) {
this.i = i;
}
@Override
public void run() {
for(int i=0; i<INC_NUM; i++) {
this.();
}
}
}
// MyInteger
static class MyInteger {
public int i;
public MyInteger(int i) {
this.i = i;
}
}
}
测试结果
测试环境 : win7 + jdk1.7.40[64bit][serverVM]
以下结果依次为线程数为1-10的结果
syncOuter为true的时候, Lock, synchronized的开销差不多[有时候 甚至于synchronized的开销比思路1的开销还小呢]
-----------------------------1----------------------------------
spent 7 ms !
spent 69 ms !
spent 88 ms !
spent 37 ms !
1000000, 1000000, 1000000, 1000000
-----------------------------2----------------------------------
spent 9 ms !
spent 344 ms !
spent 405 ms !
spent 261 ms !
1011059, 2000000, 2000000, 2000000
-----------------------------3----------------------------------
spent 12 ms !
spent 541 ms !
spent 373 ms !
spent 843 ms !
1995970, 3000000, 3000000, 3000000
-----------------------------4----------------------------------
spent 11 ms !
spent 775 ms !
spent 427 ms !
spent 1207 ms !
1999023, 4000000, 4000000, 4000000
-----------------------------5----------------------------------
spent 13 ms !
spent 877 ms !
spent 519 ms !
spent 1768 ms !
3005573, 5000000, 5000000, 5000000
-----------------------------6----------------------------------
spent 14 ms !
spent 1153 ms !
spent 628 ms !
spent 2142 ms !
2861138, 6000000, 6000000, 6000000
-----------------------------7----------------------------------
spent 14 ms !
spent 1346 ms !
spent 691 ms !
spent 2398 ms !
3911842, 7000000, 7000000, 7000000
-----------------------------8----------------------------------
spent 16 ms !
spent 1533 ms !
spent 809 ms !
spent 3032 ms !
4608651, 8000000, 8000000, 8000000
-----------------------------9----------------------------------
spent 15 ms !
spent 1771 ms !
spent 883 ms !
spent 3713 ms !
3348381, 9000000, 9000000, 9000000
-----------------------------10----------------------------------
spent 14 ms !
spent 1909 ms !
spent 976 ms !
spent 4052 ms !
4633594, 10000000, 10000000, 10000000
1 开销上面的差别
关键的地方在于, 效率上面的差别, 为何差这么多[请见之后的结果], 记得曾经在某本虚拟机方面书上面看过synchronized是比较重量级的, jdk1.5的时候增加了并发包相关的api, 可以使用Lock相关api[ReentryLock, StampedLock[jdk1.8] ], 然后在jdk1.6的时候, 似乎是对于synchronized做了优化 缩小了其余Lock相关api的差距
第一种思路 这里就不说了, 不能保证正确性
按照我的思考, synchronized应该是最慢的吧, 然后是Lock相关api, 然后是Atomic相关api
谁知道测试下来,, 与我所想的差距也太大了吧,, 在线程数为1, 2的时候Atomic相关api最快, 其他两个其次
当线程数为3-10的时候, Lock相关api最快, synchronized差不多是前者的两倍, AtomicInteger差不多是synchronized的两倍
对于AtomicInteger的思路
为何会有这么大的差距呢, 我的机器4核, 假设同时两个cpu跑了两个线程, 假设发生四次碰撞更新成功一次i, 那么 也不应该开销这么大啊,,
还是先占一个位置, 等待以后补了脑再回来思考这些问题吧,,,
或者 路过的大大指点指点 +_+
总结
非常有意思的一个问题, 期待能够快点相同问题之所在