JUC

4 篇文章 0 订阅

 

 

目录

Lock

1. 监视器实现精准唤醒

2.公平锁

3.synchronized 和 Lock 的区别

4. 8锁问题

 

集合类不安全

1.HashSet的本质就是一个HashMap

 

2.List 不安全的解决

3. Set不安全解决

注意: CopyOnWriteArraySet 内部由CopyOnWriteArrayList构成

4.Map 不安全的解决

Callable 

1.与Runnable差别

同一个FutureTask 实例, 只能被线程执行一次

源码

常用的辅助类

CountDownLatch

应用场景

代码实例

CyclicBarrier

实例

Semaphore

 读写锁

示例

ArrayBlockingQueue 阻塞队列

4组API

SynchronousQueue 同步队列

线程池

Executors 工具类

源码解析

ThreadPoolExecutor 线程池

使用

七大参数

源码

线程数

拒绝策略

CallerRunsPolicy   

执行前后线程的状态

四大函数式接口

Function 

使用

Predicate

使用

Consumer

使用

Supplier

使用

Stream流式计算

链式调用

ForkJoin 

工作窃取

推荐写法: 推荐使用 invokeAll 而不是 fork

fork



Lock

1. 监视器实现精准唤醒

同一把锁, 设置多个监视器(A,B, C。由A 唤醒 B , B唤醒C, C 唤醒A。轮流工作。其中一个工作时,其他线程等待。

代码

public class D {

    public static void main(String[] args) {
        Data4 data = new Data4();

        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                data.printA();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                data.printB();
            }
        },"B").start();

        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                data.printC();
            }
        },"C").start();
    }

}

class Data4{ // 资源类 Lock

    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int number = 1; // 1A  2B  3C

    public void printA(){
        lock.lock();
        try {
            // 业务,判断-> 执行-> 通知
            while (number!=1){
                // 等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>AAAAAAA");
            // 唤醒,唤醒指定的人,B
            number = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB(){
        lock.lock();
        try {
            // 业务,判断-> 执行-> 通知
            while (number!=2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>BBBBBBBBB");
            // 唤醒,唤醒指定的人,c
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printC(){
        lock.lock();
        try {
            // 业务,判断-> 执行-> 通知
            // 业务,判断-> 执行-> 通知
            while (number!=3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>BBBBBBBBB");
            // 唤醒,唤醒指定的人,c
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

2.公平锁

  • 公平锁:加锁前先查看是否有排队等待的线程,有的话优先处理排在前面的线程,先来先得
  • 非公平所:线程加锁时直接尝试获取锁,获取不到就自动到队尾等待。
  • Lock 默认是 非公平锁, 其有构造函数,可以设定为公平锁
  • synchronized 是非公平锁

3.synchronized 和 Lock 的区别

4. 8锁问题

首先:8锁问题是一个问题。就是锁是谁,哪一把锁的问题。

       1.搞清楚锁是什么,用的哪一把锁,该方法需要等哪一把锁。

       2.This 锁, class 锁, 自定义对象锁。不是一个锁不遵守一个同步。

 

集合类不安全

集合类不安全是由于 修改值本身,和修改size 不是原子的。多个线程的添加方法可能在同一个位置add.

1.HashSet的本质就是一个HashMap

  • 所有的Set 其本质都是一个 对应类型的Map .  Set中的 值, 就i是 Map 中的 key. value 是一个 统一的对象 PRESENT(Object)

 

2.List 不安全的解决

 

// 1、使用 Vcector 效率低
List<String> list = Collections.synchronizedList(new ArrayList<>());
List<String> list = new Vector<>();

// 2、使用 Collections 工具类转换。
List<String> list = Collections.synchronizedList(new ArrayList<>());

// 3。 使用 写入时复制, 实现读写分离
List<String> list = new CopyOnWriteArrayList<>();

 

  •  
  • Vector是增删改查方法都加了synchronized,
    • 保证同步,但是每个方法执行的时候都要去获得锁,性能就会大大下降,
  • Collections : 使用装饰模式,返回一个安全的List.
  • CopyOnWriteArrayList 只是在增删改上加锁,
    • 但是读不加锁,在读方面的性能就好于Vector,CopyOnWriteArrayList支持读多写少的并发情况
    • 读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array
    • 该集合详解

3. Set不安全解决

 

// Collections 包装
Set<String> set = Collections.synchronizedSet(new HashSet<>());
// 写入时复制
Set<String> set = new CopyOnWriteArraySet<>();

注意: CopyOnWriteArraySet 内部由CopyOnWriteArrayList构成

构造器

特性
1. 它最适合于具有以下特征的应用程序:Set 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。
2. 它是线程安全的。
3. 因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大。
4. 迭代器支持hasNext(), next()等不可变操作,但不支持可变 remove()等 操作。
5. 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。

6. 该集合详解

4.Map 不安全的解决

  • 加载因子:默认为 0.75
  • 默认初试容量: 1 << 4    16.
// 同步 Map  使用分段锁
Map<String, String> map = new ConcurrentHashMap<>();

Hashtable线程安全但效率低下

分段锁 : 

ConcurrentHashMap详解

Callable 

1.与Runnable差别

1、可以有返回值

2、可以抛出异常

3、方法不同,run()/  call()

 

创建Callable实现类,丢入 FutureTask

创建FutureTask (Runnable的实现类),丢入线程.

Callable 的 call 方法是线程的任务实体

注意:

 1.同一个FutureTask 实例, 只能被线程执行一次。一个 task 即视new 多个Thread 也只会执行一次 该task。

2. get 方法获取返回值可能会阻塞当前线程。

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        MyThread thread = new MyThread();
        FutureTask futureTask = new FutureTask(thread); // 适配类
        new Thread(futureTask,"A").start();

        // 注意: 一个  FutureTask的实例,只会 被线程执行一次。
        new Thread(futureTask,"B").start(); // 结果会被缓存,效率高

        //这个get 方法可能会产生阻塞!把他放到最后
        Integer o = (Integer) futureTask.get();
        // 或者使用异步通信来处理!
        System.out.println(o);
        System.out.println("main");

    }
}

class MyThread implements Callable<Integer> {

    @Override
    public Integer call() {
        System.out.println("call()"); // 会打印几个call
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 耗时的操作
        return 1024;
    }

}

同一个FutureTask 实例, 只能被线程执行一次

FutureTask中有一个state属性,创建FutureTask时为0,执行一次该线程后发生变化。也只有是0的时候才会开启线程去执行他

源码

Stateoffset 在Java中没有实际意义,但在c中表示 state的地址。Stateoffset 通过本地方法获取state的地址,再通过本地方法加Stateoffset 去修改state的值。上边if是实现了值的交换。这里,通过本地犯法改变了State.

常用的辅助类

CountDownLatch

线程减法计数器。

在await的线程中进行阻塞。直到countdown()在其他线程中完成计数,才唤醒。

应用场景

对比 关键路径算法,要执行一个任务,必须等待其前驱任务执行完毕,再执行该任务,而在多线程下,就是要将多个线程执行完毕后,当前线程才能继续向下执行。其实就是一个集中等待点,人齐了再发出

代码实例

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 总数是6,必须要执行任务的时候,再使用!
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <=6 ; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" Go out");
                countDownLatch.countDown(); // 数量-1
            },String.valueOf(i)).start();
        }

        countDownLatch.await(); // 等待计数器归零,然后再向下执行
        System.out.println("Close Door");

    }
}

在主线程中设置等待点,直到分线程的指定任务完成,完成计数。再唤醒主线程

CyclicBarrier

加法计数器

实例

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        /**
         * 集齐7颗龙珠召唤神龙
         */
        // 召唤龙珠的线程
        // lamada : 在统一阻塞点插入任务插入任务。
        CyclicBarrier cyclicBarrier = new CyclicBarrier(8,()->{
            System.out.println("召唤神龙成功!");
        });
        // 开启 7 个 线程
        for (int i = 1; i <=7 ; i++) {
            final int temp = i;
            // lambda能操作到 i 吗
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"收集"+temp+"个龙珠");
                try {
                    cyclicBarrier.await(); // 等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

创建计数器时, 

            1. 第一参数, 设定 触发线程数

             2. 第二参数  设定 插入任务, 是一个lamada .

运行: 

        1. 在 多个线程中执行 await , 对线程进行阻塞, 

         2. 当阻塞线程数 达到 8  后, 通过新的线程 执行 lamada 插入任务。

Semaphore

信号量(通行证)  限流

semaphore.acquire();   // 获取则进入, 否则就阻塞等待
semaphore.release();   // finally 进行释放。

资源有限,谁拿到资源谁执行。

public class SemaphoreDemo {
    public static void main(String[] args) {
        // 线程数量:停车位! 限流!
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <=6 ; i++) {
            new Thread(()->{
                // acquire() 得到
                try {
                    // 获取
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢到车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+"离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 释放
                    semaphore.release(); // release() 释放
                }

            },String.valueOf(i)).start();
        }

    }

 读写锁

读可以一起读,写只能有一个写 , 共享锁

示例

class MyCacheLock{

    private volatile Map<String,Object> map = new HashMap<>();
    // 读写锁: 更加细粒度的控制
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Lock lock = new ReentrantLock();

    // 存,写入的时候,只希望同时只有一个线程写
    public void put(String key,Object value){
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"写入"+key);
            map.put(key,value);
            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();
        }
    }

}

ArrayBlockingQueue 阻塞队列

4组API

构造方法要 给定 初始化容量。

有异常: add ,remove element  (List 接口的)

有返回值无异常:offer, poll ,peel (队列接口的)

阻塞等待 :  put()  take()  队列满时,则在等待队列等待

超时等待:offer() poll()    设定超时时间。

SynchronousQueue 同步队列

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

::: 放一个,就必须取出来一个,才能继续放,否则就阻塞。

public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>(); // 同步队列

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


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

线程池

线程池:三大方法、7大参数、4种拒绝策略

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

详细参考

Executors 工具类

封装集成了很多用于初始化线程池的方法。 但是不推荐使用

阿里规范

        ExecutorService executorsPool01 = Executors.newCachedThreadPool();
        ExecutorService executorService = Executors.newFixedThreadPool(8);
        ExecutorService executorService1 = Executors.newSingleThreadExecutor();

源码解析

通过Executors创建线程池的本质就是 通过 ThreadPoolExecutor线程池

 

ThreadPoolExecutor 线程池

使用

public class Demo01 {
    public static void main(String[] args) {
        // 自定义线程池!工作 ThreadPoolExecutor

        // 最大线程到底该如何定义
        // 1、CPU 密集型,几核,就是几,可以保持CPu的效率最高!
        // 2、IO  密集型   > 判断你程序中十分耗IO的线程,
        // 程序   15个大型任务  io十分占用资源!

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

        List  list = new ArrayList();

        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                Runtime.getRuntime().availableProcessors(),
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy());  //队列满了,尝试去和最早的竞争,也不会抛出异常!
        try {
            // 最大承载:Deque + max
            // 超过 RejectedExecutionException
            for (int i = 1; i <= 9; i++) {
                // 使用了线程池之后,使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }

    }
}

七大参数

maximumPoolSize :  // 核心线程池大小 
keepAliveTime   : // 线程挂起时间。超时就销毁
TimeUnit unit   :// 时间单位
BlockingQueue<Runnable> workQueue : //阻塞队列
ThreadFactory threadFactory : //线程工厂
RejectedExecutionHandler handler : //拒绝策略

源码

线程数

注意:核心线程保持存活, 当阻塞队列满时,每多出一个线程任务,则多创建一个 线程,直到Max.

 最大线程数 : max + queue. 多出的线程任务,只能等前边的任务执行完获取到线程去执行,或者放弃任务。

拒绝策略

* new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常
* new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里!
* new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常!
* new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会抛出异常!

AbortPolicy : 超出最大任务数(max + queue)抛出异常。

DiscardPolicy : 超出最大任务数(max + queue)丢弃任务

DiscardOldestPolicy : 尝试 插队,直接去获取线程(恰好此时有任务结束,太巧了) 失败则放弃。

 CallerRunsPolicy   : 比较难理解, 由创建该任务的线程来执行。即不再使用其他线程执行该任务,

                                       直接在该位置就地执 行该任务,相当于在该位置直接插入任务。

异常:RejectedExecutionException 

CallerRunsPolicy   

  for (int i = 1; i <= 300; i++) {
                // 使用了线程池之后,使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
                上边线程任务若获取线程失败,则 在这里就地执行该任务,相当于调用了该方法。
                System.out.println(Thread.currentThread().getName() + "11111111111");



            }



pool-1-thread-7 ok   子线程调用
main11111111111       主线程本身任务
main11111111111
main11111111111
main11111111111
main ok              输出ok任务插入到main 
main11111111111
pool-1-thread-8 ok
pool-1-thread-5 ok

获取线程失败,则相当于main 调用了一个函数,等函数执行完,才返回main函数

执行前后线程的状态

 

1、核心线程 且 allowCoreThreadTimeOut 为false

那核心线程在执行任务完后,会处于阻塞blocking状态,直到下一个任务取出它

2、核心线程 且 allowCoreThreadTimeOut 为true

核心线程如果在keepAliveTime时间内没有新的任务,超时时间之前,处于阻塞状态blocking,超时时间到了后,线程状态将变为dead(terminated)

3、非核心线程

超时时间之前阻塞状态blocking,超时时间之后,dead(terminated)

4、如果线程池都关闭了,那所有的线程都会变为dead(terminated)

四大函数式接口

四大函数式接口,其实就是 官方给的 lamada 接口的几个常用模板

Function 

一个参数,一个返回值

使用

Function<String,String> function = str->{return str;};
System.out.println(function.apply("asd"));
public interface Function<T, R> {
     R apply(T t);
}

Predicate

断定型接口  一个参数,一个boolean返回值

使用

Predicate<String> predicate = (str)->{return str.isEmpty(); };
System.out.println(predicate.test(""));

public interface Predicate<T> {
        boolean test(T t);
}

Consumer

消费型接口,只进不出,一个参数,无返回值。 

foreach(Consumer con)  : 对 列表中每一个元素作为Consumer 的参数,去执行accrpt()

使用

Consumer<String> consumer = (str)->{System.out.println(str);};
consumer.accept("sdadasd");

public interface Consumer<T> {
     void accept(T t);
}

Supplier

供给型接口  无参数, 带一个返回值  通过get可以获取到一个值。

使用

Supplier supplier = ()->{ return 1024; };
System.out.println(supplier.get());

public interface Supplier<T> {
     T get();
}  

Stream流式计算

链式调用

        list.stream()
                .filter(u->{return u.getId()%2==0;})
                .filter(u->{return u.getAge()>23;})
                .map(u->{return u.getName().toUpperCase();})
                .sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
                .limit(1)
                .forEach(System.out::println);
Stream<T> filter(Predicate<? super T> predicate);

 filter 使用 Predicate 进行判断, 从泛型可以看到,返回的的流的类型T, 就是  Predicate接收的参数类型。

流入类型,就是 流出类型

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

map 使用 Function 进行映射, 从泛型看到,返回的流的类型,是Function的返回值类型。而原流的类型是Function的参数类型。

使用Function 的 参数和返回值, 实现 流入类型到流出类型的映射。

Stream<T> sorted(Comparator<? super T> comparator);

传入一个比较器

Stream<T> limit(long maxSize);

传入一个 maxSize , 返回原流的一部分

void forEach(Consumer<? super T> action);

传入一个 Consumer , 只进不出。 对每一个输入流的 元素,执行动作。

ForkJoin 

工作窃取

将共组分组,每组都是一个双端队列, 当一个线程把自己的任务执行完以后,就去执行另一个队列中的任务,从反向执行。

推荐写法: 推荐使用 invokeAll 而不是 fork

public class Main {
    public static void main(String[] args) throws Exception {
        // 创建2000个随机数组成的数组:
        long[] array = new long[2000];
        long expectedSum = 0;
        for (int i = 0; i < array.length; i++) {
            array[i] = random();
            expectedSum += array[i];
        }
        System.out.println("Expected sum: " + expectedSum);
        // fork/join:
        ForkJoinTask<Long> task = new SumTask(array, 0, array.length);
        long startTime = System.currentTimeMillis();
        Long result = ForkJoinPool.commonPool().invoke(task);
        long endTime = System.currentTimeMillis();
        System.out.println("Fork/join sum: " + result + " in " + (endTime - startTime) + " ms.");
    }

    static Random random = new Random(0);

    static long random() {
        return random.nextInt(10000);
    }
}

class SumTask extends RecursiveTask<Long> {
    static final int THRESHOLD = 500;
    long[] array;
    int start;
    int end;

    SumTask(long[] array, int start, int end) {
        this.array = array;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        if (end - start <= THRESHOLD) {
            // 如果任务足够小,直接计算:
            long sum = 0;
            for (int i = start; i < end; i++) {
                sum += this.array[i];
                // 故意放慢计算速度:
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                }
            }
            return sum;
        }
        // 任务太大,一分为二:
        int middle = (end + start) / 2;
        System.out.println(String.format("split %d~%d ==> %d~%d, %d~%d", start, end, start, middle, middle, end));
        SumTask subtask1 = new SumTask(this.array, start, middle);
        SumTask subtask2 = new SumTask(this.array, middle, end);
        invokeAll(subtask1, subtask2);
        Long subresult1 = subtask1.join();
        Long subresult2 = subtask2.join();
        Long result = subresult1 + subresult2;
        System.out.println("result = " + subresult1 + " + " + subresult2 + " ==> " + result);
        return result;
    }
}

fork

 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));
    }


    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();
        }
    }

异步回调

        // 在这里开启异步。 该任务的执行,可以看作是另一个线程在执行,不会阻塞当前线程。
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
            int sum = 0;
            for(int i = 0; i < 10000000; i++){
                sum+= i;
            }
            int i = 10/1;
            return sum;
        });

        // 回调部分, 当上边异步完成后,在这里接收处理结果。 该位置也不是阻塞的
        // whenComplete  返回自身的实例。
        completableFuture.whenComplete((t, u) -> {
            // 结果处理。  t : 异步的返回值。 u : 错误信息
            System.out.println("t=>" + t); // 正常的返回结果
            System.out.println("u=>" + u); // 错误信息:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
            // 异常处理
        }).exceptionally((e) -> {
            System.out.println(e.getMessage());
            return 233; // 可以获取到错误的返回结果
        });

        // completableFuture.get()
CompletableFuture.supplyAsync : 有返回值的 开启 异步 的方法。 该方法不会阻塞当前线程。
CompletableFuture.runAsync    : 无返回值的异步。
whenComplete : 异步任务执行完之后将会执行。 不会阻塞当前线程。
exceptionally : 异步任务的异常处理, 不会阻塞当前线程。

以上4个任务的执行,都和当前线程无关。

completableFuture.get() : 该方法在当前先线程获取返回值, 会阻塞当前线程

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值