1、Java JUC简介
在 Java 5.0 提供了 java.util.concurrent (简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中的 Collection 实现等。
2、volatile关键字--内存可见性
下面由一段程序引出volatile关键字和内存可见性问题
package com.juc;
/**
*
* 一、volatile关键字:
*
*/
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while(true)
{
if(td.isFlag())
{
System.out.println("-----------------------");
break;
}
}
}
}
class ThreadDemo implements Runnable{
private 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;
}
}
以上程序执行结果如下,那么就会发现,当线程td修改了flag的值为true、,但是主线程里面的while循环仍然没有退出,说明读取到的flag还是false,那么就相应的提出了内存可见性问题,当多个线程操作共享数据时,彼此时不可见的。
当我们程序运行,jvm会为执行任务每一个线程分配一个独立的缓存,用于提高效率
td线程负责修改数据:
td线程修改flag时,首先回从主存拷贝一份数据flag=flase到自己的独立的缓存空间里面,然后修改flag的为true,最后将flag的值写入内存
main线程负责读取数据
读取数据也是一样,读取主存的flag=false到自己独立的缓存空间里面,然后进行while循环,对flag的值进行判断。while调用相对底层代码,执行效率非常高,以至于主线程无法再次从主存里面读取数据
那么就有了内存可见性问题,当多个线程操作共享数据时,彼此时不可见的
那么以上问题如果可以让主线程每次都从主存里面读取数据,是不是就能退出循环呢,
那么这个问题可以使用synchronized 同步锁来解决这个问题,保证每次都会刷新主存,去主存里面读取数据
执行结果如下,说明每次都会从主存读取数据
但是使用了同步锁,效率大大的降低,多个线程进来的时候,还需要判断锁,当有线程使用的时候,还会造成阻塞问题,,那么这会就有了volatile关键字,
当多个线程进行操作共享数据时,可以保证内存中的数据可见。 可以理解为 他们进行操作直接在主存中进行,,每次读取和写都是在主存中进行。
那么在flag前面加上 volatile关键字的时候,执行结果如下
volatile关键字相较于synchronized是一种j较为轻量级的同步策略
volatile 不具备“互斥性” synchronized是互斥锁,当有一个线程获取到这个锁的时候,其他线程就会发生阻塞
volatile 不能保证变量的原子性,原子性意味着不可分割。
三、原子性操作解释
例如 i++; 这个操作,它不是一个原子性操作,在实际执行时需要三步操作“读-改-写”:
package com.juc;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
* i++的原子性问题: i++的操作实际上分为三个步骤的:读-改-写
* int i = 10;
* i = i++;
* int temp = i;
* i = i+1;
* i = temp;
*
*/
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for(int i = 0;i<10;i++)
{
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable
{
private volatile int serialNumber = 0;
//private AtomicInteger serialNumber = new AtomicInteger(0);
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+getSerialNumber());
}
public int getSerialNumber() {
//return serialNumber.getAndIncrement();
return serialNumber++;
}
}
当线程1读取sn=0到自己的缓存空间中,进行的了加加操作,当没有写回到主存的时候,线程2也读取了sn=0;那么当线程1写回到主存打印出sn=1,当线程2也写回到主存也会打印出sn=1,
四、原子变量
1、类的小工具包,支持在单个变量上解除锁的线程安全编程。事实上,此包中的类可将 volatile 值、字段和数组元素的概念扩展到那些也提供原子条件更新操作的。
2、类 AtomicBoolean、AtomicInteger、AtomicLong 和 AtomicReference 的实例各自提供对相应类型单个变量的访问和更新。每个类也为该类型提供适当的实用工具方法。
3、AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray 类进一步扩展了原子操作,对这些类型的数组提供了支持。这些类在为其数组元素提供 volatile 访问语义方面也引人注目,这对于普通数组来说是不受支持的。
4、核心方法:boolean compareAndSet(expectedValue, updateValue)
5、java.util.concurrent.atomic 包下提供了一些原子操作的常用类:
AtomicBoolean 、AtomicInteger 、AtomicLong 、 AtomicReference
AtomicIntegerArray 、AtomicLongArray
AtomicMarkableReference
AtomicReferenceArray
AtomicStampedReference
6、原子变量的特征
1、volatile 保证内存可见性
2、CAS(compare-and-swap) 算法保证数据的原子性
package com.juc;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
* i++的原子性问题: i++的操作实际上分为三个步骤的:读-改-写
* int i = 10;
* i = i++;
* int temp = i;
* i = i+1;
* i = temp;
*
*/
public class TestAtomicDemo {
public static void main(String[] args) {
Ato