参考网页
主要参考
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.CounterIPlusPlus
JDK version:1.7.0_79
spend time:22
result:99367
运行结果2
Class Name:volatileTest.CounterIPlusPlus
JDK version:1.7.0_79
spend time:39
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循环操作。然后呢?如果是线程池,那么线程完成所有任务就休息了,等待下次有任务来再次被调度。如果就是单个线程,那么线程就结束生命周期了。