JUC系列学习笔记(二)

89 阅读15分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情

JUC学习笔记(二)

JUC学习笔记的第二部分主要记录了集合类的安全问题、Callable接口、JUC的三大辅助类、读写锁、阻塞队列、线程池等知识点

集合类安全问题

List不安全

List并发测试

可能会报错:java.util.ConcurrentModificationException 并发修改异常。

解决方案

1、使用Vector,Vector默认是安全的,通过synchronized同步方法实现,效率较低,比较古老(不推荐),在ArrayList之前就有了, 说明官方并不想使用Vector来解决ArrayList导致的并发问题,而是引入新的解决方式。

2、使用Collections工具类提供的同步转换方法:List<String> list = Collections.synchronizedList(new ArrayList<>());

3、使用JUC下的线程安全集合:List<String> list = new CopyOnWriteArrayList<>();效率比Vecotr高

代码示例:

public class ListTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        // List<String> list = Collections.synchronizedList(new ArrayList<>());
        // CopyOnWrite  写入时复制  存在多个线程时,读取时是固定的,写入时,需要先复制之前的内容给写入者,再进行统一写入,保证线程安全
        // List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
        // 并发测试结束
    }
}

Set不安全

Set并发测试

可能会报错:java.util.ConcurrentModificationException 并发修改异常

解决方案

1、使用Collections工具类提供的同步转换方法:Set<String> set = Collections.synchronizedSet(new HashSet<>());

2、使用JUC下的线程安全集合:Set<String> set = new CopyOnWriteArraySet<>();

代码示例:

public class SetTest {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
        // 并发测试结束
    }
}

HashMap不安全

Map并发测试

可能会报错:java.util.ConcurrentModificationException 并发修改异常

解决方案

1、使用Collections工具类提供的同步转换方法:Map<String, String> hashMap = Collections.synchronizedMap(new HashMap<>());

2、使用JUC下的线程安全集合:Map<String, String> hashMap = new ConcurrentHashMap<>();

代码示例:

public class HashMapTest {
    public static void main(String[] args) {
        
        Map<String, String> hashMap = new HashMap<>();

        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                hashMap.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
                System.out.println(hashMap);
            }, String.valueOf(i)).start();
        }
    }
}

Callable接口

Callable接口可以有返回值,可以抛出异常,方法是call;

new Thread()无法直接调用实现Callable接口的对象,需要进行一个转换才可以(一个适配器)FutureTask类,该类实现了Runnable方法,可以通过new Thread(Runnable e) 方式来传入线程对象。查看源码,FutureTask可以接收一个Callable接口类型的参数,从而实现Callable接口与new Thread()关联起来。

代码示例:

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 定义一个FutureTask,这里会根据lamda表达式自动识别泛型的类型
        FutureTask<String> futureTask = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName() + "线程" + "执行了call方法");
            return "Callable";
        });
        new Thread(futureTask, "A").start();
        // 如果多一个线程B,只有第一次A才会执行,B不执行,这里只会输出一个call
        // FutureTask有一个state,state为NEW时才能保证Callable的可见性,A执行过依次之后,state会改变,Callable将不可见,B不执行
        new Thread(futureTask, "B").start();
        // 获得Callable接口call方法的返回值,这个get方法可能会被阻塞(比如call方法中有延迟),通常放在代码最后
        System.out.println(futureTask.get());
    }
}

JUC的三大辅助类

CountDownLatch、CyclicBarrier、Semaphore三大辅助类,都是用来计数的

CountDownLatch

辅助计时,主要是countDown方法和await方法配合使用,可以查看源码中的使用示例

代码示例:

public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        // 定义一个计数器(源码中有使用示例),倒计时,并初始化count
        CountDownLatch countDownLatch = new CountDownLatch(6);
        // 开启6个线程
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "线程执行了");
                // 计数器减1,这个方法不会被阻塞
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }
        // 直到计数器为0,才继续向下执行,否则一直等待
        countDownLatch.await();
        // 利用倒计时,这里所有线程执行完毕之后才会输出
        System.out.println("所有线程执行完毕");
    }
}

CyclicBarrier

源码中有使用示例,本质还是减法计数器

关键字:公共屏障点,就类似与CountDownLatch的count

代码示例:

public class CyclicBarrierTest {
    public static void main(String[] args) {
        /**
         * 集7颗龙珠召唤神龙
         */
        // 定义一个计数器,并初始化参数,第一个为公共屏障点的值
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("召唤神龙");
        });

        for (int i = 1; i <= 7; i++) {
            // lamda表达式中不能调用外面的普通变量,需要是一个final变量,这里不能访问i,使用一个临时变量finalI,这里会自动添加final
            int finalI = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "收集第" + finalI + "龙珠");
                try {
                    // 直到达到公共屏障点之后才会继续向下执行,这里CyclicBarrier的底层实现是--count,和CountDownLatch类似,本质还是减法计数器
                    // 这个方法要放在run()方法里面执行,和CountDownLatch.await()不同
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}

Semaphore

计数信号量。 从概念上讲,信号量维护一组许可。 如有必要,每个acquire块直到许可可用,然后获取它。 每个release增加一个许可,可能会释放一个阻塞的收单方。 但是,没有使用实际的许可对象; Semaphore只是计算可用的数量并相应地采取行动。 信号量通常用于限制可以访问某些(物理或逻辑)资源的线程数。

代码示例:

public class SemaphoreTest {
    public static void main(String[] args) {
        /**
         * 抢车位模拟,有限资源,多个线程,限流时使用
         */
        // 定义一个信号量,车位数量,限流
        Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                try {
                    // 获得资源,得到车位
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "抢到车位");
                    // 停留2秒
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName() + "离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 释放资源,车子离开,让出车位
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
    }
}

ReadWriteLock - 读写锁

读锁:共享锁 写锁:独享锁、排他锁

某一时刻可以别多个线程同时读,但某一时刻只能被一个线程写。 3种情况:1、读-读 可以共存;2、读—-写 不能共存;3、写-写 不能共存

存在问题: 写入中间会出现插入现象,比如1插入未成功,同时又插入5,会出现混乱,这里读写的先后顺序不重要

资源类:

/**
 * 如果使用synchronized关键字,则不能实现多个读一个写,synchronized锁的是对象(非静态同步方法时)或者类(Class,静态同步方法时)
 */
public class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();

    // 存,写,某一时刻只能有一个线程写
    public void put(String key, Object value) {
        System.out.println(Thread.currentThread().getName() + "写入" + key);
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "写入OK");
    }

    // 取,读,某一时刻可以有多个线程读
    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "读取" + key);
        System.out.println(map.get(key));
        System.out.println(Thread.currentThread().getName() + "读取OK");
    }
}

测试类:

public class ReadWriteLockTest {
    public static void main(String[] args) {
        // 资源类
        MyCache myCache = new MyCache();
        // 开启多条线程测试,写
        for (int i = 0; i < 6; i++) {
            int finalI = i;
            new Thread(() -> {
                myCache.put(finalI + "", finalI + "");
            }, String.valueOf(i)).start();
        }
        // 开启多条线程测试,读
        for (int i = 0; i < 6; i++) {
            int finalI = i;
            new Thread(() -> {
                myCache.get(finalI + "");
            }, String.valueOf(i)).start();
        }
    }
}

输出内容如下:

输出:2写入2、5写入5、0写入0、1写入1、4写入4、3写入3、3写入OK、2写入OK、0写入OK、1写入OK、5写入OK、4写入OK

解决方法: 给资源类添加读写锁,写入未完成不会再有其他写入插队

修改后的资源类:

/**
 * 如果使用synchronized关键字,则不能实现多个读一个写,synchronized锁的是对象(非静态同步方法时)或者类(Class,静态同步方法时)
 */
public class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();
    // 定义一个读写锁,颗粒度更细,锁的内容仅是lock和unlock包围的代码块
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    // 存,写,某一时刻只能有一个线程写
    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);
            System.out.println(map.get(key));
            System.out.println(Thread.currentThread().getName() + "读取OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 解锁
            readWriteLock.readLock().unlock();
        }
    }
}

阻塞队列——BlockingQueue

是Collection下的Queue的一个实现类,本质上还是属于集合

阻塞

写入时,如果队列满则需要阻塞,等待有空余位置之后才能继续写入 读取时,如果对空则需要阻塞,等待队列中有元素之后才能读取

阻塞队列

必须设置队列大小

实现类如图: BlockingQueue.png

应用场景

多线程并发处理、线程池

四组API

对队列进行添加、删除、查队首等操作时,采用不同的方式会有以下四种情况:

1、抛出异常 代码示例:

    /**
 * 报异常,这里使用的是add和remove方法,element查看队首元素
 */
public static void expectionTest(){
        // 定义一个由数组支持的阻塞队列
        BlockingQueue BlockingQueue=new ArrayBlockingQueue<>(2);
        // 添加元素
        BlockingQueue.add("a");
        BlockingQueue.add("b");
        // 队列大小之外额外添加元素,会报异常:IllegalStateException: Queue full  队满
        // BlockingQueue.add("c");

        // 查看队首元素
        System.out.println(BlockingQueue.element());

        // 删除队首元素
        BlockingQueue.remove();
        BlockingQueue.remove();
        // 对空之后再删除,报异常:NoSuchElementException 队空,没有元素
        // BlockingQueue.remove();
        }

2、不抛出异常 代码示例:

/**
 * 不抛异常,这里使用的是offer(添加元素),poll(弹出元素),peek查看队首元素
 */
public static void noExpectionTest(){
        // 定义一个由数组支持的阻塞队列
        BlockingQueue blockingQueue=new ArrayBlockingQueue<>(2);
        // 添加元素
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        // 队列大小之外额外添加元素,会返回false,但不抛出异常
        System.out.println(blockingQueue.offer("c"));

        // 查看队首元素
        System.out.println("队首元素:"+blockingQueue.peek());

        // 删除元素
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        // 为空后继续删除元素,返回null值,但是不会抛出异常
        System.out.println(blockingQueue.poll());
  }

3、阻塞等待

一直阻塞

代码示例:

/**
 * 阻塞等待,这里用的是put(放),take(取)
 */
public static void blockWaitTest(){
        BlockingQueue blockingQueue=new ArrayBlockingQueue<>(2);

        try{
        // 添加元素
        blockingQueue.put("a");
        blockingQueue.put("b");

        // 队满之后继续添加元素,则会持续等待,不报错也没有返回值
        // blockingQueue.put("c");

        // 取出元素
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());

        // 队空之后继续取出元素,则会阻塞,持续等待
        System.out.println(blockingQueue.take());
        }catch(InterruptedException e){
        e.printStackTrace();
        }
        }

4、超时等待

等待过一段时间之后,不再等待

代码示例:

/**
 * 超时等待,这里使用的是携带参数的offer(value,时长数值,时长单位)和poll
 */
public static void timeoutWaitTest(){
        BlockingQueue blockingQueue=new ArrayBlockingQueue<>(2);

        try{
        // 添加元素,offer的三个参数分别为:要放进队列的值,超时时长数值,超时时长单位(时、分、秒等)
        System.out.println(blockingQueue.offer("a",2,TimeUnit.SECONDS));
        System.out.println(blockingQueue.offer("b",2,TimeUnit.SECONDS));
        // 额外插入,超时2秒之后结束等待,上面可以不添加超时等待的参数,但如果要超出队列大小,使用超市等待策略就要添加参数
        System.out.println(blockingQueue.offer("c",2,TimeUnit.SECONDS));

        // 取元素
        System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
        System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
        // 队空时取元素,如果使用超市等待策略,则需要添加参数:时长数值,时长单位
        System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
        }catch(InterruptedException e){
        e.printStackTrace();
        }
        }

同步队列——SynchronousQueue

不需要设置队列大小,放进去一个元素必须取出来才能继续存放元素,不能连续两次存操作

代码示例:

public class SynchronousQueueTest {
    public static void main(String[] args) {
        // 定义一个同步队列
        SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();
        // 存元素线程
        new Thread(() -> {
            try {
                // 存放元素
                synchronousQueue.put("1");
                System.out.println(Thread.currentThread().getName() + "存放了1");
                synchronousQueue.put("2");
                System.out.println(Thread.currentThread().getName() + "存放了2");
                synchronousQueue.put("3");
                System.out.println(Thread.currentThread().getName() + "存放了3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "PUT").start();

        // 取元素线程
        new Thread(() -> {
            try {
                // 模拟延时
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + "取出来" + synchronousQueue.take());
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + "取出来" + synchronousQueue.take());
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + "取出来" + synchronousQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "TAKE").start();
    }
}

线程池(重点)

池化技术

能够优化资源的使用,事先准备好资源放到池子里,随用随取,用完就放回池子里,比如有数据库连接池(减少数据库连接开关浪费资源)、线程池

线程池

3大方法、7大参数、4中拒绝策略

好处:

  • 降低资源消耗,频繁启动或杀死线程浪费CPU资源
  • 提高响应速度,使用池子中已经存在的线程提高响应速度
  • 方便管理多个线程

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

3大方法

使用Executors创建线程池的3大方法,三大方法的本质都是通过ThreadPoolExecutor构造线程池对象

/**
 * Executors可以认为是一个工具类,用于创建线程池,但阿里Java开发规范不建议使用这种方式创建
 * 推荐使用底层的ThreadPoolExecutor进行创建,能够更好的控制各个参数
 */
public class ExecutorTest {
    public static void main(String[] args) {
        // 创建一个只有一条线程的线程池
        // ExecutorService threadPool = Executors.newSingleThreadExecutor();
        // 创建一个固定大小的线程池,大小由传入的参数限定
        // ExecutorService threadPool = Executors.newFixedThreadPool(5);
        // 创建一个可伸缩大小的线程池,和CPU性能有关,如果池中有空闲线程则从池中拿,若没有则创建新的线程并放入池中
        ExecutorService threadPool = Executors.newCachedThreadPool();

        try {
            for (int i = 0; i < 50; i++) {
                // 启动线程,execute()方法中传入的依然是一个Runnable接口
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "线程OK");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 确保程序运行完毕(不再使用线程池)之后,关闭线程池
            threadPool.shutdown();
        }

    }
}

7大参数

7大参数就是ThreadPoolExecutor构造方法的7个参数

ThreadPoolExecutor源码:

/**
 * 使用给定的初始参数创建一个新的ThreadPoolExecutor 。
 * 参数:
 * corePoolSize – 要保留在池中的线程数,即使它们处于空闲状态,除非设置了allowCoreThreadTimeOut
 * maximumPoolSize – 池中允许的最大线程数
 * keepAliveTime – 当线程数大于核心数时,这是多余空闲线程在终止前等待新任务的最长时间。
 * unit – keepAliveTime参数的时间单位
 * workQueue – 用于在执行任务之前保存任务的队列。 这个队列将只保存execute方法提交的Runnable任务。
 * threadFactory – 执行程序创建新线程时使用的工厂
 * handler – 执行被阻塞时使用的处理程序,因为达到了线程边界和队列容量
 * 抛出:
 * IllegalArgumentException – 如果以下情况之一成立: corePoolSize < 0 keepAliveTime < 0 maximumPoolSize <= 0 maximumPoolSize < 
 corePoolSize
 * NullPointerException – 如果workQueue或threadFactory或handler为 null
 */
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;
        }

代码示例:

public class ThreadPoolExecutorTest {
    public static void main(String[] args) {
        // 创建一个自定义线程池
        ExecutorService threadPool = new ThreadPoolExecutor(
                // 要保留在池中的线程数,即使它们处于空闲状态,除非设置了allowCoreThreadTimeOut
                2,
                // 线程池中的最大线程数哦,当需要的线程数目大于(阻塞队列大小+核心线程数)之后,线程池的大小会逐步变大,直到等于最大线程数
                5,
                // 除了核心线程之外其他线程最大空闲时长,超过这个时长则关掉其他线程
                2,
                // 时长单位
                TimeUnit.SECONDS,
                // 阻塞队列大小,用于存放排队的线程
                new ArrayBlockingQueue<>(3),
                // 阻塞之后的策略,当阻塞队列满,且线程池大小达到最大且所有线程都在使用,会采用给定的策略,ThreadPoolExecutor.AbortPolicy策略会抛出异常
                new ThreadPoolExecutor.AbortPolicy());

        try {
            for (int i = 0; i < 10; i++) {
                // 启动线程,execute()方法中传入的依然是一个Runnable接口
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "线程OK");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 确保程序运行完毕(不再使用线程池)之后,关闭线程池
            threadPool.shutdown();
        }
    }
}

4大策略

就是ThreadPoolExecutor的最后一个参数,发生阻塞时采取的策略

ThreadPoolExecutor.CallerRunsPolicy():发生阻塞的任务从哪个线程来,回到哪个线程,不抛出异常 ThreadPoolExecutor.AbortPolicy():发生阻塞的任务不处理,抛出异常 ThreadPoolExecutor. DiscardPolicy():会丢掉发生阻塞的任务,不会抛出异常 ThreadPoolExecutor.DiscardOldestPolicy() :发生阻塞的任务会和阻塞队列中的最早的任务竞争,如果成功则进入阻塞队列等待,不抛出异常

线程池最大线程数的设置

** 参考:**blog.csdn.net/weixin_4212…

1、cpu密集型,多是运算型应用,io操作较少,主要消耗cpu资源,此时若开启多个线程,容易造成频繁的cpu上线文切换,增加额外时间消耗,顾线程数=cpu核心数+/-1比较合适。

2、io密集型,读写比较频繁,考虑阻塞、非阻塞、同步、异步等Io操作,CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度),增加线程数量可以充分利用cpu空闲时间片段 ,提高程序想用速率。网上对其线程池核心线程数大小有两种说法,第一种,线程数=cpu核数/(1-阻塞系数),阻塞系数大小范围为0.8-0.9,第二种,线程数=(线程等待时间+线程cpu时间)/线程cpu时间* cpu核数。其实仔细观察不难发现,阻塞系数和线程等待时间有某种关系。

四大函数式接口

函数式接口

参考:www.cnblogs.com/runningTurt…

只有一个抽象方法(可以有其他方法)的接口,比如Runnable接口

接口不会把其当作是抽象方法,从而符合函数式接口的定义。

  • 所接口中所定义的方法式默认方法,使用default修饰,有其默认实现。
  • 方法是静态方法,因为静态方法不能是抽象方法,而是一个已经实现了的方法。
  • 方法是继承来自 Object 类的public方法,因为任何一个接口或类都继承了 Object 的方法 共同特点:都已实现
public interface Runnable {
    /**
     * 当使用实现接口Runnable的对象创建线程时,启动线程会导致在单独执行的线程中调用对象的run方法。
     * 方法run的一般约定是它可以采取任何行动。
     */
    public abstract void run();
}

java.util.function下有大量的函数式接口

function.png

四个函数式接口:

四个基本的函数式接口,非基础类型:Consumer、Function、Predicate、Supplier

Function函数型接口

给定泛型,第一个泛型为apply方法参数类型,第二个泛型为apply方法的返回值类型

匿名内部类使用函数式接口

public class FunctionDemo {
    public static void main(String[] args) {
        // 匿名内部类的方式使用函数式接口,可以用于自定义工具类
        Function function = new Function<String, String>() {
            @Override
            public String apply(String o) {
                return o;
            }
        };
        System.out.println(function.apply("ZHANG"));
    }
}

lamda表达式简化使用函数式接口

public class FunctionDemo {
    public static void main(String[] args) {
        Function<String, String> function = (s) -> {
            return s;
        };
        System.out.println(function.apply("ZAHNG"));
    }
}

Predicate断定型函数式接口

public class PredicateTest {
    public static void main(String[] args) {
        // lamda表达式实现断定型函数式接口
        Predicate<String> predicate = (str) -> {
            System.out.println(str);
            return false;
        };
        System.out.println(predicate.test("ZHANG"));
    }
}

Consumer消费型函数式接口

/**
 * Consumer接口只有输入没有输出
 */
public class ConsumerTest {
    public static void main(String[] args) {
        Consumer<String> consumer = (str) -> {
            System.out.println(str);
        };
        consumer.accept("ZHANG");
    }
}

Supplier供给型函数式接口

/**
 * Supplier只有输出没有输入
 */
public class SupplierTest {
    public static void main(String[] args) {
        Supplier<String> supplier = () -> {
            return "ZHANG";
        };
        System.out.println(supplier.get());
    }
}

Stream流

程序:存储+计算

集合用来存储,计算交给流

代码示例:

public class StreamTest {
    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);
        // 存储数据
        List<User> list = Arrays.asList(u1, u2, u3, u4);
        // 使用流进行计算
        // lamda表达式、链式编程、函数式接口、方法引用的组合使用
        list.stream()
                // 过滤得到u的id取值能被2整除的用户,就是得到能使filter的参数Predicate接口返回值为真的用户
                .filter((u) -> {
                    return u.getId() % 2 == 0;
                })
                .filter((u) -> {
                    return u.getAge() > 21;
                })
                // map中传入的接口是Function接口,修改输入值并返回指定类型的返回值。输入为流中User类型的u,返回u的名字的大写,并使用返回的名字替换流中的u
                .map((u) -> {
                    return u.getName().toUpperCase();
                })
                // 输出
                .forEach(System.out::println);
    }
}

ForkJoin

在JDK1.7引入,用于处理大量数据,主要思想:大任务拆分为多个小任务,然后再把每个小任务的计算结果合并最终得到大任务的结果

特点:工作窃取,维护的资源是双端队列,一个线程执行完任务之后会帮另外一个线程执行任务

异步回调

类似与Ajax的异步通信技术,这里是线程之间,Ajax是客户端与服务器之间

Future

参考链接:blog.csdn.net/qq_29051413…

Future 接口在 Java 5 中被引入,设计初衷是对将来某个时刻会发生的结果进行建模。它建模了一种异步计算,返回一个执行运算结果的引用,当运算结束后,这个引用被返回给调用方。在Future 中触发那些潜在耗时的操作把调用线程解放出来(例如调用get方法),让它能继续执行其他有价值的工作,不需要等待耗时的操作完成。

如果已经运行到没有异步操作的结果就无法继续进行时,可以调用它的get方法去获取操作结果。如果操作已经完成,该方法会立刻返回操作结果,否则它会阻塞线程,直到操作完成,返回相应的结果。

代码示例:

/**
 * 异步调用测试
 * Future 接口在 Java 5 中被引入,设计初衷是对将来某个时刻会发生的结果进行建模。它建模了一种异步计算,返回一个执行运算结果的引用,当运算结束后,这个引用被返回给调用方。
 * 在Future中触发那些潜在耗时的操作把调用线程解放出来,让它能继续执行其他有价值的工作,不需要等待耗时的操作完成。
 */
public class FutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // // 没有返回值的异步回调,void泛型,runAsync方法获得
        // CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
        //     try {
        //         // 模拟延时观察效果
        //         TimeUnit.SECONDS.sleep(1);
        //     } catch (InterruptedException e) {
        //         e.printStackTrace();
        //     }
        //     System.out.println(Thread.currentThread().getName() + "runAsync => void");
        // });
        // System.out.println("1111");
        // // 异步回调,获得执行结果。这里调用之后才能执行runAsync中的的线程的内容
        // completableFuture.get();


        // 有返回值的异步回调,supplyAsync方法获得,供给型函数式接口
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                // 模拟延时观察效果
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "runAsync => String");
            // 模拟一个异常
            // int a = 10 /0 ;
            return "有返回值";
        });
        System.out.println("1111");
        // 异步回调,获得执行结果。这里调用之后才能执行runAsync中的的线程的内容
        // System.out.println(completableFuture.get());

        // completableFuture的异步方法执行时,执行成功情况
        // 这里的t是supplyAsync方法的返回值,u是一个继承Throwable接口的类型,是supplyAsync方法可能会产生的一个异常,
        System.out.println(completableFuture.whenComplete((t, u) -> {
                    System.out.println("t -> " + t);
                    System.out.println("u -> " + u);
                })
                // 执行失败情况
                .exceptionally((e) -> {
                    System.out.println(e.getMessage());
                    return "执行出错";
                })
                // 异步回调,获得执行结果。这里调用之后才能执行runAsync中的的线程的内容
                .get());
    }
}