一)内存可见性:Volatile 关键字
多个线程操作共享数据时数据不可见的现象,多个线程在读取共享数据时是将数据读入自己的缓存中去,所以线程之间存在数据不可见现象,也就是说在多个线程读取数据的同时无法保证数据在
1-1:
Volatile 关键字是此共享数据变为在主存中刷新,在实现线程更改共享数据时会及时的刷新主存中的数据,保证操作共享数据的可见性。
但是被volatile关键字修饰的变量不能被重排序
1-2:相较于Synchroinzed关键字保证线程的安全不具备:
1)不具备互斥性
Synchroinzed是保证线程安全的在一个线程拿到同步锁时其他线程无法对共享数据进行操作,而Volatile则不具备这种功能,它只保证数据在主存中刷新而不保证数据的操作是一个线程的操作。
2)不具备原子性
原子性:不可分割的意思
也就是无法保证数据的安全性,也就是存在线程安全问题
图为线程操作共享数据,数据存于主存线程在对共享数据进行操作的时候不会再将数据读取到自己的缓存中来,而是直接对主存的中的数据进行操作病时刻对其进行刷新,线程也都自己单独的存储区域,为缓存,当线程需要对共享数据进行操作时,会先将主存中的共享数据读到自己的缓存中来。在自己的缓存中对数据进行操作。但是线程的操作是cpu来回切换的如果操作的数据还未返回践行主存的刷新就会对数据的即是更新出现问题
二)原子性问题
1-1:i++的原子性问题:
i++的实际操作分为3个步骤“读-改-写”
基础时对i++和++i的区别是
i++:现使用变量然后在对自身加1
++i:现对自身加一在使用变量
计算机底层对递增的算法是,先读取i的值存到一个临时变量中,在对这个临时变量进行加一的操作。
此时这个变量的不具备线程中安全的性质
int i = 10;
i=i++;
底层的操作是
int temp = i
i = i+ 1;
i=temp;
演示原子问题
/**
* @project_name:juc
* @date:2019/9/24:16:30
* @author:shinelon
* @Describe:演示的volatile的原子性问题
*/
public class TestAtomicDemo
{
public static void main(String[] args)
{
AtomicDemo atomicDemo = new AtomicDemo();
for (int i = 0;i<10;i++){
new Thread(atomicDemo).start();
}
}
}
class AtomicDemo implements Runnable{
private volatile int num = 0;
@Override
public void run()
{
try
{
Thread.sleep(2000);
} catch (InterruptedException e)
{
}
System.out.println(Thread.currentThread().getName()+":"+getNum());
}
public int getNum(){
return num++;
}
}
结果发现使用volatile关键字修饰的依然无法保证操作的共享数据的安全问题。
1-2:原子变量
原子变量就是保证这个数据不会被分割:
在JDK1.5后的java.util.concurrent.atomic包下提供了常用的原子变量具体的API可参考JDK1.5以上的帮助文档
首先在在原子变量的第一个要素就是必须是
1)Volatile修饰过的,保证内存的可见性
2)原子变量采用CAS算法
CAS(Compare-And-Swap)保证数据的原子性 (比较并更新)
CAS算法是硬件对并发操作共享数据的支持
CAS 包括三个操作;
内存值 V
预估值 A
更新值 B
当且仅当V==A 是 V=B
具体的原理:
在对原子性数据的操作时,会进行三个操作(准确来讲是2个),
1.当多线程执行这个共享数据时,会将此数据进行读取到缓存中,对其进行操作,完毕后再对其进行主存的数据进行更新时,会在此读取内存的中的值,如果在此读取的值和原先第一次读取的值相等时才会进行更新操作,否则将什么都不做。
2:而相较于Synchroinzed同步锁是效率很大的,
因为在Synchroinzed中如果一个线程拿到了同步锁其他的线程是无法在进行操作的,如果当前线程操作失败,就会放弃CPU的执行权,但是原子变量则不会而是会继续去尝试修改。
public class TestAtomicDemo
{
public static void main(String[] args)
{
AtomicDemo atomicDemo = new AtomicDemo();
for (int i = 0;i<10;i++){
new Thread(atomicDemo).start();
}
}
}
class AtomicDemo implements Runnable{
AtomicInteger num = new AtomicInteger(0);
@Override
public void run()
{
try
{
Thread.sleep(2000);
} catch (InterruptedException e)
{
}
System.out.println(Thread.currentThread().getName()+":"+getNum());
}
public int getNum(){
return num.getAndIncrement();
}
}
2)模拟CAS算法
public class TestCAS
{
public static void main(String[] args)
{
final CompareAndSwap cas = new CompareAndSwap();
for (int i = 0;i<10;i++){
new Thread(new Runnable(){
@Override
public void run()
{
int exp = cas.get(); //每次线程获取内存值
boolean b = cas.compareAndSet(exp, (int) Math.random() * 101);
System.out.println(b);
}
}).start();
}
}
}
class CompareAndSwap{
//内存值
private int value;
/**
* 过去内存中的值
* @return
*/
public synchronized int get(){
return value;
}
/**
*比较值的方法
* @param estimate 预估值
* @param newValue 更新值
* @return
*/
public synchronized int compareAndSwap(int estimate,int newValue){
//将内存值赋值给oldValue变量
int oldValue = value;
if (oldValue == estimate)
{//如果是内存值和与估值相等的时候则改变内存中的值
this.value = newValue;
}
return oldValue;
}
/**
* 设置值的方法
* @param estimate 目标值
* @param newValue 新的值
* @return
*/
public synchronized boolean compareAndSet(int estimate,int newValue){
//如果预估值等于旧值则代表修改成功
return estimate == compareAndSwap(estimate, newValue);
}
}