Java之线程池和Lambda表达式

时间:2022-10-05 19:12:10

线程池和lambda表达式

学习线程池和lambda表达式的理解

补充一个知识点(单例设计模式)

在多线程中,我们只需要一个任务类,为了防止创建多个任务类,这个时候就需要用到单例模式,单例模式有两种设计:

  1. 延迟加载(懒汉式)
    • 私有构造方法
    • 创建本类对象,但不初始化
    • 创建静态方法进行初始化对象并返回
  2. 立即加载(饿汉式)
    • 私有构造方法
    • 创建本类的对象并初始化(私有的)
    • 创建静态方法获取本类对象

下面用代码做个实例:

package com.wzlove.single;

/**
 * 延迟加载(懒汉式)
 *  1.私有构造方法
 *  2.创建本类对象,但不初始化
 *  3.创建静态方法进行初始化对象并返回
 *
 *  优点:
 *      使用到类的对象才会加载,不消耗内存
 *  缺点:
 *      可能会出现线程安全问题,但是可以使用同步代码块消除这个安全问题
 * @author WZLOVE
 * @create 2018-07-19 10:36
 */
public class DeplayedSingle {

    // 私有构造方法
    private DeplayedSingle(){}

    // 创建本类对象,不初始化
    private  static DeplayedSingle instance ;

    // 静态方法初始化
    public static DeplayedSingle getInstance(){
        synchronized (DeplayedSingle.class){
            if(instance == null){
                instance = new DeplayedSingle();
            }
        }
        return instance;
    }
}



package com.wzlove.single;

/**
 * 立即加载(饿汉式)
 *  1.私有构造方法
 *  2.创建本类的对象并初始化(私有的)
 *  3.创建静态方法获取本类对象
 *
 *  优点:
 *      保证的线程的安全,没有线程安全问题
 *  缺点:
 *      使用类就会加载,比较消耗内存
 * @author WZLOVE
 * @create 2018-07-19 10:37
 */
public class ImmediateSingle {

    private ImmediateSingle(){}

    private static ImmediateSingle instance = new ImmediateSingle();

    public static ImmediateSingle getInstance(){
        return instance;
    }
}

线程池

概述

线程池其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,
无需反复创建线程而消耗过多资源。作用是节省资源,原理是容器.

使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题.如果不使用线程池,
有可能造成系统创建大量同类线程而导致消耗完内存或者"过度切换"的问题.

创建线程池:

  • public static ExecutorService newFixedThreadPool(int nThreads) :返回线程池对象。(创建的是有界线
    程池,也就是池中的线程个数可以指定最大数量)
  • public Future<?> submit(Runnable task) :获取线程池中的某一个线程对象,并执行
  • Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。

步骤:

  • 使用线程池的工厂类Executors里面提供静态方法newFixedThreadPool生产一个指定线程数量的线程池
  • 创建一个类,实现Runnable接口,重写run方法,设置线程任务
  • 调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程(前提是线程池中有线程可用),执行run方法
  • 调用ExecutorService中的shutdown方法销毁线程池(不建议执行)

补充线程的创建

创建线程的第三种方式(这种方式很少见)

package com.wzlove.executor;

import java.util.ArrayList;
import java.util.concurrent.Callable;

/**
 * 线程的第三种创建方法
 *  Callable有返回值并且可以抛出异常
 * @author WZLOVE
 * @create 2018-07-18 14:14
 */
public class CallableImpl implements Callable<String> {

    private static ArrayList<String> arrayList = new ArrayList<>();

    static{
        arrayList.add("1");
    }

    @Override
    public String call() throws Exception {
        return Thread.currentThread().getName();
    }
}



package com.wzlove.executor;

import java.util.concurrent.*;

/**
 * 测试线程的第三中创建方式
 *
 * @author WZLOVE
 * @create 2018-07-18 14:14
 */
public class Demo {

    public static void main(String[] args) {
        // 创建实现Callable的实现类对象
        Callable<String> callable = new CallableImpl();
        // 创建FutureTask,并传递Callable接口的实现类对象
        FutureTask task = new FutureTask(callable);
        // 创建线程池对象
        // ExecutorService executor = Executors.newSingleThreadExecutor();
        ExecutorService executor = Executors.newFixedThreadPool(3);
        executor.submit(callable);
        // 执行线程
        executor.execute(task);

        try {
            System.out.println(task.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }


}

Lambda表达式

lambda体现的是一种函数式编程的思想, 它强调的是做什么,而不是以什么形式做。

使用前提

  • 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。无论是JDK内置的Runnable 、Comparator 接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。
  • 使用Lambda必须具有上下文推断。也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。

有且仅有一个抽象方法的接口,称为“函数式接口”。

Lambda标准格式

Lambda省去面向对象的条条框框,格式由3个部分组成:

  • 一些参数
  • 一个箭头
  • 一段代码

Lambda表达式的标准格式为:

(参数类型 参数名称) ‐> { 代码语句 }

格式说明:

  • 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
  • -> 是新引入的语法格式,代表指向动作。
  • 大括号内的语法与传统方法体要求基本一致。

线程匿名内部类的转化(无参无返回值)

实现Runnable接口的匿名内部类的实现:

public class Demo01Runnable {
    public static void main(String[] args) {
        // 匿名内部类
        Runnable task = new Runnable() {
            @Override
            public void run() { // 覆盖重写抽象方法
                System.out.println("多线程任务执行!");
            }
        };
        new Thread(task).start(); // 启动线程
    }
}

代码分析

  • Thread 类需要Runnable 接口作为参数,其中的抽象run 方法是用来指定线程任务内容的核心;
  • 为了指定run 的方法体,不得不需要Runnable 接口的实现类;
  • 为了省去定义一个RunnableImpl 实现类的麻烦,不得不使用匿名内部类;
  • 必须覆盖重写抽象run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;
  • 而实际上,似乎只有方法体才是关键所在。

使用Lambda进行简化:

public class Demo01Runnable {
    public static void main(String[] args) {
         new Thread(()->System.out.println("多线程任务执行!")).start(); // 启动线程
    }
}

比较器内部类的转化(有参有返回值)

使用比较器举个例子:

// 创建数组
Integer[] arr = {1,8,3,4,9,2,5};

// 匿名内部类实现数组升序排序
Arrays.sort(arr,new Comparator<Integer>(){

    @Override
    public int compare(Integer o1, Integer o2) {
        return 0;
    }
});

// 使用lambda进行数组的升序排序
Arrays.sort(arr,( a, b)->{ return a - b ;});

Lambda表达式的省略规则

在Lambda标准格式的基础上,使用省略写法的规则为:

  1. 小括号内参数的类型可以省略;
  2. 如果小括号内有且仅有一个参,则小括号可以省略;
  3. 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。

虽然有省略写法,但是我感觉这个有点灵活,所以不建议省略,因为代码是给别人看的,省略的话感觉别人看起来会有点费劲.