JUC 学习入门(二)

1、ForkJoin

  • 将大任务分层若干个小任务,并行执行,结果继续合并。

  • 每个任务维护一个双端队列,执行速度快的分任务可以偷窃其他分任务的任务,叫做工作窃取

  • 任务类

任务类继承RecursiveTask<Long>,泛型为返回结果类型

public class ForkJoinDemo extends RecursiveTask<Long> {
    private Long start; // 1
    private Long end; // 1990900000
    // 临界值
    private Long temp = 10000L;// 临界值
    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }
    // 计算方法
    @Override
    protected Long compute() {
        if ((end-start)<temp){
            Long sum = 0L;
            for (long i = start; i <= end; i++) {
                sum+=i;
            }
            return sum;
        }else { // forkjoin 递归
            long middle = (start + end) / 2; // 中间值
            
            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
            task1.fork(); // 拆分任务,把任务压入线程队列
            
            ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
            task2.fork(); // 拆分任务,把任务压入线程队列
            
            return task1.join() + task2.join();//收集结果
        }
    }
}
  • 调用任务方法
public static void main(String[] args) throws ExecutionException,InterruptedException {
    test1(); // 24203
    test2(); // 1953
    test3(); // 1187
}

// 普通计算
public static void test1(){
    Long sum = 0L;
    long start = System.currentTimeMillis();
    
    for (Long i = 1L; i <= 10_0000_0000; i++) {
        sum += i;
    }
    
    long end = System.currentTimeMillis();
    System.out.println("sum="+sum+" 时间:"+(end-start));
}

// ForkJoin计算
public static void test2() throws ExecutionException, InterruptedException {
    long start = System.currentTimeMillis();
    
    ForkJoinPool forkJoinPool = new ForkJoinPool();
    ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
    ForkJoinTask<Long> submit = forkJoinPool.submit(task);// 提交任务
    Long sum = submit.get();
    
    long end = System.currentTimeMillis();
    System.out.println("sum="+sum+" 时间:"+(end-start));
}

// Stream并行流 
public static void test3(){
    long start = System.currentTimeMillis();

    long sum = LongStream.rangeClosed(0L,10_0000_0000L).parallel().reduce(0, Long::sum);
    
    long end = System.currentTimeMillis();
    System.out.println("sum="+"时间:"+(end-start));
}

2、异步回调

与AJAX类似,异步执行任务。回调返回和失败

  • 异步回调,无返回值
public static void main(String[] args) throws ExecutionException, InterruptedException {
    CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
        System.out.println(Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    System.out.println(runAsync.get());//回调阻塞,无返回null
    System.out.println("=====main====");
}
  • 异步回调,有返回值
public static void main(String[] args) throws ExecutionException, InterruptedException {
    CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName());
        int i=10/0;
        return 1024;
    });//异步任务

    Integer integer = supplyAsync.whenComplete((t, u) -> {
        System.out.println("u=>" + u);//异步任务返回异常信息
        System.out.println("t=>" + t);//无异常正常返回,有异常为null
    }).exceptionally((e) -> {//处理异常
        System.out.println("e=>" + e);//异常信息
        return 2048;//异常统一返回
    }).get();

    System.out.println(integer);
}

3、JMM

Java内存模型

  1. 线程解锁前,必须把共享变量立刻刷回主存
  2. 线程加锁前,必须读取主存中的最新值到工作内存中!
  3. 加锁和解锁是同一把锁

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的

在这里插入图片描述

4、Volatile

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排
  • 保证可见性

主存中的数据被某一条线程改变时,其他线程会接到通知

private static int num=0;//主存数值
public static void main(String[] args) throws InterruptedException {
    new Thread(()->{
        while(num==0){//线程的num为0
            //业务
        }
    }).start();

    TimeUnit.SECONDS.sleep(2);

    num=1;//主线程修改num对线程不可见
    System.out.println("====main====");//线程死循环
}

private static volatile int num=0;//volatile关键字使得num改变时线程可知
public static void main(String[] args) throws InterruptedException {
    new Thread(()->{
        while(num==0){//线程的num为0
            //业务
        }
    }).start();

    TimeUnit.SECONDS.sleep(2);

    num=1;
    System.out.println("====main====");
}
  • 不保证原子性

原子性 : 不可分割

线程A在执行任务的时候,不能被打扰的,也不能被分割。要么同时成功,要么同时失败。

private volatile static int num = 0;
public static void sum(){
    num++;
}
public static void main(String[] args) throws InterruptedException {
    for (int i = 0; i < 20; i++) {//20条线程
        new Thread(()->{//线程内加1000次
            for (int j = 0; j < 1000; j++) {
                sum();
            }
        }).start();
    }

    while (Thread.activeCount()>2){//等待线程执行完毕
        Thread.yield();
    }

    System.out.println(Thread.currentThread().getName()+"num=>"+num);//19196

}
#字节码
public static void sum();#sum方法 在操作时不是一个整体,在执行时其他线程可干预
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                 
         3: iconst_1   	#取到值
         4: iadd		#加一
         5: putstatic     #2                  
         8: return
  • java.util.concurrent.atomic保证操作原子性
private volatile static AtomicInteger num = new AtomicInteger();//原子操作数

public static void sum(){
    num.getAndIncrement();//原子操作加1
}

#字节码
public static void sum();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #2                  
         3: invokevirtual #原子操作加1                 
         6: pop
         7: return

  • 禁止指令重排

指令重排:源代码–>编译器优化的重排–> 指令并行也可能会重排–> 内存系统也会重排—> 执行

处理器在进行指令重排的时候考虑:数据之间的依赖性

int x = 1; // 1
int y = 2; // 2
x = x + 5; // 3
y = x * x; // 4
//1234  2134 1324  执行顺序都可以

//多线程时指令顺序可能破坏数据的依赖关系

volatile可以避免指令重排:内存屏障(CPU指令)。

作用:

  1. 保证特定的操作的执行顺序!
  2. 可以保证某些变量的内存可见性

在这里插入图片描述

5、单例模式

  • 饿汉式

在调用之前就创造对象,浪费内存

public class SigleHug {
    private SigleHug() { }//构造器私有
    public static final SigleHug SIGLE_HUG = new SigleHug();//全局唯一
    public static SigleHug getInstance(){
        return SIGLE_HUG;//返回对象
    }
}

//静态内部类方式
public class SingleInner {
    private SingleInner() { }
    public static SingleInner getInstance(){
        return InnerClass.SINGLE_INNER;
    }
    private static class InnerClass{
        public static final SingleInner SINGLE_INNER = new SingleInner();
    }
}
  • 懒汉式

在调用时创建对象

public class SingleLazy {
    private SingleLazy() { }//构造器私有
    public volatile static SingleLazy singleLazy;
    public static SingleLazy getInstance(){
        //1. 分配内存空间
        //2、执行构造方法,初始化对象
        //3、把这个对象指向这个空间
        //不是原子操作,volatile修饰
        if (singleLazy==null){//线程重复创建
            singleLazy=new SingleLazy();
        }
        return singleLazy;
    }
}

问题:多线程情况下不安全

public static void main(String[] args) {
    new Thread(()-> System.out.println(SingleLazy.getInstance())).start();//@4c999645
    new Thread(()-> System.out.println(SingleLazy.getInstance())).start();//@75e24497
}

//DCL懒汉式
public class SingleLazy {
    private SingleLazy() { }
    public volatile static SingleLazy singleLazy;
    public static SingleLazy getInstance(){ 
        if (singleLazy==null) {//避免重复创建,创建实例后之间返回
            synchronized (SingleLazy.class){//线程同步
                if (singleLazy==null){//避免下一条线程重复创建
                    singleLazy=new SingleLazy();
                }
            }
        }
        return singleLazy;
    }
}

问题:反射可使类构造器私有属性改变,破坏单例

public static void main(String[] args) throws Exception {
    System.out.println(SingleLazy.getInstance());//原始对象,@14ae5a5
    Constructor<SingleLazy> constructor = SingleLazy.class.getDeclaredConstructor(null);
    //获取无参构造器
    constructor.setAccessible(true);//修改访问权限
    SingleLazy singleLazy = constructor.newInstance();
    System.out.println(singleLazy);//反射对象,@7f31245a
}

//解决:隐藏标志位,创建对象成功后改变标志位
public class SingleLazy {
    public volatile static SingleLazy singleLazy;
    
    private static boolean flag = false;//标志位,创建实例改为true
    private SingleLazy() {
        synchronized (SingleLazy.class){
            if (!flag){
                flag = true;
            }else {//反射时抛出异常,反射进行第一次创建也能成功
                throw new RuntimeException("不要用反射创建对象");
            }
        }
    }
    public static SingleLazy getInstance(){
        if (singleLazy==null) {
            synchronized (SingleLazy.class){
                if (singleLazy==null){
                    singleLazy=new SingleLazy();//不是原子操作,volatile修饰
                }
            }
        }
        return singleLazy;
    }
}

//若获得了标志位信息,也可反射成功
public static void main(String[] args) throws Exception {
        Constructor<SingleLazy> constructor = SingleLazy.class.getDeclaredConstructor(null);
        Field flag = SingleLazy.class.getDeclaredField("flag");//获得标志位
        flag.setAccessible(true);//改变标志位的访问权限
        constructor.setAccessible(true);

        SingleLazy singleLazy1 = constructor.newInstance();
        flag.set(singleLazy1,false);//改变标志位

        SingleLazy singleLazy2 = constructor.newInstance();

        System.out.println(singleLazy1);//@6d6f6e28
        System.out.println(singleLazy2);//@135fbaa4

    }

枚举类可使反射失效

//jdk反射源码
if ((clazz.getModifiers() & Modifier.ENUM) != 0)//如果是一个枚举类
    throw new IllegalArgumentException("Cannot reflectively create enum objects");
	//抛出不能通过反射创建枚举

//枚举单例
public enum  EnumSingle {
    INSTANCE;
    public static EnumSingle getInstance(){
        return INSTANCE;
    }
}

#字节码
javap -p EnumSingle.class

public final class com.wdd.lock.EnumSingle extends 
	java.lang.Enum<com.wdd.lock.EnumSingle> {
		public static final com.wdd.lock.EnumSingle INSTANCE;#INSTANCE属性
		private static final com.wdd.lock.EnumSingle[] $VALUES;
		public static com.wdd.lock.EnumSingle[] values();
		public static com.wdd.lock.EnumSingle valueOf(java.lang.String);
		private com.wdd.lock.EnumSingle();#无参构造
        public static com.wdd.lock.EnumSingle getInstance();
		static {};
}


//无参构造反射破环枚举
public static void main(String[] args) throws Exception {

    Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(null);
    constructor.setAccessible(true);
    //NoSuchMethodException: com.wdd.lock.EnumSingle.<init>()
    //异常表明EnumSingle类没有无参构造,不是JDK源码中写的无法反射枚举
    EnumSingle enumSingle1 = constructor.newInstance();
    System.out.println(enumSingle1);

}

//专业反编译软件 jad.exe  jad -sjava EnumSingle.class
private EnumSingle(String s, int i)
{
	super(s, i);//实际运行时EnumSingle枚举类只有一个有参构造器
}

//有参构造反射破环枚举
public static void main(String[] args) throws Exception {

    Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);//有参构造
    constructor.setAccessible(true);
    //IllegalArgumentException: Cannot reflectively create enum objects
    //异常表明反射无法破环枚举类的单例,枚举类运行时确实只有有参构造
    EnumSingle enumSingle1 = constructor.newInstance();
    System.out.println(enumSingle1);
}

6、CAS

CAS :比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!

public static void main(String[] args) {
    
    AtomicInteger integer = new  AtomicInteger(2000);//原子数

    boolean compareAndSet = integer.compareAndSet(2000, 3000);
    System.out.println(compareAndSet);//true
    System.out.println(integer.get());//3000

    integer.getAndIncrement();//原子数加1

    boolean compareAndSet1 = integer.compareAndSet(2000, 3000);
    System.out.println(compareAndSet1);//false
    System.out.println(integer.get());//3001

}

compareAndSet():源码

//AtomicInteger类
//本地内存地址valueOffset
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
===============================
// Unsafe类
//本地CAS方法
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

getAndIncrement()源码

//AtomicInteger类
//本地内存地址valueOffset
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}
// Unsafe类
//(对象,地址,增加的值)
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        //var地址中取值
        var5 = this.getIntVolatile(var1, var2);
        //自旋锁,内存中完成后退出循环
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

缺点

1、 循环会耗时

2、一次性只能保证一个共享变量的原子性

3、ABA问题

7、ABA问题

compareAndSet():其他线程值A修改为B又改回A,本线程不知道

解决方法:原子引用=====>乐观锁机制

  • ABA问题
public static void main(String[] args) {
    AtomicInteger integer = new  AtomicInteger(2000);
    
    System.out.println("=========捣乱的线程=========");
    System.out.println(integer.compareAndSet(2000, 3000));//true
    System.out.println(integer.get());//3000
    
    System.out.println(integer.compareAndSet(3000, 2000));//true
    System.out.println(integer.get());//2000
    
    System.out.println("=========正常的线程=========");
    
    System.out.println(integer.compareAndSet(2000, 4000));//true
    System.out.println(integer.get());//4000
}
  • 原子引用
public static void main(String[] args) {
    AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(1,2000);
    
    System.out.println("=========捣乱的线程=========");
    
    System.out.println(reference.compareAndSet(1, 2,2000,3000));//true,版本号加1
    System.out.println(reference.getReference());//2,获取版本号
    System.out.println(reference.getStamp());//3000,获取值

    System.out.println(reference.compareAndSet(2, 3,3000,2000));//true,版本号加1
    System.out.println(reference.getReference());//3
    System.out.println(reference.getStamp());//2000
    
    System.out.println("=========正常的线程=========");
    
    System.out.println(reference.compareAndSet(1,2,2000, 4000));//false,版本号不一致
    System.out.println(reference.getReference());//3
    System.out.println(reference.getStamp());//2000
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值