【JUC 三】JDK1.8部分新特性 分支合并ForkJoin 异步调用 JMM Volatile

12、JDK1.8 部分新特性

一、四大函数式接口(必需掌握)

新时代程序员:lambda表达式、链式编程、函数式接口、Stream流式计算

一般情况是有一个方法的接口,并非绝对

函数式接口在框架底层比较多,可以简化代

JDK1.8 新特性

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

1、常规接口 Function

常规函数式接口

接收 T 类型参数,输出 R 类型结果

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
} 
//T : 参数类型
//R : 返回值类型

  • 示例
public class FunctionDemo01 {
    public static void main(String[] args) {
		/*Function<String, String> function = new Function<String, String>() {
            @Override
            public String apply(String str) {
                return str;
            }
        };*/
        Function function = str -> {return str;};// lambda 表达式
        System.out.println(function.apply("adaffa2342423f"));
    }
}

2、断定型接口 Predicate

接收一个 T 类型参数,输出 布尔类型值

public class PredicateDemo {
    public static void main(String[] args) {
        Predicate<String> predicate = new Predicate<String>(){
            @Override
            public boolean test(String str) {
                return str.length() > 5 ? true : false;
            }
        };

        // lambda 表达式
        Predicate<String> predicate1 = (str) -> {return str.length() > 5 ? true : false;};

        System.out.println(predicate1.test("afaf"));

    }
}

3、供给型接口 Supplier

不需要输入,有返回值

public class SupplierDemo01 {
    public static void main(String[] args) {
/*        Supplier<String> supplier = new Supplier<String>(){
            @Override
            public String get() {
                return DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(LocalDateTime.now());
            }
        };*/
        Supplier<String> supplier = () -> {return DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(LocalDateTime.now());};
        System.out.println(supplier.get());
    }
}

4、消费型接口 Consumer

接收一个 T 类型参数,没有返回

public class ConsumerDemo01 {
    public static void main(String[] args) {
/*        Consumer<String> consumer = new Consumer<String>(){
            @Override
            public void accept(String str) {
                System.out.println(str);
            }
        };*/
        //lambda 表达式
        Consumer<String> consumer = (str) -> {System.out.println(str);};
        consumer.accept("afafa212414f");
    }
}

5、区别

全部位于 java.util.function 包下

类型接口类方法输入参数类型返回值类型
常规FunctionR apply(T t)TR
断定值Predicateboolean test(T t)Tboolean
供给型SupplierT get()-T
消费型Consumervoid accept(T t)T-

二、Stream 流式计算

package functionInterface;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;

/**
 * @author ajun
 * Date 2021/7/7
 * @version 1.0
 * 流式计算
 * --------------------
 * 输出要求:
 * 1、ID 为偶数
 * 2、年龄大于23
 * 3、名字大写
 * 4、名字按字母倒序排列
 * 5、只输出2个
 */
public class StreamDemo {
    public static void main(String[] args) {
        User u1 = new User(1, "a", 21);
        User u2 = new User(2, "b", 22);
        User u3 = new User(3, "c", 20);
        User u4 = new User(4, "d", 25);
        User u5 = new User(5, "e", 23);
        User u6 = new User(6, "f", 24);

        List<User> list = Arrays.asList(u1, u2, u3, u4, u5,u6);
        //lambda表达式、链式编程、函数式接口、Stream流式计算
        list.stream()
                .filter((u) -> {return u.getId() % 2 == 0;})//filter 参数是断定型函数式接口
                .filter((u) -> {return u.getAge() > 23;})
                //.map((u) -> {return new User(u.getId(),u.getName().toUpperCase(),u.getAge());})
            	.map((u) -> {u.setName(u.getName().toUpperCase());return u;})
                .sorted((uu1,uu2) -> {return uu2.getName().compareTo(uu1.getName());})
                .limit(2)
                //.forEach((u) -> {System.out.println(u);});//forEach 参数是消费型函数式接口
                .forEach(System.out::println);//forEach 参数是消费型函数式接口。双冒号是简写
    }
}

//lombok
@Data //pojo 数据模型
@NoArgsConstructor //无参构造器
@AllArgsConstructor //全参构造器
class User{
    private int id;
    private String name;
    private int age;
}
//输出结果
User(id=6, name=F, age=24)
User(id=4, name=D, age=25)

知识点:

双冒号:: 表示方法引用 (JDK1.8新特性)

静态方法引用(static method)语法:classname::methodname 例如:Person::getAge
对象的实例方法引用语法:instancename::methodname 例如:System.out::println
对象的超类方法引用语法: super::methodname
类构造器引用语法: classname::new 例如:ArrayList::new
数组构造器引用语法: typename[]::new 例如: String[]::new

forEach((u) -> {System.out.println(u);});//forEach 参数是消费型函数式接口

forEach(System.out::println);//forEach 参数是消费型函数式接口。双冒号是简写

13、分支合并 ForkJoin

1、什么是ForkJoin?

ForkJoin在JDK1.7,并行执行任务,大数据量!

大数据:Map Reduce( 把大任务拆分成小任务)

2、ForkJoin特点

工作窃取

内部维护的是一个双端队列

当一个子任务先运行完之后,会帮助其它任务运行;从其它任务的另一端进入;

3、类/接口 关系图

4、ForkJoinPool

分支合并池

5、ForkJoinTask

分支合并任务

recursive
英 [rɪˈkɜːsɪv] 美 [rɪˈkɜːrsɪv]
adj. 递归的;循环的

6、示例

用三种方法求和计算:

1、传统方法

2、ForkJoin

3、并行流

  • ForkJoin 任务类
package forkJoin;

import java.util.concurrent.RecursiveTask;

/**
 * @author ajun
 * Date 2021/7/7
 * @version 1.0
 * 分支合并任务
 * -------------
 * 求和计算
 * 通过forkjoinpool来执行
 * 计算任务forkJoinPool, execute(forkjoinTask task),submit(forkjoinTask task)
 * 计算类要继承自 forkjointask
 */
public class ForkJoinTaskDemo extends RecursiveTask<Long> {
    private long start;//起始值
    private long end;//结束值
    private long temp = 1_0000L;//临界值

    //构造器
    public ForkJoinTaskDemo(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 {//分支合并计算
            long middle = (start + end) / 2;//中间值

            ForkJoinTaskDemo task1 = new ForkJoinTaskDemo(start, middle);
            task1.fork();//拆分任务,把任务压入线程队列

            ForkJoinTaskDemo task2 = new ForkJoinTaskDemo(middle + 1, end);
            task2.fork();//拆分任务,把任务压入线程队列

            return task1.join() + task2.join();//合并子任务的结果
        }
    }
}
  • 测试类
package forkJoin;

import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;

/**
 * @author ajun
 * Date 2021/7/7
 * @version 1.0
 */
public class ForkJoinTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1(0L,50_0000_0000L);
        test2(0L,50_0000_0000L);
        test3(0L,50_0000_0000L);
    }

    //传统方法求和
    public static void test1(Long start,Long end) {
        Instant instant1 = Instant.now();//开始时间

        long sum = 0L;
        for (long i = start; i <= end; i++) {
            sum += i;
        }

        Instant instant2 = Instant.now();//结束时间
        Duration duration = Duration.between(instant1, instant2);//时间差
        System.out.println("耗时:" + duration.toMillis() + " 毫秒");
        System.out.println("sum = " + sum);
    }

    //分支合并求和
    public static void test2(Long start,Long end) throws ExecutionException, InterruptedException {
        Instant instant1 = Instant.now();//开始时间

        ForkJoinPool forkJoinPool = new ForkJoinPool();//定义ForkJoin池子
        ForkJoinTaskDemo forkJoinTast = new ForkJoinTaskDemo(start, end);//定义ForkJoin任务
        ForkJoinTask<Long> result = forkJoinPool.submit(forkJoinTast);//执行任务
        Long sum = result.get();//得到结果

        Instant instant2 = Instant.now();//结束时间
        Duration duration = Duration.between(instant1, instant2);//时间差
        System.out.println("耗时:" + duration.toMillis() + " 毫秒");
        System.out.println("sum = " + sum);
    }

    //并行流 stream 求和
    public static void test3(Long start,Long end) {
        Instant instant1 = Instant.now();//开始时间
        
        /*
        range(): 从 start 到 end ,不包含 end
        rangeClosed(): 从 start 到 end ,包含 end
        parallel():并行执行
        reduce():根据一定的规则将Stream中的元素进行计算后返回一个唯一的值
         */
        long sum = LongStream.rangeClosed(start, end).parallel().reduce(0, Long::sum);

        Instant instant2 = Instant.now();//结束时间
        Duration duration = Duration.between(instant1, instant2);//时间差
        System.out.println("耗时:" + duration.toMillis() + " 毫秒");
        System.out.println("sum = " + sum);
    }
}
//运行结果
耗时:1556 毫秒
sum = -5946744071209551616
耗时:1499 毫秒
sum = -5946744071209551616
耗时:808 毫秒
sum = -5946744071209551616

14、异步调用

Future设计的初衷: 对将来的某个事件的结果进行建模

1、无返回值

runAsync

package completableFuture;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * @author ajun
 * Date 2021/7/7
 * @version 1.0
 * 异步调用
 */
public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();
    }

    //无返回值异步调用
    public static void test1() throws ExecutionException, InterruptedException {
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 异步调用。无返回值");
        });

        System.out.println("11111");

        future.get();
    }
}
//返回结果
11111
ForkJoinPool.commonPool-worker-1异步调用。无返回值

2、有返回值

supplyAsync

/*
     * 有返回值的异步调用
     * 有成功回调,有异常(失败)回调
     */
public static void test2() throws ExecutionException, InterruptedException {
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        //System.out.println("供给型接口");
        //int i = 10/0;
        return 1024;
    });

    System.out.println("111");

    //得到结果值
    //1024 : 成功
    //200 : 失败
    Integer result = future.whenComplete((t, u) -> {
        //成功方法
        //System.out.println(t);//正常的返回结果
        //异常方法
        //System.out.println(u);异常的返回结果
    }).exceptionally((e) -> {//异常时
        //e.printStackTrace();
        //System.out.println(e.getMessage());
        return 200;
    }).get();

    System.out.println("结果:"+result);
}
  • 简易写法
/*
     * 有返回值的异步调用
     * 有成功回调,有异常(失败)回调
     */
public static void test2() throws ExecutionException, InterruptedException {
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        //System.out.println("供给型接口");
        //int i = 10/0;
        return 1024;//成功时
    }).exceptionally((e) -> {
        return 200;//失败时
    });

    System.out.println("111");

    Integer result = future.get();

    System.out.println("结果:"+result);
}

15、JMM

1、引言

面试:对 Volatile 的理解

volatile
英 [ˈvɒlətaɪl] 美 [ˈvɑːlətl]
adj.易变的;无定性的;无常性的;可能急剧波动的;不稳定的;易恶化的;易挥发的;易发散的

Volatile 是java虚拟机提供轻量级的同步机制

  • 保证可见性
  • 不保证原子性
  • 禁止指令重排

2、什么是JMM?

JMM: java内存模型 (Java Memory Model)

是不存在的东西,只是一种概念!一种约定!

3、JMM的一些同步约定

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

4、线程、工作内存、主内存

5、四对(8种)操作

操作说明
1lock加锁作用于主内存的变量,把一个变量标识为线程独占状态
1unlock解锁作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
2read
读取
作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
2load载入作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
3use
使用
作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
3assign赋值作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
4store
存储
作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
4write
写入
作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

assign
英 [əˈsaɪn] 美 [əˈsaɪn]
v. 分配(某物);分派,布置(工作、任务等);指定;指派;委派;派遣

6、八种规则

编号规则
1不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
2不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
3不允许一个线程将没有assign的数据从工作内存同步回主内存
4一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
5一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
6如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
7如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
8对一个变量进行unlock操作之前,必须把此变量同步回主内存

16、Volatile

1、保证可见性

package volatileTest;

import java.util.concurrent.TimeUnit;

/**
 * @author ajun
 * Date 2021/7/8
 * @version 1.0
 * volatile 可见性测试
 */
public class Demo01 {
    //不加volatile 程序就会死循环
    //加上volatile 可以保证可见性
    private static int num = 0;

    public static void main(String[] args) {
        new Thread(()->{
            while(num == 0){
                //num = 0 时,线程一直循环执行
            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        num = 1;
        
        System.out.println(num);
    }
}

2、不保证原子性

原子性:不可分割

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

1)非原子性测试

package volatileTest;

/**
 * @author ajun
 * Date 2021/7/8
 * @version 1.0
 * volatile 原子性测试
 */
public class Demo02 {
    private volatile static int num = 0;
    private static void add(){//加法
        num++;
    }
    public static void main(String[] args) {
        //开启20个线程,每个线程执行1000次add
        //最后num的理论值应该是 20000
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        //循环判断线程数量,大于2时就说明加法计算没有执行完,就一直等待
        while(Thread.activeCount() > 2){//程序中至少有两个线程存在:主线程main、垃圾回收线程gc
            Thread.yield();
        }
        System.out.println(num);
    }
}
//输出结果
19991

2)原因分析

对class文件反编译后发现,add方法中的 num++ 操作,被分成好几步来完成,在多线程情况下,这几步不是一个整体,不具有原子性,多个线程中这几步操作可能会穿插执行

反编译

javap -c -private Demo02

详细说明参考:Java 基础 - javap 反编译命令 - frank_cui - 博客园

3)实现原子性

  • 加 lock 和 synchronized (不推荐)

  • 使用原子类

 可以不加volatile

这些类的底层都直接和操作系统挂钩 ! 在内存中修改值! UnSafe类是一个很特殊的存在

private volatile static AtomicInteger num = new AtomicInteger(0);//原子类
private static void add(){//加法
    num.getAndIncrement();//加1
}

3、禁止指令重排

什么是指令重排?

你写的程序,计算机并不是按照指定的的步骤执行,有可能会重新排序后执行

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

volatile可以避免指令重排:

内存屏障: CPU指令, 作用:

  1. 保证特定的操作的执行顺序!
  2. 可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)

4、总结

Volatile 是可以保证可见性,不能保证原子性,由于内存屏障,可以避免指令重排的现象产生 !

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

土味儿~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值