15-JUC(java.util.concurrent)

JUC:

在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架;还提供了用于多线程上下文中的 Collection 实现等。

三个特性:

在并发编程中的三个特性:(1)互斥性(原子性)(2)内存可见性(3)指令重排序

  • volatile 关键字:

volatile:易挥发的,不稳定

volatile 不具备"互斥性";不能保证变量的"原子性";当多个线程进行操作共享数据时,可以保证内存中的数据是可见的,相较于synchronized 是一种较为轻量级的同步策略

  • synchronizedvolatile 的区别:

(1)synchronized 可以保证互斥性,可以实现内存可见性,不能禁止指令重排序。

(2)volatile 不能保证原子性,可以实现内存可见性,禁止指令重排序。

  • 内存可见性问题:

import java.io.IOException;

public class Demo {
	public static void main(String[] args) throws IOException {
		//1.创建线程对象
		MyThread thread = new MyThread();
		thread.start();
		System.out.println("输入任意字符结束子线程:");
		System.in.read();
		thread.flag = true;
		System.out.println(thread.flag);
		System.out.println("主线程结束了");
	}
}

class MyThread extends Thread{
	public boolean flag;//(默认为false)
	
	@Override
	public void run() {
		System.out.println("子线程开始执行");
		while (true) {
			if (flag) {
				break;
			}
		}
		System.out.println("子线程结束了");
	}
}

运行结果:

可以看到程序还在运行,因为子线程并没有结束(子线程的缓存区保存着内存中 flag 的原始数据,并没有访问到主存中已更改的 flag)(为了运行效率,每个线程具备自己的缓存区)

即对于子线程来说,是没有访存的(内存的不可见性)

  • 在线程类的共享资源 flag 上添加 volatile 关键字:
class MyThread extends Thread{
	
	//volatile 去掉子线程的缓存(用内存屏障实现),效率降低,但是解决了内存可见性问题
	public volatile boolean flag;
	
	@Override
	public void run() {
		System.out.println("子线程开始执行");
		while (true) {
			if (flag) {
				break;
			}
		}
		System.out.println("子线程结束了");
	}
}

运行结果:

  • synchronized 实现内存可见性:

用 synchronized也可以实现内存可见性,但效率低。

public class Test {

    public static void main(String[] args) throws IOException {
        ThreadTest2 thread0 = new ThreadTest2("歪比歪比");
        ThreadTest2 thread1 = new ThreadTest2("歪比巴布");
        ThreadTest2 thread2 = new ThreadTest2("玛卡巴卡");
        thread0.start();
        thread1.start();
        thread2.start();
        System.out.println("输入任意字符结束子线程:");
        System.in.read();
        ThreadTest2.flag = true;
        System.out.println(ThreadTest2.flag);
        System.out.println("主线程结束了");
    }

}

class ThreadTest2 extends Thread {
    public static boolean flag;//(默认为false)

    public ThreadTest2() {}

    public ThreadTest2(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println("子线程开始执行");
        while (true) {
            synchronized (this){
                if (flag) {
                    break;
                }
            }
        }
        System.out.println("子线程"+Thread.currentThread().getName()+"结束了");
    }
}

运行结果:

  • 禁止指令重排序:

/*
 * 单例设计模式:
 * 懒汉式:
 * 1.私有化构造方法
 * 2.类内部创建对象
 * 3.添加公开访问方法
 */
public class Demo1 {
	private volatile static Demo1 instance;
	private Demo1() {super();}
	
	public static Demo1 getInstance() {
		if(instance == null) {
			synchronized (Demo1.class) {//此处消耗大,在外层加上判断
				if (instance == null) { //双重检查:double check
					instance = new Demo1();
					/*
					 * 实例化过程:
					 * 1.堆中开辟空间
					 * 2.调用构造方法初始化对象
					 * 3.把对象的地址赋值给变量
					 */
				}
			}
		}
		return instance;
	}
}

 判断同步性能消耗大 --> 双重检查机制 --> 实例化过程并不是原子性 --> JVM优化指令顺序后线程1执行了实例化过程的(1)和(3),但并未执行(2)初始化对象,此时线程2获得CPU,会拿到一个未初始化的对象并返回,出现异常!!!

---->  在对象上添加 volatile 关键字禁止指令重排序

  • 单例的静态内部类写法:
/*
 * 单例设计模式:
 * 静态内部类写法:
 * 1.私有化构造方法
 * 2.创建静态内部类,在静态内部类中创建常量
 * 3.添加公开方法,返回这个对象
 */
public class Demo2 {

	private Demo2() {super();}
	
	/*
	 * 静态内部类不用的时候不会初始化
	 * 1.节省内存空间
	 * 2.解决了线程安全问题
	 */
	private static class SingleTonHolder{
		private static final Demo2 instance = new Demo2();
	}
	
	public static Demo2 getInstance() {
		return SingleTonHolder.instance;
	}

}

此写法在安全的基础上更节省了内存空间,比饿汉式更优秀!

  •  原子性:

  •  i++  的原子性问题:
public class Demo3 {
	
	public static void main(String[] args) {
		int num = 10;
		num++;
		System.out.println(num);
	}

}

 在相应类的字节码(.class)文件夹下运行cmd,用指令javap可查看字节码文件的内容:

(1) i++的操作实际上分为三个步骤: "读‐改‐写";

        i++可拆分为:

        int temp1=i;

        int temp2=temp+1;

        i=temp2;

(2) 原子性: 就是"i++"的"读‐改‐写"是不可分割的三个步骤;

(3) 原子变量: JDK1.5 以后, java.util.concurrent.atomic 包下,提供了常用的原子变量:

        3.1 原子变量中的值,使用 volatile 修饰,保证了内存可见性

        3.2 CAS(Compare‐And‐Swap) 算法保证数据的原子性

public class Demo4 {
	public static void main(String[] args) {
		TreadTest threadTest = new TreadTest();
		for (int i = 0; i < 5; i++) {//一个对象5个线程引用
			new Thread(threadTest).start();
		}
	}
}

class TreadTest implements Runnable{
	private int num = 0;
	
	@Override
	public void run() {
		try {
			Thread.sleep(200);//线程休眠0.2秒,效果更明显
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(++num);
	}
}

运行结果:

  • 3.1使用原子变量
import java.util.concurrent.atomic.AtomicInteger;
public class Demo4 {
	public static void main(String[] args) {
		ThreadTest threadTest = new ThreadTest();
		for (int i = 0; i < 5; i++) {
			new Thread(threadTest).start();
		}
	}
}

class ThreadTest implements Runnable{
	private AtomicInteger num = new AtomicInteger(0);
	
	@Override
	public void run() {
		System.out.println(num.incrementAndGet());//++i;
//		System.out.println(num.getAndIncrement());//i++;
	}
}

运行结果:

  • 3.2 CAS 算法

CAS(Compare-And-Swap) 算法是硬件对于并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问;CAS 是一种无锁的非阻塞算法(属于乐观锁)的实现;

CAS 包含了三个操作数:

        进行比较的旧预估值:A

        需要读写的内存值:V

        将写入的更新值:B

当且仅当 A == V 时, V = B;否则,将不做任何操作,并且这个比较交换过程属于原子操作

  • 模拟CAS算法
//模拟CAS算法
import java.util.Random;
public class Demo5 {
	public static void main(String[] args) {
		CompareAndSwap compareAndSwap = new CompareAndSwap();
		
		for (int i = 0; i < 5; i++) {
			
			new Thread(new Runnable() {
				@Override
				public void run() {
					while (true) {
						int expect = compareAndSwap.getValue();
						boolean b = 
					compareAndSwap.compareAndSet(expect, new Random().nextInt(101));
						System.out.println(b);
						if (b) {
							break;
						}
					}
					
				}
			}).start();
			
		}
	}
}

class CompareAndSwap{
	private int value;//V
	
	public synchronized int getValue() {
		return value;
	}
	//A:expect    B:newValue
	public synchronized boolean compareAndSet(int expect,int newValue) {
		if (expect == value) {
			this.value = newValue;
			System.out.println("赋值成功:"+this.value);
			return true;
		}else {
			return false;
		}
	}
}

运行结果:

  • ABA问题:

在CAS算法中,需要取出内存中某时刻的数据(由用户完成),在下一时刻比较并替换(由CPU完成,该操作是原子的)。这个时间差中,会导致数据的变化。

假设如下事件序列:

        线程 1 从内存位置V中取出A。

        线程 2 从位置V中取出A。

        线程 2 进行了一些操作,将B写入位置V。

        线程 2 将A再次写入位置V。

        线程 1 进行CAS操作,发现位置V中仍然是A,操作成功。

尽管线程 1 的CAS操作成功,但不代表这个过程没有问题——对于线程 1 ,线程 2 的修改已经丢失。

  • 解决 ABA 问题

在多线程环境中,对内存值 V 加上版本戳,在比较时不只比较 A 和 V,也比较版本号,就可做到控制 ABA 问题了

//CAS算法中的 ABA 问题
import java.util.concurrent.atomic.AtomicStampedReference;
public class Demo6 {
	//解决方法:加版本戳,在更改V(内存值)的时候,版本戳+1,通过版本号判断 V 是否变化
	private static AtomicStampedReference<Integer> integer=new AtomicStampedReference<Integer>(0, 0);
	//(0,0) (初始值,版本戳) --> 比较的时候除了比较期望值和原始值,还比较戳
	public static void main(String[] args) throws Exception{
		for(int i=0;i<100;i++) {
			//Thread.sleep(10);
			new Thread(new Runnable() {
				@Override
				public void run() {
					while(true) {
						//1.获取版本号
						int stamp = integer.getStamp();
						//2.获取期望值
						Integer reference = integer.getReference();
						//compareAndSet(期望值,新值,期望戳,新戳)
						boolean b = integer.compareAndSet(reference, reference+1, stamp, stamp+1);
						if(b) {
							System.out.println(reference+1);
							break;
						}
					}
				}
			}).start();
		}
	}
	
}

运行结果:输出唯一的 1-100 ,但不保证输出顺序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值