【多线程】<JUC>原子操作类

JUC (java.util.concurerency)

在Java5.0提供了java.util.concurerency(简称JUC)包,在此包中增加了在并发编程中

常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池,异步IO和轻量级任务框架

提供可调的,灵活的线程池,还提供了设计用于多线程上下文的Collection实现等

一、volatile关键字、内存可见性

内存可见性

内存可见性(Memory Vaibility)是指当某个线程正在使用对象状态而另一个线程同时修改状态

需要确保当一个线程修改了对象的状态后,其他线程能够看到发生的状态变化

可见性错误是指当读操作与写操作在不同的线程中运行时,我们无法确保执行读操作的线程能适应地看到其他线程写入的值

扫描二维码关注公众号,回复: 1774693 查看本文章

有时甚至是根本不可能的事情

我们可以通过同步来保证对象被安全的发布,除此之外我们也可以使用一种更加轻量级的volatile变量

volatile关键字

Java提供了一种稍弱的同步机制,即volatile变量,用来确保将变量更新操作通知道其他线程,【可以保证内存中的数据可见】

可见将volatile看做一个轻量级的话,但是是又与之不同

·对于多线程,不是一种互斥关系

·不能保证变量状态的“原子操作类”

public class ThreadDemoA implements Runnable{

         private volatile boolean flag = false;

         @Override

         public void run() {

                   try {

                            Thread.sleep(200);

                   } catch (InterruptedException e) {

                            e.printStackTrace();

                   }

                   flag=true;

                   System.out.println("flag="+isFlag());

         }

         public Boolean isFlag(){

                   return flag;

         }

         public void setFlag(boolean flag) {

                   this.flag = flag;

         }

        

        

}

public class TestDemoA {

         public static void main(String[] args) {

                   ThreadDemoA tdA = new ThreadDemoA();

                   new Thread(tdA).start();

                   while(true){

                            if(tdA.isFlag()){

                                     System.out.println("——————————————");

                                     break;

                            }

                   }

         }

}结果:

——————————————

flag=true

此时如果不加volatile关键字,只会输出它,且程序死循环

flag=true

volatile关键字保证了flag变量对所有线程内存条件,所以当flag变量值变化后,主线程while循环中检测到,打印后程序执行完成

退出;如果不加volatile关键字,主线程将一直while死循环,不退出

二、原子变量、CAS

原子变量:JDK1.5后juc包中atomic类的小工具包,支持在单个变量上解除锁的线程安全编程,

包下提供了常用的原子变量:

·AtomicBoolean

·AtomicInteger

·AtomicLong

·AtomicReference

·AtomicArray

·AtomicLongrray

·AtomicMarkableReference

·AtomicReferenceArray

·AtomicStampedReference

【1】类中的变量都是volatile类型:保证内存的可见型

【2】使用CAS算法:保证数据的原子性

CAS(Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令

用于管理对共享数据的并发访问

CAS包含三个操作数:

内存值 V

预估值 A

更新值 B

当且仅当V==A时,B的值才更新给A,否则不做任何操作

原子操作:

原子操作是不需要synchronized

这是多线程中被各大企业广泛使用的原子操作

所谓原子操作就是指:不会被线程调度机制打断的操作

特点:这种操作一旦开始,就一直运行到结束,中间不会有任何context switch

(切换到拎一个线程)

如果这个操作所处的层的更高层不能发现其内部实现与结构,那么这个操作是一个原子操作

原子操作可以是一个步骤,也可以是多个操作步骤,但其顺序不能改变不能被打乱,也不可以

被切割而只执行其中的一部分

通常所说的原子操作类包括对非long和double型的primitve进行赋值,以及返回两者之外的primitve

之所以要把他们排除在外,会因为他们比较大,而JVM的设计规范又没有要求读操作和赋值操作必须是原子操作

首先处理器会自动保证基本的内存操作的原子性,处理器保证从系统内存当中读取或者写入一个字节是原子的,意思:当一个处理器

读取一个字节时,其他处理器不能访问这个字节的内存地址

奔腾6和最新的处理器能自动保证单处理器对同一个缓存里进行16/32/64位的操作原子的

但是复杂的内存操作处理器不能自动保证原子性,比如:跨多少个缓存行

原子性:就是不会有中间的状态存在,要么什么的都没改变,要么全部改变,不会有部分改变。

每个东西的出现都不会是无缘无故的,Atomic类的出现是为了解决一些常见类型变量的原子操作而提出的

范例:观察以下代码

package cn.test;

public class AtomicExample implements Runnable{

         private int value;

         @Override

         public void run() {

                   try {

                            Thread.sleep(7);

                   } catch (InterruptedException e) {

                            e.printStackTrace();

                   }

                   System.out.println(getValue());

         }

         public intgetValue(){

                   return value++;

         }

}

package cn.test;

public class AtomicProblem {

         public static void main(String[] args) {

                   AtomicExample  atomicExample = new AtomicExample();

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

                            new Thread(atomicExample).start();

                   }

         }

}结果:

0

1

2】

4

3

2】

5

6

7

8

【以上程序在经过多次运行之后,居然出现两个2】、

用volatile能解决吗?

范例:观察以下代码

package cn.test;

public class AtomicExample implements Runnable{

         //在这里加了volatile

         private volatile int value;

         @Override

         public void run() {

                   try {

                            Thread.sleep(7);

                   } catch (InterruptedException e) {

                            e.printStackTrace();

                   }

                   System.out.println(getValue());

         }

         public intgetValue(){

                   return value++;

         }

}结果:

0

0

0

1

4

3

2

5

6

7

【以上代码出现了多个0】有时候会正常,有时候就会出问题

分析产生原因

为什么volatile没法解决

我们先想想,volatile特别是操作的时候,就是写完必须刷新回主存,所以volatile没有效果,此方法的解决途径之一————加锁

非锁的解法

非锁的解法最终还是我们自己来写,是CPU底层实现的,在我们这里看起来就是没锁

就要提高CAS比较交换策略,当前处理器都支持CAS,每一个CAS操作过程都包含三个运算符:

一个内存地址V,一个预估值(期望值)A和一个更新值(新值)B

操作的时候如果这个地址上存放的值等于这个预估值的值A,则将地址上的值赋值为更新值B没不作任何操作

CAS的基本思路就是,如果这个地址上的和预估值相等,则为其赋予更新值,否则不足任何操作

猜你喜欢

转载自www.cnblogs.com/laipan/p/9236716.html