1.volatile关键字
<1>作用:强制线程每次在使用的时候,都会看一下共享区域最新的值[用于提供线程安全]
<2>与synchronized的区别和联系:
把代码块声明为 synchronized,有两个重要后果,通常是指该代码具有 原子性(atomicity)和 可见性(visibility);
volatile变量具有 synchronized 的可见性特性,但是不具备原子性。这就是说线程能够自动发现 volatile 变量的最新值
<3>使用条件
您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
a.对变量的写操作不依赖于当前值。
b.该变量没有包含在具有其他变量的不变式中
注:【单独使用 volatile 还不足以实现计数器、互斥锁或任何具有与多个变量相关的不变式的类(例如 “start <=end”)】
<4>实现场景【结婚基金问题】
//volatile关键字:强制线程每次在使用的时候,都会看一下共享区域最新的值
public class Demo1 {public static void main(String[] args) {//结婚基金问题Girl girl = new Girl();Boy boy = new Boy();girl.start();boy.start();}
}//Boy类
public class Boy extends Thread {@Overridepublic void run() {try {Thread.sleep(10);Money.money = 90000;} catch (InterruptedException e) {e.printStackTrace();}}
}//Girl类
public class Girl extends Thread{@Overridepublic void run() {while (true){while (Money.money==100000){}System.out.println("基金不是十万了");break;}}
}//Money
public class Money {//1.volatile关键字:强制线程每次在使用的时候,都会看一下共享区域最新的值public static volatile int money=100000;//2.同步代码块/*public static int money=100000;public static final Object lock =new Object();*/
}打印结果:
--------------------------------------------------------------------------------
基金不是十万了
2.AtomicInteger[原子类]的原子性
<1>概念
原子性指意思是多个操作是不可分割的原子项,他们要么同时成功,要么同时失败
<2>原理
主内存【堆】,线程本地内存【栈】
自增操作,count++为例,其实底层是要完成三个步骤(这三个步骤是不可分割的)
1.从主内存拷贝一个变量副本到线程本地内存
2.对变量副本进行操作
3.把变量副本的值写回主内存
但是由于CPU的随机性,可能一个线程没有完成这个三个步骤,执行权被其他线程抢走了,就破坏了原子性。
<3>AtomicInteger[原子类] 方法
//AtomicInteger[原子类] 方法
public class Demo2 {public static void main(String[] args) {//构造方法1 public AtomicInteger() 空参构造,初始值默认为0//构造方法2 public AtomicInteger(int number) 初始值默认为int//int get():获取值AtomicInteger ac = new AtomicInteger(10);System.out.println(ac.get());//get返回的值可再进行其他操作//int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增前的值。System.out.println(ac.getAndIncrement());System.out.println(ac);System.out.println("----------------------------------------");//int incrementAndGet():以原子方式将当前值加1,注意,这里返回的是自增后的值。AtomicInteger ac2 = new AtomicInteger(10);System.out.println(ac2.incrementAndGet());//int addAndGet(int data):以原子方式将参数与对象中的值相加,并返回结果。System.out.println(ac2.addAndGet(10));//int getAndSet(int value):以原子方式设置为newValue的值,并返回旧值。System.out.println(ac2.getAndSet(30));//查看新值System.out.println(ac2);}
}打印结果:
--------------------------------------------------------------------------------
10
10
11
----------------------------------------
11
21
21
30
<4>AtomicInteger保证线程安全【小男孩送冰淇淋案例】
public class Demo1 {public static void main(String[] args) {MyAtomThread myAtomThread = new MyAtomThread();for (int i = 0; i < 100; i++) {new Thread(myAtomThread).start();}}
}//具体实现类
public class MyAtomThread implements Runnable {private int count =0;//记录送冰淇淋的数量@Overridepublic void run() {for (int i = 0; i < 100; i++) {//1,从共享数据中读取数据到本线程栈中//2,修改本线程栈中变量副本的值//3,会把本线程栈中变量副本的值赋值给共享数据count++;System.out.println("已经送了"+count+"冰淇淋");}}
}打印结果:
--------------------------------------------------------------------------------
已经送了1冰淇淋
......
已经送了9999冰淇淋
已经送了10000冰淇淋
<5>AtomicInteger原子性原理 :CAS算法和自旋
先获取主内存变量值
// 在修改共享数据的时候,把原来的旧值记录下来了。
// 如果现在内存中的值跟原来的旧值一样[compareAndSet],证明没有其他线程操作过内存值,则修改成功。
compareAndSet【比较内存中的值,旧值是否相等,如果相等就把修改后的值写到内存中,返回true,表示修改成功】
// 如果现在内存中的值跟原来的旧值不一样了[compareAndSet],证明已经有其他线程操作过内存值了。
// 则修改失败,需要获取现在最新的值,再次进行操作,这个重新获取就是自旋。
3.悲观锁【synchronized】和乐观锁【CAS】
<1>相同点:在多线程情况下,都可以保证共亨数据的安全性。
<2>不同点:
synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每次操作共享数据之前,都会上锁。(悲观锁)
cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。
如果别人修改过,那么我再次获取现在最新的值。
如果别人没有修改过,那么我现在直接修改共享数据的值。(乐观锁)
【注:synchronized和volatile都不具备原子性】