学习资源整理自:B站《狂神说》
书接上回
父类:Future,对将来的某个事件的结果进行建模
可以用ajax进行理解。
从1.8开始
场景:需要阻塞等待的任务,使用异步,可以让后面的任务继续进行,不出现阻塞,提高系统性能。
java.util.concurrent
Interface Future<V>
参数类型
V - 未来的 get方法返回的结果类型
All Known Subinterfaces:
Response <T>, RunnableFuture <V>, RunnableScheduledFuture <V>, ScheduledFuture <
所有已知实现类:
CompletableFuture , CountedCompleter , ForkJoinTask , FutureTask , RecursiveAction , RecursiveTask , SwingWorker
public static void main(String args[]) throws ExecutionException, InterruptedException {
//执行,不返回,没有返回结果
CompletableFuture<Void> runAsync = CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"==> runAsync");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("111");
System.out.println(runAsync.get());//返回: null
System.out.println("222");
}
console
111
ForkJoinPool.commonPool-worker-1==> runAsync
null
222
public static void main(String args[]) throws ExecutionException, InterruptedException {
// 使用supplyAsync异步,有返回值,供给型函数式接口
CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +"==> supplyAsync");
//int i = 1/0; //产生异常
return "石似心";
});
String result = supplyAsync.whenComplete((s, t) -> {
//当执行结束时
System.out.println("爬梯结束 s=> " + s);// 正常执行完s有结果,异常s为null
System.out.println("爬梯结束 t=> " + t);// 正常执行完t为null,异常t为Throwable
}).exceptionally(t -> {
//当执行异常时(onError)
t.printStackTrace();
System.out.println(t.getMessage());
return "再接再厉";
}).get();
System.out.println("最终结果==> "+result);
}
console
ForkJoinPool.commonPool-worker-1==> supplyAsync
爬梯结束 s=> 石似心
爬梯结束 t=> null
最终结果==> 石似心
java memory model java内存模型,是一个不存在的东西,是一种概念,约定。
关于JMM的同步约定:
1、线程解锁前,必须把共享变量立即刷回主内;
2、线程加锁前,必须读取主存中的最新值到工作内存中;
3、加锁和解锁是通一把锁
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
不允许一个线程将没有assign的数据从工作内存同步回主内存
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
对一个变量进行unlock操作之前,必须把此变量同步回主内存
private static boolean flag=true;
public static void main(String args[]) throws InterruptedException {
new Thread(() -> {
while (flag){
//无限循环
}
}).start();
// 一秒后 主线程修改了flag
TimeUnit.SECONDS.sleep(1);
flag = false;
System.out.println(flag);//打印false
/**
* 程序一直运行
*/
}
引发问题:
当线程A操作过的flag没有写入到主内存中时,线程B将flag更改写入了主内存,此时线程A不知道主线程中的flag已经变更。将会造成线程A的数据紊乱、丢失问题!
Volatile 是java虚拟机提供的轻量级同步机制
volatile有三个特性!!!
1、保证可见性
2、不保证原子性
3、禁止指令重排
//添加volatile关键字
private volatile static boolean flag=true;
public static void main(String args[]) throws InterruptedException {
new Thread(() -> {
while (flag){
//无限循环
}
}).start();
// 一秒后 主线程修改了flag
TimeUnit.SECONDS.sleep(1);
flag = false;
System.out.println(flag);//打印false
/**
* 程序结束
*/
}
原子性:不可分割
/**
* 验证volatile的非原子性
* @author: stone
* @create: 2020-08-25
*/
public class Test03 {
//定义简单变量
private volatile static int num = 0;
//实现加一方法
private static void incr(){
num++;
}
public static void main(String[] args) {
//多线程情况下,共同修改 num ,查看最终结果是否正常
for (int i = 0; i < 10; i++) {
//创建10个线程去跑
new Thread(() -> {
//多次修改num值
for (int j = 0; j < 1000; j++) {
incr();
}
}).start();
}
//10个线程跑完后,输出num
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(num); // 结果 并不是 10*1000
}
}
由此得:volatile并不保证修饰对象的原子性!
引出一个非Synchronized、非Lock的高效方法保证原子性
使用原子类 java.util.concurrent.atomic.AtomicInteger
private static AtomicInteger num = new AtomicInteger(0);
这些类的底层都直接和操作系统挂钩,都是native方法,在内存中修改值,Unsaft类是一个很特殊的存在。
什么是指令重排
程序代码,在计算机中并不一定是按照代码的顺序去执行的。
源代码 --> 编译器优化重排 --> 指令并行可能会重排 --> 内存系统也会重排 --> 执行
举例:
1:int a = 1;
2:int b = 2;
3:a = a + 3;
4:b = a + 4;
在以上代码中,正常的重排是不会影响代码逻辑的,如:第一行和第二行重排对执行结果是没有影响的;
但!当两条以上的线程操作共享数据时:每个线程内的重排就可能会造成不同的结果:
定义:a、b、c、d四个变量都为 0
线程1 | 线程2 |
---|---|
a=b | c=d |
d=2 | b=1 |
若线程1中的c=2重排为第一个执行,则会对线程2的结算结果产生影响:a=1,c=2
volatile禁止指令重排的原理
这里有两个计算机专业的概念:内存屏障、CPU指令
当volatile修饰的代码,会在前后加入一层内存屏障,保证执行顺序是不可更改的。
首先展示三种常规的单例操作
/**
* 单例模式 饿汉模式
* 可能会造成内存浪费
* @author: stone
* @create: 2020-08-25 17:59
*/
public class Hungry {
private Hungry(){
}
//单一不可修改
private final static Hungry hungry = new Hungry();
public Hungry getInstance(){
return hungry;
}
}
/**
* 单例模式 懒汉式
* @author: stone
* @create: 2020-08-25 18:32
*/
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName());
}
private static LazyMan lazyMan;
/**
* 创建对象,在底层为三行代码
* 1、开辟内存空间
* 2、执行构造方法,初始化对象
* 3、对象指向内存空间
* 故此可能发生指令重排 132
* 而第二条线程则误以为lazyMan!=null,将会导致错误
* 所以synchronized保证原子性
*/
public static LazyMan getInstance(){
// DCL懒汉模式 双重检查锁 double check lock。。。
if(lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
public static void main(String args[]){
for (int i = 0; i < 10; i++) {
new Thread(() -> {
LazyMan.getInstance();
}).start();
}
}
}
/**
* 静态内部类实现单例模式
* @author: stone
* @create: 2020-08-25 23:45
*/
public class Holder {
private Holder(){
}
private static class InnerClass{
//在内部类创建对象
private static Holder holder = new Holder();
}
public Holder getInstance(){
return InnerClass.holder;
}
}
懒汉类:ReflectSingle.class
public static void main(String args[]) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//正常获取一个懒汉单例
ReflectSingle lazyMan = ReflectSingle.getInstance();
//获取单例的无参构造器
Constructor<ReflectSingle> declaredConstructor = ReflectSingle.class.getDeclaredConstructor();
//开启 访问private权限
declaredConstructor.setAccessible(true);
ReflectSingle lazyMan2 = declaredConstructor.newInstance();
}
懒汉类升级了构造器,判断私有字段lazyMan,实现三重验证
private ReflectSingle(){
synchronized(ReflectSingle.class){
if(lazyMan!=null){
throw new RuntimeException("禁止重新创建此类");
}
}
}
破坏思路:不是用getInstance方法创建第一个对象,保持lazyMan一直为空
public static void main(String args[]) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//正常获取一个懒汉单例
//ReflectSingle lazyMan = ReflectSingle.getInstance();
//获取单例的无参构造器
Constructor<ReflectSingle> declaredConstructor = ReflectSingle.class.getDeclaredConstructor();
//开启 访问private权限
declaredConstructor.setAccessible(true);
ReflectSingle lazyMan2 = declaredConstructor.newInstance();
ReflectSingle lazyMan3 = declaredConstructor.newInstance();
}
懒汉升级:使用flag标示构造方法只能走一次
private ReflectSingle(){
synchronized(ReflectSingle.class){
if(!prFlag_ssx){
prFlag_ssx = true;
}else{
throw new RuntimeException("禁止重新创建此类");
}
}
}
private static boolean prFlag_ssx = false;
这时只有第一条走完构造器的代码可以获取到对象。
反射破坏:得知标识字段名后,可以强行获取,破坏私有权限,直接修改值
public static void main(String args[]) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
//正常获取一个懒汉单例
//ReflectSingle lazyMan = ReflectSingle.getInstance();
//知道flag字段名后,可以强行获取,破坏私有权限,直接修改值
Field flagField = ReflectSingle.class.getDeclaredField("prFlag_ssx");
flagField.setAccessible(true);
//获取单例的无参构造器
Constructor<ReflectSingle> declaredConstructor = ReflectSingle.class.getDeclaredConstructor();
//开启 访问private权限
declaredConstructor.setAccessible(true);
ReflectSingle lazyMan2 = declaredConstructor.newInstance();
//修改标示,让构造器可以再走一次
flagField.set(declaredConstructor,false);
ReflectSingle lazyMan3 = declaredConstructor.newInstance();
}
枚举在java里就是一个类,其底层也是继承了一个 Enum的类
使用工具反编译枚举类
jad -sjava EnumSingle.class
package com.ssx.single;
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/ssx/single/EnumSingle, name);
}
//得知枚举中其实有一个 使用两个参数的构造方法
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstance()
{
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}
/**
* 枚举本身就是一个单例模式
* 其底层有写禁止反射创建对象的代码
* @author: stone
* @create: 2020-08-26 00:32
*/
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String args[]) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//尝试使用反射破解枚举单例模式
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle enumSingle = declaredConstructor.newInstance();
}
}
结果
Cannot reflectively create enum objects
java在反射的底层禁止了对enum的反射创建
什么是CAS
比较并交换
unsafe:java为C++留的后门,里面全是native方法
由于java无法操作内存,所以java需要调用C++操作内存
public static void main(String args[]){
AtomicInteger atomicInteger = new AtomicInteger(2020);
/**
* 比较与设置
* public final boolean compareAndSet(int expect, int update)
* 比较期望值,如果==则设置为更新值,如果不相等,则返回false
*/
atomicInteger.compareAndSet(2020,2021);
System.out.println(atomicInteger.get());
}
比较当前工作内存中的值和主内存中的值(保证原子性),如果这个值是期望的,那么则执行操作!如果不是则一直循环。
个人理解:计算之前,先获取内存中的值,然后比较,然后计算。原理上有点像乐观锁。
缺点:
1、循环耗时
2、一次性只能保证一个共享变量
3、造成ABA问题
public static void main(String args[]){
//共享变量
AtomicInteger atomicInteger = new AtomicInteger(2020);
//线程2,意料之外的线程操作
atomicInteger.compareAndSet(2020,2021);
System.out.println(atomicInteger.get());
atomicInteger.compareAndSet(2021,2020);
System.out.println(atomicInteger.get());
//线程1,正常业务线程
//这个时候,其实已经不是原来的那个2020了,被替换过
atomicInteger.compareAndSet(2020,2021);
System.out.println(atomicInteger.get());
}
java.util.concurrent.atomic.AtomicStampedReference
引用乐观锁的原理,使用版本号
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* 原子引用解决ABA问题
* @author: stone
* @create: 2020-08-26 13:09
*/
public class TestABA2 {
public static void main(String args[]){
//创建原子引用对象,赋予初始值和初始版本号
//源码:public AtomicStampedReference(V initialRef, int initialStamp)
AtomicStampedReference<Integer> atomic = new AtomicStampedReference<>(1,1);
//使用两个线程模拟ABA现象
//线程1
new Thread(() -> {
Integer stamp = atomic.getStamp();//获取版本号
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 修改值 期望是1 则改为2,期望版本号stamp,新的版本号+1
System.out.println(atomic.compareAndSet(1, 2, stamp, stamp + 1));
System.out.println("线程1= stamp ==》"+atomic.getStamp());
// 修改值 期望是2 则改为1,期望版本号stamp,新的版本号+1
System.out.println(atomic.compareAndSet(2, 1, atomic.getStamp(), stamp + 1));
System.out.println("线程1= stamp ==》"+atomic.getStamp());
}).start();
//线程2
new Thread(() -> {
Integer stamp = atomic.getStamp();//获取版本号
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 修改值 期望是1 则改为2,期望版本号stamp,新的版本号+1
// 由于最初获取的版本号已经被修改,所以这里是set失败的
System.out.println(atomic.compareAndSet(1, 3, stamp, stamp + 1));
System.out.println("线程2= stamp ==》"+atomic.getStamp());
}).start();
/**
* 最终结果:
* true
* 线程1= stamp ==》2
* true
* 线程1= stamp ==》2
* false
* 线程2= stamp ==》2
*/
}
}
题外话,Integer的一个坑
公平锁:先来后到,排队逐个获得锁、施放锁,不可插队
非公平锁:可以插队 java.util.concurrent.locks.ReentrantLock
(递归锁)
锁中包含的锁,会一同获取,需要整个大锁结束,才会施放。
手写自旋锁
/**
* 手写自旋锁
* @author: stone
* @create: 2020-08-26 13:54
*/
public class SpinLock {
//使用原子引用
//无参构造器,初始值为null
AtomicReference<Thread> atomic = new AtomicReference<>();
/**
* 加锁方法
*/
public void lock(){
//如果当前原子引用不是null,则无法退出循环,形成自旋
while (!atomic.compareAndSet(null,Thread.currentThread())){
}
System.out.println(Thread.currentThread().getName()+" -- 获得 了锁 --");
}
/**
* 解锁方法
*/
public void unLock(){
Thread thread = Thread.currentThread();
atomic.compareAndSet(thread,null);
System.out.println(thread.getName()+" -- 释放 了锁 --");
}
}
class Test{
public static void main(String args[]) throws InterruptedException {
SpinLock spinLock = new SpinLock();
new Thread(()->{
try {
spinLock.lock();//获得锁
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinLock.unLock();//施放锁
}
},"石似心").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
try {
spinLock.lock();//获得锁
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinLock.unLock();//施放锁
}
},"周杰伦").start();
}
}
/**
* 模拟死锁场景
* 交叉锁
* @author: stone
* @create: 2020-08-26 14:17
*/
public class DeadLock {
public static void main(String args[]){
//资源锁
Lock lockA = new ReentrantLock();
Lock lockB = new ReentrantLock();
new Thread(new MyThread(lockA,lockB),"石似心").start();
new Thread(new MyThread(lockB,lockA),"悟空").start();
}
}
class MyThread implements Runnable{
//模拟交叉
private Lock lockA;
private Lock lockB;
public MyThread(Lock lockA, Lock lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"获得了lockA ");
lockA.lock();//锁住A
System.out.println(Thread.currentThread().getName()+"尝试获取lockB");
lockB.lock();
//..死锁....这里是进行不下去的
lockA.unlock();
lockB.unlock();
}
}
1、使用jps命令获取进程号
E:\workspace_self\study-juc\study-juc>jps -l
16080 org.jetbrains.kotlin.daemon.KotlinCompileDaemon
13588 org.jetbrains.jps.cmdline.Launcher
1732 sun.tools.jps.Jps
12440 com.ssx.MyLock.DeadLock
8792
2、使用jstack命令查找堆栈信息
E:\workspace_self\study-juc\study-juc>jstack 12440
......
Found one Java-level deadlock:
=============================
"悟空":
waiting for ownable synchronizer 0x00000000db0d6fb8, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "石似心"
"石似心":
waiting for ownable synchronizer 0x00000000db0d6fe8, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "悟空"
......
可以很清楚的看到当前存在一个死锁现象,都各自拥有一个锁,然后等待对方的锁。