package com.hcx.myvolatile;public class Demo {public static void main(String[] args) {MyThread1 t1 = new MyThread1();t1.setName("小路同学");t1.start();MyThread2 t2 = new MyThread2();t2.setName("小皮同学");t2.start();}
}
package com.hcx.myvolatile;public class Money {public static int money = 100000;
}
package com.hcx.myvolatile;public class MyThread1 extends Thread{@Overridepublic void run() {while(Money.money == 100000){}System.out.println("结婚基金已经不是十万了");}
}
public class MyThread2 extends Thread {@Overridepublic void run() {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}Money.money = 90000;}
}
运行结果
以上案例出现的问题:
当线程2修改了共享数据时,B线程没有及时获取到最新的值,如果还在使用原先的值,就会出现问题。
package com.hcx.myvolatile;public class Demo {public static void main(String[] args) {MyThread1 t1 = new MyThread1();t1.setName("小路同学");t1.start();MyThread2 t2 = new MyThread2();t2.setName("小皮同学");t2.start();}
}
package com.hcx.myvolatile;public class Money {public static volatile int money = 100000;
}
package com.hcx.myvolatile;public class MyThread1 extends Thread{@Overridepublic void run() {while(Money.money == 100000){}System.out.println("结婚基金已经不是十万了");}
}
package com.hcx.myvolatile;public class MyThread2 extends Thread {@Overridepublic void run() {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}Money.money = 90000;}
}
synchronized解决:
代码实现
package com.hcx.myvolatile2;import com.hcx.myvolatile.MyThread2;public class Demo {public static void main(String[] args) {MyThread1 t1 = new MyThread1();t1.setName("小路同学");t1.start();MyThread2 t2 = new MyThread2();t2.setName("小皮同学");t2.start();}
}
package com.hcx.myvolatile2;public class Money {public static Object lock = new Object();public static int money = 100000;
}
package com.hcx.myvolatile2;public class MyThread1 extends Thread{@Overridepublic void run() {while(true){synchronized (com.hcx.myvolatile2.Money.lock){if(Money.money!=100000){System.out.println("结婚基金已经不是十万了");break;}}}}
}
package com.hcx.myvolatile2;import com.hcx.myvolatile.Money;public class MyThread2 extends Thread {@Overridepublic void run() {synchronized (com.hcx.myvolatile2.Money.lock) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}Money.money = 90000;}}
}
package com.hcx.threadatom;public class AtomDemo {public static void main(String[] args) {MyAtomThread myAtomThread = new MyAtomThread();for (int i = 0;i < 100; i++) {new Thread(myAtomThread).start();}}
}
package com.hcx.threadatom;public class MyAtomThread implements Runnable {private volatile int count = 0;//送冰淇淋的数量@Overridepublic void run() {for (int i = 0;i < 100;i++) {//1.从共享数据中读取数据到本线程中//2.修改本线程栈中变量副本的值//3.会把本线程栈中变量副本的值赋值给共享数据。count++;System.out.println("已经送了"+count +"个冰淇淋");}}
}
运行结果
思考:100个线程执行100次,最后应该打印结果为10000,为什么运行结果是9999呢?
综上,我们可以得出volatile关键字:
package com.hcx.threadatom2;import com.hcx.threadatom.MyAtomThread;public class AtomDemo {public static void main(String[] args) {MyAtomThread myAtomThread = new MyAtomThread();for (int i = 0;i < 100; i++) {new Thread(myAtomThread).start();}}
}
package com.hcx.threadatom2;public class MyAtomThread implements Runnable {private volatile int count = 0;//送冰淇淋的数量private Object lock = new Object();@Overridepublic void run() {for (int i = 0;i < 100;i++) {//1.从共享数据中读取数据到本线程中//2.修改本线程栈中变量副本的值//3.会把本线程栈中变量副本的值赋值给共享数据。synchronized (lock) {count++;System.out.println("已经送了"+count +"个冰淇淋");}}}
}
package com.hcx.threadatom3;import java.util.concurrent.atomic.AtomicInteger;public class MyAtomIntergerDemo1 {//public AtomicInteger(): 初始化一个默认值为0的原子型Integer//public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integerpublic static void main(String[] args) {AtomicInteger ac = new AtomicInteger();System.out.println(ac);AtomicInteger ac2 = new AtomicInteger(10);System.out.println(ac2);}
}
package com.hcx.threadatom3;import java.util.concurrent.atomic.AtomicInteger;public class MyAtomIntergerDemo2 {// int get(): 获取值// int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。// int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。// int addAndGet(int data): 以原子方式将参数与对象中的值相加,并返回结果。// int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。public static void main(String[] args) {
// AtomicInteger ac1 = new AtomicInteger(10);
// System.out.println(ac1.get());
//
// AtomicInteger ac2 = new AtomicInteger(10);
// int andIncrement = ac2.getAndIncrement();
// System.out.println(andIncrement);
// System.out.println(ac2.get());// AtomicInteger ac3 = new AtomicInteger(10);
// int incrementAndGet = ac3.incrementAndGet();
// System.out.println(incrementAndGet);// AtomicInteger ac4 = new AtomicInteger(10);
// int i = ac4.addAndGet(20);
// System.out.println(i);
// System.out.println(ac4.get());AtomicInteger ac5 = new AtomicInteger(10);int andSet = ac5.getAndSet(20);System.out.println(andSet);System.out.println(ac5.get());}
}
AtomicInteger原理:自旋锁+CAS算法
CAS算法:
我们来解释一下,首先还是一个堆内存,堆内存中有共享数据值为100,然后我开启了两个线程,一个线程为A线程,另一个线程为B线程,现在A线程跟B线程想要进行的事情是要将共享数据里边的值进行自增,也就是我们最后要把共享数据的值变成102,共享数据100相当于CAS算法中的内存值,假设A线程先抢到CPU的执行权,它接下来需要将内存值100读到自己的线程栈里边,读过来以后存到自己的变量副本中,此时这个值为旧的预期值,现在线程A需要进行自增, 是不是应该将101覆盖给了变量副本,那么变量副本里边现在变成了101,这个时候变量副本里边的值就是CAS算法中的要修改的值,此时B线程抢到了CPU的执行权,它同样将100读到变量副本当中,那么B线程,也有一个旧的预期值,然后线程B也进行了自增,将101覆盖给了变量副本,此时,B线程中要修改的值也为101,现在两个线程的自增都做完了,现在需要将101写到共享数据里面了,假设A线程先写, 它发现A里边的旧的预期值==内存值,表示这个内存值没有被其他线程操作过,所以就将A线程的101写到共享数据修改成功,我们B线程也需要往共享区域写,它发现旧的预期值!=内存值,表示有可能共享数据已经被其他线程操作过了,所以这个时候它修改失败,它需要将现在最新的内存值101再次读到自己的变量副本中,这个时候线程B的旧的预期值就变成101了,然后进行自增,并将102覆盖给变量副本,102就是要修改的值,然后将102写到堆内存的共享数据中。它发现第二次旧的预期值=内存值,所以修改成功。
package com.hcx.threadatom4;import com.hcx.threadatom.MyAtomThread;public class AtomDemo {public static void main(String[] args) {MyAtomThread myAtomThread = new MyAtomThread();for (int i = 0;i < 100; i++) {new Thread(myAtomThread).start();}}
}
package com.hcx.threadatom4;import java.util.concurrent.atomic.AtomicInteger;public class MyAtomThread implements Runnable {
// private volatile int count = 0;//送冰淇淋的数量
// private Object lock = new Object();AtomicInteger ac = new AtomicInteger(0);@Overridepublic void run() {for (int i = 0;i < 100;i++) {//1.从共享数据中读取数据到本线程中//2.修改本线程栈中变量副本的值//3.会把本线程栈中变量副本的值赋值给共享数据。
// synchronized (lock) {
// count++;int count = ac.incrementAndGet();System.out.println("已经送了"+count +"个冰淇淋");
// }}}
}
运行结果
源码解析
本篇文章介绍了 volatile 关键字的作用,通过一个用 volatile 关键字解决不了的送冰淇淋问题引出原子性的概念,介绍了JDK1.5使用原子的方式更新整数类型-AtomicInteger,并对AtomicInteger进行了内存分析和源码分析,最后还介绍了 synchronized和CAS的相同点与不同点,引出了悲观锁和乐观锁的概念。
更新不易,希望大家多多支持!!