并发编程入门(五):Java并发包和Java8并发

72 阅读9分钟

目录

前言

JUC(Java.util.concurrent)

1.Java并发包之原子类

1.1.AtomicInteger

1.2.AtomicReference

1.3.AtomicStampedReference

 1.4.AtomicArray

2.Java并发包之工具类

2.1.CountDownLatch

2.2.CycilerBarrier

2.3.ExChanger

3.Java并发包之并发容器

3.1.BlockQueue

3.2.阻塞式写方法

 3.3.非阻塞式写方法

3.4.阻塞式读方法

3.5.非阻塞式读方法

 3.6.实现生产者-消费者

4.小结 

Java8与并发

1.并行流与并行排序

1.1.使用并行流过滤数据

 1.2.从集合中获取并行流

2.增强的Future:CompletableFuture

2.1.完成了就通知我

 2.2.异步执行任务

 2.3.流式调用

2.4.处理异常

3.小结 

多线程究竟有什么用?


前言

本文是《Java并发视频入门》视频课程的笔记总结,旨在帮助更多同学入门并发编程。

本系列共五篇博客,本篇博客着重聊并发编程工具类JUC、Java8与并发。

侵权删。原视频地址:

【多线程】Java并发编程入门_哔哩哔哩_bilibili本课程是Java并发编程入门版课程,适合基础薄弱的同学进行学习。如需完整版请移步腾讯课堂进行购买:https://ke.qq.com/course/3486171?tuin=5e1f405a更多优质课程请上腾讯课堂搜索“雷俊华老师”:https://ke.qq.com/course/3486171?tuin=5e1f405ahttps://www.bilibili.com/video/BV1a5411u7o7?p=1

博客汇总地址: 

《并发编程入门》总结篇_勤修的博客-CSDN博客本文是《Java并发视频入门》视频课程的笔记总结,旨在帮助更多同学入门并发编程。本系列共五篇博客,此文为五篇博客的汇总篇。https://kevinhe.blog.csdn.net/article/details/125143179

JUC(Java.util.concurrent)

并发编程使用的工具类,除了线程池、显式锁,还包括原子类、工具类和容器类。

1.Java并发包之原子类

volatile修饰基本/引用类型后,具备了可见性和有序性,但无法保证原子性。原子性可通过Sychronized关键字来保证。执行简单操作加锁过于大材小用。

比如执行i++,可以通过原子类工具集。比如AtomicInteger,这些类是线程安全的。

1.1.AtomicInteger

AtomicInteger是线程安全的,除此之外,还有AtomicBoolean、AtomicLong。

线程不安全代码如下:

public class Test1 {
    private static int value = 0;
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        System.out.println(value++);
                        try {
                            TimeUnit.MILLISECONDS.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }
}
结果输出:
0
...
10
12
11
11
15
15
...

 优化后的线程安全代码:

public class Test1 {
    private static AtomicInteger value = new AtomicInteger(0);
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        System.out.println(value.addAndGet(2));
//                        System.out.println(value.getAndIncrement());
                        try {
                            TimeUnit.MILLISECONDS.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }
}
结果输出:
2
6
4
8
10
12
...

1.2.AtomicReference

针对对象引用的非阻塞原子性操作。

应用场景:个人银行账号。几点要求:1.个人账号被设计为不可变对象;2.只包含账号名、现金;3.便于验证,资金只增不减。

假设此时有十个人往这个账号打钱,每个人每次打10元,多线程来实现。

线程不安全代码:

public class Test2 {
    private static volatile BankAccount bankAccount = new BankAccount("kevin", 0);

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()-> {
                // 读取引用
                final BankAccount account = bankAccount;
                // 创建新对象
                BankAccount newAccount = new BankAccount(account.getAccount(), account.getAmount() + 10);
                System.out.println(newAccount);
                bankAccount = newAccount;
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

}


class BankAccount {
    private final String account;
    private final int amount;
    public BankAccount(String account, int amount) {
       this.account = account;
       this.amount = amount;
    }

    public int getAmount() {
        return amount;
    }

    public String getAccount() {
        return account;
    }

    @Override
    public String toString() {
        return "BankCount{" +
                "account='" + account + '\'' +
                ", amount=" + amount +
                '}';
    }
}
结果输出:
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=10}

 线程安全代码,但仍有问题:

public class Test2 {
    private static AtomicReference<BankAccount> reference = new AtomicReference<>(new BankAccount("kevin", 0));

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                // 读取引用
                final BankAccount account = reference.get();
                // 创建新对象
                BankAccount newAccount = new BankAccount(account.getAccount(), account.getAmount() + 10);
                // CAS更换
                if (reference.compareAndSet(account, newAccount)) {
                    System.out.println(newAccount);
                }
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}


class BankAccount {
    private final String account;
    private final int amount;

    public BankAccount(String account, int amount) {
        this.account = account;
        this.amount = amount;
    }

    public int getAmount() {
        return amount;
    }

    public String getAccount() {
        return account;
    }

    @Override
    public String toString() {
        return "BankCount{" +
                "account='" + account + '\'' +
                ", amount=" + amount +
                '}';
    }
}
结果输出:
BankCount{account='kevin', amount=40}
BankCount{account='kevin', amount=60}
BankCount{account='kevin', amount=10}
BankCount{account='kevin', amount=20}
BankCount{account='kevin', amount=30}
BankCount{account='kevin', amount=50}
BankCount{account='kevin', amount=80}
BankCount{account='kevin', amount=70}

1.3.AtomicStampedReference

ABA问题:一开始是A、后来变成B,再后来又变成A。那么CAS可能没法进行辨别,如何避免?加入版本号概念,使用AtomicStampedReference。

public class Test3 {
    private static AtomicStampedReference<String> reference = new AtomicStampedReference<>("Hello", 0);

    public static void main(String[] args) {
        reference.compareAndSet("Hello", "world", 0, 1);
        String r = Test3.reference.getReference();
        System.out.println(r);
    }
}
结果输出:
world

 1.4.AtomicArray

原子性操作数组,比如AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray。

public class Test4 {
    public static void main(String[] args) {
        int []array = {1,2,3,4,5,6,7,8,9};
        AtomicIntegerArray integerArray = new AtomicIntegerArray(array);
        int andIncrement = integerArray.getAndIncrement(0);
        System.out.println(andIncrement);
        System.out.println(integerArray);
    }
}
结果输出:
1
[2, 2, 3, 4, 5, 6, 7, 8, 9]

2.Java并发包之工具类

2.1.CountDownLatch

子任务均结束后,当前主任务才会进入下一个阶段。

类似于倒计时阀门,有一个门阀等待倒计数,计数器为0时才打开。

eg:计算商品价格。

public class Test5 {
    public static void main(String[] args) throws InterruptedException {
        int[] productIds = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        List<ProducePrice> priceList = Arrays.stream(productIds).mapToObj(ProducePrice::new).collect(Collectors.toList());
        //创建CountDownLatch,容量为子任务数量
        CountDownLatch latch = new CountDownLatch(priceList.size());
        //创建子任务
        priceList.forEach( p -> {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(p.getProductId() + "开始计算价格");
                        TimeUnit.SECONDS.sleep(new Random().nextInt(10));
                        p.setPrice(p.getProductId() * 0.9D);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        //计数器减一,子任务减一。
                        latch.countDown();
                    }
                }
            }).start();
        });
        //await开始等待,主任务阻塞,直到latch.countDown()=0
        latch.await();
        System.out.println("所有价格计算完毕");
        priceList.forEach(System.out::println);
    }

    private static class ProducePrice {
        private int productId;

        private double price;

        public ProducePrice(int productId) {
            this.productId = productId;
        }


        public ProducePrice(int productId, double price) {
            this.price = price;
            this.productId = productId;
        }

        public void setPrice(double price) {
            this.price = price;
        }

        public double getPrice() {
            return price;
        }

        public int getProductId() {
            return productId;
        }

        @Override
        public String toString() {
            return "ProducePrice{" +
                    "productId=" + productId +
                    ", price=" + price +
                    '}';
        }
    }
}
结果输出:
4开始计算价格
9开始计算价格
10开始计算价格
3开始计算价格
1开始计算价格
5开始计算价格
7开始计算价格
6开始计算价格
8开始计算价格
2开始计算价格
所有价格计算完毕
ProducePrice{productId=1, price=0.9}
ProducePrice{productId=2, price=1.8}
ProducePrice{productId=3, price=2.7}
ProducePrice{productId=4, price=3.6}
ProducePrice{productId=5, price=4.5}
ProducePrice{productId=6, price=5.4}
ProducePrice{productId=7, price=6.3}
ProducePrice{productId=8, price=7.2}
ProducePrice{productId=9, price=8.1}
ProducePrice{productId=10, price=9.0}

2.2.CycilerBarrier

循环屏障,允许多个线程在执行完相应的操作后彼此等待共同到达一个障点。与CountDownLatch类似,有一个不同点:CountDownLatch不可重复利用,CycilerBarrier可重复利用。

快速上手

与CountDownLatch的“快速上手”类似。

public class Test6 {
    public static void main(String[] args) {
        int[] productIds = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        List<ProducePrice> priceList = Arrays.stream(productIds).mapToObj(ProducePrice::new).collect(Collectors.toList());
        //参数为分片,而非计数器。
        CyclicBarrier barrier = new CyclicBarrier(priceList.size());
        List<Thread> threadList = new ArrayList<>();
        priceList.forEach(p -> {
            Thread thread = new Thread(() -> {
                try {
                    System.out.println(p.getProductId() + "开始计算价格");
                    TimeUnit.SECONDS.sleep(new Random().nextInt(10));
                    p.setPrice(p.getProductId() * 0.9D);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //等待其他子任务也执行到这个障点
                    try {
                        barrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            });
            threadList.add(thread);
            thread.start();
        });
        threadList.forEach( t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println("所有价格计算完毕");
        priceList.forEach(System.out::println);
    }


    private static class ProducePrice {
        private int productId;

        private double price;

        public ProducePrice(int productId) {
            this.productId = productId;
        }


        public ProducePrice(int productId, double price) {
            this.price = price;
            this.productId = productId;
        }

        public void setPrice(double price) {
            this.price = price;
        }

        public double getPrice() {
            return price;
        }

        public int getProductId() {
            return productId;
        }

        @Override
        public String toString() {
            return "ProducePrice{" +
                    "productId=" + productId +
                    ", price=" + price +
                    '}';
        }
    }
}
结果输出:
7开始计算价格
2开始计算价格
9开始计算价格
5开始计算价格
10开始计算价格
1开始计算价格
4开始计算价格
3开始计算价格
8开始计算价格
6开始计算价格
所有价格计算完毕
ProducePrice{productId=1, price=0.9}
ProducePrice{productId=2, price=1.8}
ProducePrice{productId=3, price=2.7}
ProducePrice{productId=4, price=3.6}
ProducePrice{productId=5, price=4.5}
ProducePrice{productId=6, price=5.4}
ProducePrice{productId=7, price=6.3}
ProducePrice{productId=8, price=7.2}
ProducePrice{productId=9, price=8.1}
ProducePrice{productId=10, price=9.0}

循环使用

无需显式重置,内部到零后重置。需要确保能被整除完。

旅游团上车之前(障点)清点人数,到达站点之后(障点)再次清点人数,写个程序模拟下。

public class Test7 {
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(4);
        for (int i = 0; i < 3; i++) {
            new Thread(new Tourist(i, cyclicBarrier)).start();
        }
        cyclicBarrier.await();
        System.out.println("所有人已经上车");
        cyclicBarrier.await();
        System.out.println("所有人已经下车");
    }

    private static class Tourist implements Runnable {

        private final int id;
        private final CyclicBarrier barrier;

        public Tourist(int id, CyclicBarrier barrier) {
            this.id = id;
            this.barrier = barrier;
        }

        @Override
        public void run() {
            System.out.println("游客" + id + "上车");
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("游客" + id + "已经上车,等待其他人上车");
            try {
                barrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println("游客" + id + "等待上车结束");
            try {
                TimeUnit.SECONDS.sleep(new Random().nextInt(10));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("游客" + id + "已经下车,等待其他人下车");
            try {
                barrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println("游客" + id + "等待下车结束");
        }
    }
}
结果输出:
游客1上车
游客2上车
游客0上车
游客2已经上车,等待其他人上车
游客0已经上车,等待其他人上车
游客1已经上车,等待其他人上车
所有人已经上车
游客1等待上车结束
游客0等待上车结束
游客2等待上车结束
游客1已经下车,等待其他人下车
游客0已经下车,等待其他人下车
游客2已经下车,等待其他人下车
所有人已经下车
游客2等待下车结束
游客0等待下车结束
游客1等待下车结束

与CountDownLatch的区别:

CountDownLatch:一个线程,等待另外N个线程执行完后才能执行;(一个线程等待多个线程)

CycilerBarrier:N个线程相互等待,任何一个线程完成之前,所有线程必须等待。(多个线程互相等待)

Phaser:

可重复使用的同步屏障,功能非常类似于CycilerBarrier和CountDownLatch的合集。解决了CountDownLatch手动重置的问题;解决了CycilerBarrier一旦制定Size无法改变的问题。一般不建议使用。

2.3.ExChanger

ExChanger简化了两个线程间的数据交互,提供了两个线程的数据交换点。交换两个线程提供给对方的数据。

 快速上手:可以看做生产者-消费者模式的实现,关注重点是数据交换。等待3s后交换。

public class Test9 {
    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger<>();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T1启动了");
                try {
                    TimeUnit.SECONDS.sleep(1);
                    String data = exchanger.exchange("我是T1数据");
                    System.out.println("从T2获取的数据" + data);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T2启动了");
                try {
                    TimeUnit.SECONDS.sleep(3);
                    String data = exchanger.exchange("我是T2数据");
                    System.out.println("从T1获取的数据" + data);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }).start();
    }
}
结果输出:
T1启动了
T2启动了
从T1获取的数据:我是T1数据
从T2获取的数据:我是T2数据

3.Java并发包之并发容器

3.1.BlockQueue

元素数量存在界限,队列满时,写操作线程将会被阻塞挂起,队列空时,读操作线程将会被阻塞挂起。类似于生产者-消费者。内部实现主要依赖于显式Lock和Condition。

ArrayBlockQueue是基于数组实现的FIFO阻塞队列,分为两种不同写法:阻塞式和非阻塞式。

3.2.阻塞式写方法

Put:阻塞写操作;Offer:阻塞写,但有超时时间。

public class Test12 {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
        //写操作,若超时1s,即超过指定时间,则写入失败
        boolean a = queue.offer("a", 1, TimeUnit.SECONDS);
        System.out.println(a);
        boolean b = queue.offer("b", 1, TimeUnit.SECONDS);
        System.out.println(b);
        boolean c = queue.offer("c", 1, TimeUnit.SECONDS);
        System.out.println(c);
    }

    public static void test1(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
        //写操作, 尾部写入。
        queue.put("a");
        System.out.println("a添加成功");
        queue.put("b");
        System.out.println("b添加成功");
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(3);
                    //读操作
                    queue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        queue.put("c");
        System.out.println("c添加成功");
    }
}
结果输出:
case1:put操作
a添加成功
b添加成功

case1:put操作后take,类似于test1
a添加成功
b添加成功
c添加成功

case2:offer(XX)操作
true
true
false

 3.3.非阻塞式写方法

offer/add:非阻塞式写

public class Test12 {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
        //写操作
        boolean a = queue.offer("a");
        System.out.println(a);
        boolean b = queue.offer("b");
        System.out.println(b);
        boolean c = queue.offer("c");
        System.out.println(c);
    }

    public static void test3(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
        //写操作, 尾部写入。
        queue.add("a");
        System.out.println("a添加成功");
        queue.add("b");
        System.out.println("b添加成功");
        queue.add("c");
        System.out.println("c添加成功");
    }
}
结果输出:
case1:offer方法
true
true
false

case1:put方法
a添加成功
b添加成功
Exception in thread "main" java.lang.IllegalStateException: Queue full
        at java.base/java.util.AbstractQueue.add(AbstractQueue.java:98)
        at java.base/java.util.concurrent.ArrayBlockingQueue.add(ArrayBlockingQueue.java:329)
        at com.company.unit9.Test12.main(Test12.java:25)

3.4.阻塞式读方法

take:头部获取数据并移除,当队列为空时,会进入阻塞。

poll:超时时间后,退出阻塞。

public class Test13 {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
        //写操作, 尾部写入。
        queue.add("a");
        queue.add("b");
        String a = queue.poll(1, TimeUnit.SECONDS);
        System.out.println(a);
        String b = queue.poll(1, TimeUnit.SECONDS);
        System.out.println(b);
        String c = queue.poll(1, TimeUnit.SECONDS);
        System.out.println(c);
    }
    public static void test1(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
        //写操作, 尾部写入。
        queue.add("a");
        queue.add("b");
        String a = queue.take();
        System.out.println(a);
        String b = queue.take();
        System.out.println(b);
        new Thread(()-> {
            try {
                TimeUnit.SECONDS.sleep(1);
                queue.offer("c");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }).start();
        String c = queue.take();
        System.out.println(c);
    }
}
结果输出:
case1:poll(X,X)方法
a
b
null

case2:take方法,test1
a
b
c

3.5.非阻塞式读方法

poll:非阻塞式读方法,移除数据;

peek:非阻塞式读方法,不会移除数据。

public class Test13 {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
        //写操作, 尾部写入。
        queue.add("a");
        queue.add("b");
        String a = queue.peek();
        System.out.println(a);
        String b = queue.peek();
        System.out.println(b);
        String c = queue.peek();
        System.out.println(c);
    }

    public static void test3(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
        //写操作, 尾部写入。
        queue.add("a");
        queue.add("b");
        String a = queue.poll();
        System.out.println(a);
        String b = queue.poll();
        System.out.println(b);
        String c = queue.poll();
        System.out.println(c);
    }

}
结果输出:
case1:peek方法
a
a
a

case2:poll方法
a
b
null

 3.6.实现生产者-消费者

public class Test14 {
    public static void main(String[] args) {
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                while (true) {
                    String s = System.currentTimeMillis() + "";
                    queue.offer(s);
                    System.out.println(Thread.currentThread() + "生产数据" + s);
                    try {
                        TimeUnit.SECONDS.sleep(new Random().nextInt(5));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                while (true) {
                    try {
                        String s = queue.take();
                        System.out.println(Thread.currentThread() + "读取数据" + s);
                        TimeUnit.SECONDS.sleep(new Random().nextInt(5));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }).start();
        }
    }
}
结果输出:
Thread[Thread-7,5,main]生产数据1654479198979
Thread[Thread-3,5,main]生产数据1654479198977
Thread[Thread-15,5,main]读取数据1654479198981
Thread[Thread-12,5,main]读取数据1654479198977
Thread[Thread-11,5,main]读取数据1654479198977
Thread[Thread-17,5,main]读取数据1654479198978
Thread[Thread-0,5,main]生产数据1654479198977
Thread[Thread-15,5,main]读取数据1654479199013
Thread[Thread-6,5,main]生产数据1654479198978

4.小结 

本节我们介绍了Java并发包。共分为:原子类(AtomicInteger、AtomicReference、AtomicStampedReference和AtomicArray)、工具类(CountDownLatch、CycilerBarrier和ExChanger)和并发容器(基于ArrayBlockingQueue的阻塞/非阻塞写、阻塞/非阻塞读、生产者-消费者模型)。

Java8与并发

1.并行流与并行排序

1.1.使用并行流过滤数据

public class Test1 {
    public static void main(String[] args) {
        //流式编程,串行计算
        long time1 = System.currentTimeMillis();
        long count = IntStream.range(1, 10000000).filter(PrimUtils::isPrime).count();
        long time2 = System.currentTimeMillis();
        System.out.println(count);

        //流式编程,并行流,性能会有提升。
        long parallelCount = IntStream.range(1, 10000000).parallel().filter(PrimUtils::isPrime).count();
        long time3 = System.currentTimeMillis();
        System.out.println(parallelCount);
        System.out.println(time2 - time1);
        System.out.println(time3- time2);

    }
    private static class PrimUtils {
        public static boolean isPrime(int number) {
            int tmp =  number;
            if (tmp < 2) {
                return false;
            }
            for (int i = 2; Math.sqrt(tmp) >= i; i++) {
                if (tmp % i == 0) {
                    return false;
                }
            }
            return true;
        }
    }
}
结果输出:
664579
664579
5476
869

 1.2.从集合中获取并行流

public class Test2 {

    public static void main(String[] args) {
       List<Student> students = new ArrayList<>();
        for (int i = 0; i < 1000000; i++) {
            students.add(new Student(new Random().nextInt(100)));
        }
        //串行流,lambda + 流式编程
        double score = students.stream().mapToInt(s -> s.score).average().getAsDouble();
        System.out.println(score);
        //并行流:parallelStream
        double score2 = students.parallelStream().mapToInt(s -> s.score).average().getAsDouble();
        System.out.println(score2);
    }

    private static class Student {
        private int score;

        private Student(int score) {
            this.score = score;
        }
    }
}
结果输出:
49.546835
49.546835

 还有并行排序,从Arrays.sort改为Arrays.parallelSort()。

2.增强的Future:CompletableFuture

2.1.完成了就通知我

public class Test5 {
    public static void main(String[] args) throws InterruptedException {
        CompletableFuture<Integer> completableFuture = new CompletableFuture<>();
        new Thread(new AskThread(completableFuture)).start();
        TimeUnit.SECONDS.sleep(1);
        completableFuture.complete(60);
    }

    public static class AskThread implements Runnable {
        CompletableFuture<Integer> re = null;

        public AskThread(CompletableFuture<Integer> re) {
            this.re = re;
        }

        @Override
        public void run() {
            int myRe = 0;
            try {
                //get作用:阻塞到complete执行完毕,
                myRe = re.get() * re.get();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(myRe);
        }
    }
}
结果输出:
3600

 2.2.异步执行任务

public class Test51 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            //异步执行,新建线程
           return calc(50);
        });
        //调用get会阻塞
        System.out.println(future.get());
    }

    private static Integer calc(Integer param) {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return param * param;
    }
}
结果输出:
2500

 2.3.流式调用

public class Test6 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //链式调用
        CompletableFuture<Void> future = CompletableFuture.supplyAsync(() ->
            //异步执行,新建线程
           calc(50)
        ).thenApply(e -> {
            System.out.println("在supply Async中处理的值是:" + e);
            return Integer.toString(e);
        }).thenApply(e -> {
            System.out.println("在thenApply中处理的值是:" + e);
            return "\""+ e + "\"";
        }).thenAccept(System.out::println);
        //调用get阻塞,直到CompletableFuture所有任务执行完毕为止。
        future.get();
    }

    private static Integer calc(Integer param) {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return param * param;
    }
}
结果输出:
在supply Async中处理的值是:2500
在thenApply中处理的值是:2500
"2500"

2.4.处理异常

public class Test6 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //链式调用
        CompletableFuture<Void> future = CompletableFuture.supplyAsync(() ->
            //异步执行,新建线程
           calc(50)
        ).exceptionally(ex -> {
            System.out.println("计算出现异常" + ex);
            System.out.println("即将返回零");
            return 0;
        }).thenApply(e -> {
            System.out.println("在supply Async中处理的值是:" + e);
            return Integer.toString(e);
        }).thenApply(e -> {
            System.out.println("在thenApply中处理的值是:" + e);
            return "\""+ e + "\"";
        }).thenAccept(System.out::println);
        //调用get阻塞,直到CompletableFuture所有任务执行完毕为止。
        future.get();
    }

    private static Integer calc(Integer param) {
        return param / 0;
    }
}
结果输出:
计算出现异常java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
即将返回零
在supply Async中处理的值是:0
在thenApply中处理的值是:0
"0"

3.小结 

本节我们介绍了并行流与并行排序、增强的Future:CompletableFuture。

多线程究竟有什么用?

针对百万数据一次性导出、调用高德接口进行路径处理推荐三条路线、上千用户同时导出数据等场景,多线程、线程池(配置两个线程池,一个线程池提供给普通用户,一个线程池提供给VIP用户)会提高执行效率。