JUC (java.util.concurerency)
在Java5.0提供了java.util.concurerency(简称JUC)包,在此包中增加了在并发编程中
常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池,异步IO和轻量级任务框架
提供可调的,灵活的线程池,还提供了设计用于多线程上下文的Collection实现等
一、volatile关键字、内存可见性
内存可见性
内存可见性(Memory Vaibility)是指当某个线程正在使用对象状态而另一个线程同时修改状态
需要确保当一个线程修改了对象的状态后,其他线程能够看到发生的状态变化
可见性错误是指当读操作与写操作在不同的线程中运行时,我们无法确保执行读操作的线程能适应地看到其他线程写入的值
有时甚至是根本不可能的事情
我们可以通过同步来保证对象被安全的发布,除此之外我们也可以使用一种更加轻量级的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的基本思路就是,如果这个地址上的和预估值相等,则为其赋予更新值,否则不足任何操作