【CAS之2】CAS操做实践——i++的同步代码与非同步代码比较

参考网页

主要参考java

https://www.jianshu.com/p/fb6e91b013cc算法

代码1--i++非同步代码

代码

package volatileTest;


public class CounterIPlusPlus {


int i = 0;


public static void main(String[] args) throws InterruptedException {

System.out.println("Class Name:"

+ Thread.currentThread().getStackTrace()[1].getClassName());

System.out.println("JDK version:" + System.getProperty("java.version"));


Long s = System.currentTimeMillis();


final CounterIPlusPlus c = new CounterIPlusPlus();


// TODO Auto-generated method stub

for (int j = 0; j < 100; j++) {

new Thread(new Runnable() {

public void run() {

for (int k = 0; k < 1000; k++) {

c.add();

}

}

}).start();

}


while (Thread.activeCount() > 1)

// 保证前面的线程都执行完

Thread.yield();

System.out.println("spend time:" + (System.currentTimeMillis() - s));

System.out.println("result:" + c.i);

}


public void add() {

++i;

}

}

运行结果1

Class Name:volatileTest.CounterIPlusPlusatom

JDK version:1.7.0_79spa

spend time:22操作系统

result:99367线程

运行结果2

Class Name:volatileTest.CounterIPlusPlus3d

JDK version:1.7.0_79code

spend time:39blog

result:100000生命周期

运行结果3

Class Name:volatileTest.CounterIPlusPlus

JDK version:1.7.0_79

spend time:17

result:98962

代码1--i++同步时为什么失败?--从字节码指令角度解读(最后成为汇编指令也是多条指令)

查看字节码指令

i++没法获得原子操做的保证

代码2--synchronized完成i++操做

代码

package volatileTest;


public class CounterSynchronized {


public int i = 0;


public static void main(String... strings) {

System.out.println("Class Name:"

+ Thread.currentThread().getStackTrace()[1].getClassName());

System.out.println("JDK version:" + System.getProperty("java.version"));


Long s = System.currentTimeMillis();


final CounterSynchronized t = new CounterSynchronized();


for (int i = 0; i < 100; i++) {

new Thread() {

public void run() {

for (int j = 0; j < 1000; j++) {

t.increase();

}

}

}.start();

}


while (Thread.activeCount() > 1)

// 保证前面的线程都执行完

Thread.yield();


System.out.println("spend time:" + (System.currentTimeMillis() - s));

System.out.println("result:" + t.i);

}


public synchronized void increase() {

i++;

}

}

运行结果1

Class Name:volatileTest.CounterSynchronized

JDK version:1.7.0_79

spend time:26

result:100000

运行结果2

Class Name:volatileTest.CounterSynchronized

JDK version:1.7.0_79

spend time:44

result:100000

运行结果3

Class Name:volatileTest.CounterSynchronized

JDK version:1.7.0_79

spend time:39

result:100000

代码2--synchronized完成i++操做分析

100个线程,能够想象有100把锁。每一个线程操做时,都会加锁(让其余线程没法操做)。实际上此时100个线程的执行已经不是并行执行了,已经变成了串行执行。

synchronized 每次加锁解锁都涉及上下文切换,须要涉及操做系统级别的操做,因此是重量级的操做。

代码3--cas算法完成自增操做

代码

package volatileTest;


import java.util.concurrent.atomic.AtomicInteger;


public class CounterCAS {


private AtomicInteger i = new AtomicInteger(0);


public static void main(String... strings) {

System.out.println("Class Name:"

+ Thread.currentThread().getStackTrace()[1].getClassName());

System.out.println("JDK version:" + System.getProperty("java.version"));


Long s = System.currentTimeMillis();


final CounterCAS c = new CounterCAS();


for (int j = 0; j < 100; j++) {

new Thread(new Runnable() {

public void run() {

for (int k = 0; k < 1000; k++) {

c.increment();

}

}

}).start();

}


while (Thread.activeCount() > 1)

// 保证前面的线程都执行完

Thread.yield();


System.out.println("spend time:" + (System.currentTimeMillis() - s));

System.out.println("result:" + c.i.get());

}


public void increment() {

for (;;) {

int a = i.get();

if (i.compareAndSet(a, a + 1)) {

break;

}

}

}

}

运行结果1

Class Name:volatileTest.CounterCAS

JDK version:1.7.0_79

spend time:19

result:100000

运行结果2

Class Name:volatileTest.CounterCAS

JDK version:1.7.0_79

spend time:20

result:100000

运行结果3

Class Name:volatileTest.CounterCAS

JDK version:1.7.0_79

spend time:20

result:100000

代码3--cas自增操做同步时为什么成功?

加锁(使用synchronized)能够实现同步操做。

使用CAS,使用AtomicInteger的 incrementAndGet 方法也能够实现同步,原理呢?

硬件技术上

CPU中指令集支持【Compare And Swap】这种原子性操做,好比Intel的cmpxchg指令。

原理上

100条线程同时【Compare And Swap】操做时,只有一个线程能真正成功的进行CAS操做,而后其余线程都会获取CAS操做失败的信号(获取操做失败信号的线程就会什么也不作,这些线程至关于空转了一回)。

代码中的一些细节

都是100个Thread里面再重复1000次操做,为什么不直接开启100 X 1000个线程?

线程太多,直接计算机就卡死了,亲测。

保证前面的线程都执行完--是如何保证的?

while (Thread.activeCount() > 1)

// 保证前面的线程都执行完

Thread.yield();

★★★想象一下CAS实现自增操做是怎么运行的?同时想象下synchronized如何实现的++i?比较一下这两种场景

synchronized操做

synchronized操做i++,实质上并行操做已经变成了串行操做。至于线程A每次抢到锁,加锁后是作了1次i++操做就解锁,仍是连续作了10次i++操做再解锁,不知道,都有可能。这些也都不重要了,由于总共100 X 1000次i++操做至关因而一个一个顺序执行的,因此最终数字不会出错。

synchronized操做,实际上每一个线程都是足足执行了1000次自增,哪一个线程也没少干活,只不过各个线程都再争抢先干完,结束本身的1000次自增任务。详细见后面《CounterSynchronizedDetail.java》的说明。

CAS操做

public void increment() {

for (;;) {

int a = i.get();

if (i.compareAndSet(a, a + 1)) {

break;

}

}

}

CAS操做i++,没有线程的切换,全部线程都是一直在运行,只不过某个线程在作循环CAS操做时,若是一直【捕捉不到本身的备份数据跟内存数据一致的状况】时,就只好一直空转了,可是每一个线程的每次自增操做不走空,啥意思?就是必需要成功进行一次自增操做才会跳出循环。而某次循环时幸运地成功【捕捉到本身的备份数据跟内存数据一致】,则在成功执行自增操做后进入下一次外层的for循环【for (int k = 0; k < 1000; k++)】。

循环CAS操做就比如不少人(实际上这里是线程)在抢活儿干,每次任务都是进行自增操做。可是每次的活儿(就是自增操做)只有一我的(实际上这里是线程)能成功抢到,其余人没抢到只好继续抢下一次机会。并且每一个人(实际上这里是线程)不管抢活儿的能力如何,都要完成1000次的自增,因此早干完(1000次循环)早休息,晚干完晚休息。并且每次使用【for (;;)】抢活,抢不到的话线程就不会跳出【for (;;)】,必定要在里面抢到一次活儿才会跳出来进行下次外层的for循环【for (int k = 0; k < 1000; k++)】。

CAS算法中实际上每一个线程也都足足执行1000次自增,每一个线程也都是没少干活,各个线程也都是在争取先完成本身的1000次自增,而后早点结束本身的1000次自增任务。

CAS中为何不能有两个线程同时抢到自增执行权呢?(以Intel为例)由于CAS最底层依赖的是CPU的cmpxchg原子操做,若是A线程成功的完成了cmpxchg操做(线程A进行cmpxchg操做时硬件机制能保证其余线程没法进行cmpxchg操做),那么内存数据就改变且立刻全部线程均可以看到最新的内存数据了。其余线程再作cmpxchg操做时,发现本身手里的备份数据已经和如今内存的数据不一致了,cmpxchg操做失败,立马进入下一次循环,争取下一次能成功进行cmpxchg操做。

因此CAS最终仍然是依靠原子操做保证每次自增只有一个线程能成功执行,与synchronized不一样的是CAS操做不涉及加锁解锁和线程间的切换。synchronized是在代码层经过synchronized原语自己保证的;而CAS算法的原子操做时依赖于底层CPU硬件的指令(好比Intel中是cmpxchg)保证的。

抽象的说,就是:每次只有一个线程能真正成功的进行CAS操做,而后其余线程都会获取CAS操做失败的信号,获取操做失败信号的线程就会什么也不作,这些线程至关于空转了一回,而后转入本身的下一循环看是否可以成功地进行一次CAS操做。

CounterSynchronizedDetail.java

源码

package volatileTest;


public class CounterSynchronizedDetail {


public int i = 0;


public static void main(String... strings) {

System.out.println("Class Name:"

+ Thread.currentThread().getStackTrace()[1].getClassName());

System.out.println("JDK version:" + System.getProperty("java.version"));


Long s = System.currentTimeMillis();


final CounterSynchronizedDetail t = new CounterSynchronizedDetail();


for (int i = 0; i < 100; i++) {

new Thread() {

public void run() {

for (int j = 0; j < 1000; j++) {

t.increase();

}

}

}.start();

}


while (Thread.activeCount() > 1)

// 保证前面的线程都执行完

Thread.yield();


System.out.println("spend time:" + (System.currentTimeMillis() - s));

System.out.println("result:" + t.i);

}


public synchronized void increase() {

System.out.println(Thread.currentThread().getName());

i++;

}

}

执行结果分析

将打印结果拷贝到notepad中。

从行号能够看到多打印了四行,剩下的100000行都是线程名Thread-XX。说明全部线程一共执行了100000次。

进行计数,Thread-0以下,能够看到是1000次匹配。

Thread-1呢?(注意要选择【全词匹配】)也是1000次匹配,每一个线程都是如此。

因此说虽然线程们争先恐后的抢夺锁的控制权,实际上那个线程都没少干活,抢来抢去都是为了早点完成本身的任务而后结束本身全部的for循环操做。而后呢?若是是线程池,那么线程完成全部任务就休息了,等待下次有任务来再次被调度。若是就是单个线程,那么线程就结束生命周期了。

相关文章
相关标签/搜索