JUC学习笔记(二)

9.读写锁

ReadWriteLock接口,它只有一个实现类,即:ReentrantReadWriteLock类

ReadWriteLock维护一对关联的locks,一个用于只读操作,一个用于写入。read lock可以由多个线程同时去读,而write lock只能同时由一个线程去写!

独占锁(写锁) 一次只能被一个线程占有

共享锁(度锁) 多个线程可以同时占有

/**
 * ReadWriteLock
 * 独占锁(写锁) 一次只能被一个线程占有
 * 共享锁(度锁) 多个线程可以同时占有
 * 读写存在三种情况:
 * 读-读  可以共存
 * 读-写  不能共存
 * 写-写  不能共存
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        //写入
        for(int i=1; i<=5; i++){
            final int temp = i;
            new Thread(()->{
                myCache.put(temp+"",temp+"");
            },String.valueOf(i)).start();
        }

        //读取
        for(int i=1; i<=5; i++){
            final int temp = i;
            new Thread(()->{
                myCache.get(temp+"");
            },String.valueOf(i)).start();
        }
    }
}

/**
 * 自定义缓存,缓存其实只有两个操作,一个用于往其中set,一个用于从其中get
 * 因为存的时候要求只能由一个线程操作,而取的时候可以由多个线程操作,所以
 * 下方使用ReadWriteLock
 */
class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();

    //存,写:存的时候要求只能由一个线程操作
    public void put(String key, Object object){
        System.out.println(Thread.currentThread().getName()+"写入"+key);
        map.put(key, object);
        System.out.println(Thread.currentThread().getName()+"写入OK");
    }

    //取,读:取的时候可以由多个线程操作
    public void get(String key){
        System.out.println(Thread.currentThread().getName()+"读取"+key);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName()+"读取OK");
    }
}
//返回结果为:
3写入3
2写入2
5写入5
3写入OK
5写入OK
4写入4
4写入OK
2写入OK
3读取3
1写入1
1写入OK
5读取5
5读取OK
4读取4
4读取OK
1读取1
1读取OK
2读取2
2读取OK
3读取OK
//很明显,上述的返回结果是有误的,因为我们所要求的是写入的时候只能由一个线程来操作,即上述
//打印:3写入3 后,后续应该是打印: 3写入OK,但它被:2写入2  给插入了,即一个线程在进行写操作的时候,另外一个线程又来执行了写入操作!

使用ReentrantReadWriteLock解决上述问题:

/**
 * ReadWriteLock
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCacheLock myCache = new MyCacheLock();

        //写入
        for(int i=1; i<=5; i++){
            final int temp = i;
            new Thread(()->{
                myCache.put(temp+"",temp+"");
            },String.valueOf(i)).start();
        }
        
    }
}

//加锁的缓存实现
class MyCacheLock {
    private volatile Map<String, Object> map = new HashMap<>();
    //读写锁:更加细粒度的控制
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    //存:写入的时候,只希望同时只有一个线程写!
    public void put(String key, Object object){
        //因为是进行写操作,所以对写锁加锁。写和读的锁分离,实现更加细粒度的控制!
        readWriteLock.writeLock().lock();

        try {
            System.out.println(Thread.currentThread().getName()+"写入"+key);
            map.put(key, object);
            System.out.println(Thread.currentThread().getName()+"写入OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    //取:所有人都可以进行读取!
    public void get(String key){
        readWriteLock.readLock().lock();

        try {
            System.out.println(Thread.currentThread().getName()+"读取"+key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}
//打印输出结果为:
4写入4
4写入OK
5写入5
5写入OK
2写入2
2写入OK
3写入3
3写入OK
1写入1
1写入OK
//每一个线程的写入到写入OK没有被其他线程插队!

10.阻塞队列

阻塞:停止等待

队列:一个队列,在写入的时候,如果队列满了,就必须阻塞等待。在从队列中取出内容的时候,如果队列为空则必须阻塞等待

在这里插入图片描述

阻塞队列:BlockingQueue是一个接口

在这里插入图片描述

BlockingQueue接口

什么情况下我们会使用阻塞队列:多线程、线程池

BlockingQueue接口实际上是和List、Set同级的接口,BlockingQueue的实现类有ArrayBlockingQueue、LinkedBlockingQueue等
在这里插入图片描述

在这里插入图片描述

学会使用队列

添加、移除

阻塞队列的四组API

1.抛出异常

2.不会抛出异常(有返回值)

3.阻塞等待

4.超时等待

方式抛出异常有返回值,不抛出异常阻塞等待超时等待
添加add()offer()put()带参数offer()方法
移除remove()poll()take()带参数poll()方法
检测队首元素element()peek()--
/**
 * 抛出异常
 */
public static void test1(){
    ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

    System.out.println(blockingQueue.add("a"));
    System.out.println(blockingQueue.add("b"));
    System.out.println(blockingQueue.add("c"));

    System.out.println(blockingQueue.element()); //查看队首元素
    
    //IllegalStateException: Queue full  抛出异常
    //System.out.println(blockingQueue.add("d"));

    System.out.println(blockingQueue.remove());
    System.out.println(blockingQueue.remove());
    System.out.println(blockingQueue.remove());

    //java.util.NoSuchElementException 抛出异常
    //System.out.println(blockingQueue.remove());
}

/**
 * 有返回值,没有异常
 */
public static void test2(){
    ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

    System.out.println(blockingQueue.offer("a"));
    System.out.println(blockingQueue.offer("b"));
    System.out.println(blockingQueue.offer("c"));
    System.out.println(blockingQueue.offer("c")); // 返回false 不抛出异常


    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll()); // 返回null 不抛出异常

}
/**
 * 等待、阻塞(一直阻塞)
 */
public static void test3() throws InterruptedException {
    ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

    //一直阻塞
    blockingQueue.put("a");
    blockingQueue.put("b");
    blockingQueue.put("c");
    //blockingQueue.put("d"); //因为阻塞队列容量定义为3,当添加第4个元素时,会一直等待

    System.out.println(blockingQueue.take());
    System.out.println(blockingQueue.take());
    System.out.println(blockingQueue.take());
    System.out.println(blockingQueue.take());

}
/**
 * 等待、阻塞(等待超时)
 */
public static void test4() throws InterruptedException {
    ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

    blockingQueue.offer("a");
    blockingQueue.offer("b");
    blockingQueue.offer("c");
    //blockingQueue.offer("d", 2, TimeUnit.SECONDS); //往队列中添加元素d,若队列是满的,则会延时等待,等待时间到了就会退出

    System.out.println("================");
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
}
SynchronousQueue 同步队列

没有容量,进去一个元素,必须等待取出来之后,才能再往里面放一个元素!

存取操作为:put、take

/**
 * 同步队列和其他的BlockingQueue是不一样的,因为SynchronousQueue
 * 不存储元素,只要往其中put了一个元素,必须先从中take出来,才可以再put第二个元素
 */
public class SynchronousQueueDemo {
    public static void main(String[] args) {
        //同步队列
        SynchronousQueue<Object> synchronousQueue = new SynchronousQueue<>();

        new Thread(()->{
            try {
                synchronousQueue.put("1");
                System.out.println(Thread.currentThread().getName()+"put 1");
                synchronousQueue.put("2");
                System.out.println(Thread.currentThread().getName()+"put 2");
                synchronousQueue.put("3");
                System.out.println(Thread.currentThread().getName()+"put 3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        },"T1").start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+"take"+synchronousQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        },"T2").start();
    }
}

11.线程池(重点)

线程池部分的内容:三大方法、7大参数、4种拒绝策略。搞懂这些,线程池差不多了!

池化技术:

程序的运行本质:占用系统资源!为了优化资源的使用,也就有了池化技术!

线程池、连接池、内存池、对象池!这些池的出现是因为单独资源的创建和销毁十分浪费系统资源,所以采用池化技术统一管理!

池化技术:事先准备好一些资源,当某个软件需要使用时,就去池子中拿,用完之后再还给池子!

线程池的好处:

1.降低资源的消耗

2.提高响应的速度

3.方便管理

线程复用,可以控制最大并发数、管理线程

线程池:创建的三大方法

在这里插入图片描述

/**
 * Executors 工具类,该工具类中有三大方法
 *
 * 使用了线程池之后,要使用线程池来创建线程,而不再是new Thread方式
 */
public class Demo01 {
    public static void main(String[] args) {
        //ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
        //ExecutorService threadPool = Executors.newFixedThreadPool(5); //固定线程池大小
        ExecutorService threadPool = Executors.newCachedThreadPool(); //可伸缩的线程池

        try {
            for(int i=0; i<10; i++){
                //使用了线程池之后,要使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"  ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,需要关线程池
            threadPool.shutdown();
        }
    }
}
7大参数

源码分析:

//newSingleThreadExecutor()方法底层本质上是new一个ThreadPoolExecutor
//new ThreadPoolExecutor()时参数含义:核心线程池大小为1,最大核心线程池大小也为1
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

//newFixedThreadPool的底层也是ThreadPoolExecutor
//new ThreadPoolExecutor()时参数含义:核心线程池大小为给定参数,最大核心线程池大小也为给定参数
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

//同理newCachedThreadPool底层也是ThreadPoolExecutor
//最大核心线程池大小也为Integer.MAX_VALUE,非常大,如果这么大的值去跑的话电脑一定会移除
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

本质:开启线程池调用的是ThreadPoolExecutor
接下来研究一下ThreadPoolExecutor的源码
//该方法有7个参数
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
                          int maximumPoolSize, //最大核心线程池大小
                          long keepAliveTime, //超时了没有被调用就会释放
                          TimeUnit unit, //超时单位
                          BlockingQueue<Runnable> workQueue, //阻塞队列
                          ThreadFactory threadFactory, //线程工厂:创建线程的,一般不动
                          RejectedExecutionHandler handler //拒绝策略) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

对7大参数的理解:

在这里插入图片描述

手动创建一个线程池
//自定义线程池:工作中就是这么创建的,因为通过Executors方法创建的不安全
ExecutorService threadPool = new ThreadPoolExecutor(
    2,
    5,
    3,
    TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(3),
    Executors.defaultThreadFactory(), // 线程工厂一般不会变化
    new ThreadPoolExecutor.AbortPolicy());
4种拒绝策略

在这里插入图片描述

默认的终止策略是:AbortPolicy() -> 相当于线程池已经开启了最大线程数,然后阻塞队列中也塞满了待执行的任务,当再有任务进入阻塞队列的时候就会抛出异常!

CallerRunsPolicy拒绝策略:即哪来的去哪里!

最大承载:Deque + max

所以当Deque + max < 待执行任务数 时,就会报rejectedExecution异常,下方代码中Deque + max = 3 + 5=8,所以当for循环有9个任务时,报了上述异常!

public class Demo01 {
    public static void main(String[] args) {
        //自定义线程池:工作中就是这么创建的,因为通过Executors方法创建的不安全
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(), // 线程工厂一般不会变化
                new ThreadPoolExecutor.AbortPolicy());

        //最大承载:Deque + max
        try {
            for(int i=0; i<9; i++){
                //使用了线程池之后,要使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"  ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,需要关线程池
            threadPool.shutdown();
        }
    }
}

AbortPolicy()策略:银行满了,还有人进来,不处理这个人的,抛出异常

CallerRunsPolicy()策略:哪来的去哪里,比如从main线程来的,那么银行满了,还有人进来,不处理这个人的,将这个人交给main线程去处理,相当于让你们公司帮你办银行卡,而不是让你去银行办银行卡!

DiscardPolicy()策略:队列满了,丢掉任务,不会抛出异常!

DiscardOldestPolicy()策略:队列满了,尝试去和最早的竞争,也不会抛出异常!

小结和拓展:maximumPoolSize如何设定

最大线程到底该如何定义:

1.CPU密集型

几核CPU就定义为多少,通过代码去获取服务器的核数。

//获取CPU的核数
System.out.println(Runtime.getRuntime().availableProcessors());

//所以可以用下述代码定义一个线程池,第二个参数直接用的CPU核数
ExecutorService threadPool = new ThreadPoolExecutor(
        2,
        Runtime.getRuntime().availableProcessors(),
        3,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(3),
        Executors.defaultThreadFactory(), // 线程工厂一般不会变化
        new ThreadPoolExecutor.DiscardOldestPolicy());
2.IO密集型

大于核数即可!2倍的核数最宜!

12.四大函数式接口(必须掌握)

新时代程序员必须要掌握的几点:lambda表达式、链式编程、函数式接口、Stream流式计算=> 取代for循环的方法

函数式接口:即只有一个方法的接口

该接口的上方会有一个注解:@FunctionalInterface

比如:Runnable接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Java中有超级多@FunctionalInterface,它可以简化编程模型,在新版本的框架底层大量使用!

如果不懂函数式接口的话,面试官随便问你个问题你可能都不懂,比如面试官问:foreach的参数是什么?

深入到forEach()方法的底层:

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

发现它的参数为Consumer接口,它也是一个函数式接口:

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
}

并且forEach的参数是消费者类的函数时接口!

四大函数式接口:在java.util.function包下查看

在这里插入图片描述

代码测试:
Function函数式接口

在这里插入图片描述

package com.codeyu.function;

import java.util.function.Function;

/**
 * Function 函数式接口,有一个输入参数,有一个输出
 * 并且:只要是函数式接口,就可以用lambda表达式简化(因为实现的一定是那唯一一个方法)
 */
public class Demo01 {
    public static void main(String[] args) {
        //匿名内部类
//        Function function = new Function<String, String>() {
//            @Override
//            public String apply(String s) {
//                return s;
//            }
//        };

        //写lambda表达式分如下几步:1.写一个()->{}
        // 2.将参数丢入()中
        // 3.然后函数体写在{}中即可!
        Function function = (str)->{
            return str;
        };

        System.out.println(function.apply("abc"));
    }
}
断定型接口Predicate:有一个输入参数,返回值只能是布尔值!

在这里插入图片描述

package com.codeyu.function;

import java.util.function.Predicate;

/**
 * 断定型接口:有一个输入参数,返回值只能是布尔值!
 */
public class Demo02 {
    public static void main(String[] args) {
        //功能:判断一个整型值是否大于0
//        Predicate<Integer> predicate1 = new Predicate<Integer>(){
//            @Override
//            public boolean test(Integer num) {
//                return num > 0;
//            }
//        };
        //上述匿名内部类简化为下述的lambda表述形式:
        Predicate<Integer> predicate = (Obj)->{ return Obj > 0; };

        System.out.println(predicate.test(0));
    }
}
消费型接口Consumer:只有输入,没有返回值

在这里插入图片描述

package com.codeyu.function;

import java.util.function.Consumer;

/**
 * Consumer消费型接口:只有输入,没有返回值
 */
public class Demo03 {
    public static void main(String[] args) {
        Consumer<String> consumer = new Consumer<>() {
            @Override
            public void accept(String str) {
                System.out.println(str);
            }
        };

        Consumer<String> consumer1 = (str) -> { System.out.println(str); };

        consumer.accept("123");
        consumer1.accept("456");

    }
}
供给型接口Supplier:没有参数,只有返回值

在这里插入图片描述

package com.codeyu.function;

import java.util.function.Supplier;

/**
 * Supplier 供给型接口:没有参数,只有返回值
 */
public class Demo04 {
    public static void main(String[] args) {
        Supplier<String> supplier = new Supplier<>() {
            @Override
            public String get() {
                return "Hello world!";
            }
        };

        //lambda表达
        Supplier<String> supplier1 = () -> {return "Hello world---!";};

        System.out.println(supplier.get());
        System.out.println(supplier1.get());
    }
}
函数式接口的作用:

简化编程模型!

Java程序员必须要会的技术:泛型、枚举、反射、lambda表达式、链式编程、函数式接口、Stream流式计算

13.Stream流式计算

什么是流式计算?

大数据时代:存储+计算

用来存储东西的,如:集合、MySQL,它们本质就是存储东西的!

但是真正的计算操作应该交给来操作!

流位于Java.util.stream包下。该包下都是与流相关的内容,以下主要介绍Stream接口:

在这里插入图片描述

//User类定义
package com.codeyu.stream;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    private int id;
    private String name;
    private  int age;

}

//Test类
package com.codeyu.stream;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;

/**
 * 现在有一道题目:要求一分钟内完成此题,只能用一行代码实现!
 * 现在有5个user,赛选:
 * 1.ID必须是偶数
 * 2.年龄必须大于23岁
 * 3.用户名转为大写字母
 * 4.用户名字母倒着排序
 * 5.只输出一个用户!
 *
 */

public class Test {
    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", 23);
        User u4 = new User(4, "d", 24);
        User u5 = new User(6, "e", 25);
        //集合就是存储
        List<User> list = Arrays.asList(u1, u2, u3, u4, u5);

        //计算交给Stream流。下方也正好是链式编程!
        list.stream()
                .filter(user->{ return user.getId()%2==0; })//通过实现Predicate函数式接口的匿名内部类来实现排序
                .filter(user->{return user.getAge()>23; })//通过实现Predicate函数式接口的匿名内部类来实现排序
                .map(user -> {
                    //通过实现Function函数式接口的匿名内部类来实现将name字段转为大写
                    user.setName(user.getName().toUpperCase());
                    return user;
                })
                .sorted((o1, o2) -> {
                    //通过实现Comparator函数式接口的匿名内部类来实现排序
                    if(o1.getName().charAt(0) > o2.getName().charAt(0)){
                        return -1;
                    }
                    else if(o1.getName().charAt(0) < o2.getName().charAt(0)){
                        return 1;
                    }
                    else {
                        return 0;
                    }
                })
                .limit(1) //只输出一个用户数据
                .forEach(System.out::println);
    }
}

上方的排序部分参数是一个Comparator的函数式接口,其还可以有如下几种实现形式:

.sorted((uu1,uu2)-> uu1.getName().compareTo(uu2.getName()))
//因为String类型实现了Comparable接口,即实现了Comparable接口中的compareTo方法,所以上述比较name字段时,可以直接使用String类型的compareTo()方法。

值得注意的是:不管是实现Comparator函数式接口中的compare方法,还是实现Comparable接口中的compareTo()方法,都是第一个参数与第二个参数比较,如果第一个参数的某个值大于第二个参数,并且此时主动令其返回正值,则排序是增序排序,若此时主动令其返回负值,则排序是降序排序。看上方Test类中的测试方法:

//因为要实现降序,所以当第一个参数的name字段值大于第二个参数的name字段值时,主动让其返回负值,此时能实现将降序排序!
.sorted((o1, o2) -> {
    //通过实现Comparator函数式接口的匿名内部类来实现排序
    if(o1.getName().charAt(0) > o2.getName().charAt(0)){
        return -1;
    }
    else if(o1.getName().charAt(0) < o2.getName().charAt(0)){
        return 1;
    }
    else {
        return 0;
    }
})

14.ForkJoin

什么是ForkJoin?

ForkJoin 在JDK1.7出现,它主要是将大任务拆分成小任务再来执行,从而提高处理效率!

在这里插入图片描述

ForkJoin特点:工作窃取

如下图所示:比如两个线程A和B,假设A执行任务到1半的时候,B已经执行完毕了,那么它会去将A的任务拿过来执行,从而提高效率,这就是工作窃取!

ForkJoin中维护的是双端队列!

在这里插入图片描述

ForkJoin的操作

执行ForkJoin的操作,ForkJoin操作是通过ForkJoinPool类对象来执行的,如下方法:

void	execute(ForkJoinTask<?> task)

在这里插入图片描述

ForkJoin计算类:
package com.codeyu.forkjoin;

import java.util.concurrent.RecursiveTask;

/**
 * 求和计算的任务
 * 3000    6000(ForkJoin)   9000(Stream并行流)
 * //如何使用ForkJoin
 * //1.ForkJoinPool 通过它来执行
 * //2.计算任务 	forkJoinPool.execute​(ForkJoinTask task)
 * //3.计算类要继承ForkJoinTask
 *
 */
public class ForkJoinDemo extends RecursiveTask<Long> {

    private Long start;
    private Long end;

    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    private Long threshold = 300000000L;

    //计算方法
    @Override
    protected Long compute() {
        //只要end-start大于临界值,我们就希望它去走分支合并,即ForkJoin
        //临界值
        if((end - start) > threshold){
            //分支合并(ForkJoin操作) 递归
            long middle = (start+end)/2; //中间值
            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
            task1.fork(); //拆分任务,把任务压入线程队列
            ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
            task2.fork(); //拆分任务,把任务压入线程队列

            //将二者计算结果合并
            long res = task1.join() + task2.join();

            return res;
        }
        else {
            //否则的话,执行普通的计算方式,不走分支合并
            Long sum = 0L;
            for(Long i=start; i<=end; i++){
                sum+=i;
            }
            return sum;
        }
    }
}
测试类:
package com.codeyu.forkjoin;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;

/**
 *  3000    6000(ForkJoin)   9000(Stream并行流)
 */
public class Test {
    public static void main(String[] args) throws Exception {
        //test1(); //sum=500000000500000000 时间:11455
        //test2(); //sum=500000000500000000 时间:9016
        //test3(); //sum=500000000500000000 时间:924
        //test3()所使用的stream式计算花费时间最少!效率提高十几倍
    }

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



    }

    //forkjoin
    public static void test2() throws Exception {
        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(); //此处get()方法会阻塞等待结果

        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+" 时间:"+(end-start));
    }

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

        //stream并行流
        Long sum = LongStream.rangeClosed(0L,10_0000_0000L).parallel().reduce(0,Long::sum);

        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+" 时间:"+(end-start));
    }
}

ForkJoin在大数据量的情况下才使用!小数据量没必要使用!

15.异步回调

Future

在这里插入图片描述

package com.codeyu.future;

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

/**
 * 异步回调:
 * 执行成功 -> 成功回调
 * 执行失败 -> 失败回调
 */
public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //没有返回之的异步回调。下方的Void是void的封装
//        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
//            try {
//                TimeUnit.SECONDS.sleep(2);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println(Thread.currentThread().getName()+"runAsync=>void");
//        });
//
//        completableFuture.get(); //获取返回值,该get()方法会产生阻塞,所以最终"11111111"后打印
//
//        System.out.println("11111111");

        //====================下方代码是有返回值的异步回调=========================
        //ajax有成功/失败的回调!
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
            int i = 10/0;
            return 1024; //成功时返回1024
        });

        System.out.println(completableFuture.whenCompleteAsync((t, u) -> {
            System.out.println("t=>" + t); //正常的返回结果
            System.out.println("u=>" + u); //错误信息:java.util.concurrent.CompletionException
        }).exceptionally((e) -> {
            System.out.println(e.getMessage());
            return 233; //失败时返回233
        }).get());

        /**
         * success code 200
         * error code 400 500
         *
         */

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值