天天看点

java中的线程池及Lambda表达式(初学java笔记)

一、等待唤醒机制

  1. 线程间的通信

    多个线程在处理同一个资源,但是处理动作及线程的任务却不相同

    多个线程并发执行时没在默认情况下 CPU 是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且希望有规律的执行,那么多线程之间需要一些协调通信,由此来达到多线程共同操作一份数据

    那么如何线程通信有效利用资源,在多线程处理统一资源,并任务不同时,需要线程通信来解决线程之间对同一个变量使用或操作,就是多个线程在操作同一份数据时,避免对同一共享变量争夺,也就是需要通过一定手段是各个线程能有效利用资源, 那么这就是 等待唤醒机制

  2. ** 等待唤醒机制**

    等待唤醒机制就是多个线程的一种协作机制.在一个线程进行了规定的操作后,就进入等待状态 ( wait() ) ,等待其他线程执行完指定的代码后,再将其唤醒 ( notify() ) ,若有多个线程等待时, 可以使用 notifyAll() 来唤醒所有的等待线程

等唤醒等待的方法

wait : 线程不在活动,进入 wait set 中,因此不会浪费 CPU 的资源,这是线程状态就是 waiting , 还要等待其他线程执行一个特别动作, 也就是 通知 (notify) 在这个对象上等待线程从 wait set 中释放出来,从新进入调度队列中

notify : 则是选取所通知对象的 wait set 中一个线程释放,

notifyAll : 则释放所通知对象 wait set 上的全部线程

二、线程池

  1. 线程池

    如果并发数很多,而每个线程都执行很短时间就结束了,这样频繁创建线程会降低系统的效率,因为频繁创建线程和销毁线程需要时间,那么线程池可以来解决这个问题

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

  1. 线程池的使用

    在java 中线程池的顶级接口是 java.util.concurrent.Executor, 但是 Executor 冰块不是一个线程池,只是一个执行线程的工具, 而真正的线程池接口是 java.util.concurrent.ExecutorService.

    Executor 类中有创建线程池的方法有:

public static ExecutorService newFixedThread(int nThread) : 返回线程池对象

public Future<?> submit(Runnable task) : 获取线程池中某个线程对象,并执行

Future 接口 : 用来记录线程任务执行完毕后产生的结果,线程池创建与使用

使用线程池中线程对象的步骤

a 创建线程池对象

b 创建Runnable 接口子类对象

c 提交Runnable 接口子类对象

d 关闭线程池(一般不做)

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo {
    public static void main(String[] args){
        //创建线程池
        ExecutorService service = Executors.newFixedThreadPool(2); //2个先传给你对象
        //创建 Runnable对象
        Runnabletest rt = new Runnabletest();

//        //自己创建线程对象的方式
//        Thread t = new Thread(rt);
//        t.start();

        //从线程池中获取线程,然后调用 Runnabletest 中的 run()
        service.submit(rt);

        //在获取两个线程对象,调用Runnabletest 中的 run()
        service.submit(rt);
        service.submit(rt);

        //注意submit方法调用结束后,程序并未终止,因为线程池控制了线程的关闭
        //使用完的线程又到线程池中

        //线程池关闭
        //service.shutdown();
    }
}

class Runnabletest implements Runnable{
    @Override
    public void run(){
        System.out.println("我需要一杯水");
        try{
            Thread.sleep(2000);
        }catch (InterruptedException e){
           e.printStackTrace();
        }
        System.out.println("有一杯水放在桌子上: "+ Thread.currentThread().getName());
        System.out.println("将水喝到肚子里了");
    }
}
           

输出:

java中的线程池及Lambda表达式(初学java笔记)

三、lambda 表达式

  1. 传统代码-使用实现类

    要启动一个线程,需要创建一个 Thread 类的对象并调用 start()方法,为了指定线程内容,需要调用 Thread 类结构方法:

    Thread(Runnable target)

    为了获取 Runnable 接口实现对象,可以为该接口定义一个实现类 RunnableTests

public class RunnableLambda {
    public static void main(String[] args){s
        RunnableTests rt = new RunnableTests();
        new Thread(rt).start();
    }
}s

class RunnableTests implements Runnable{
    @Override
    public void run(){
        System.out.println("多线程任务执行");
    }
           
  1. 传统代码-使用匿名内部类

    这个 RunnableTests 类只是为了实现 Runnable 接口而存在,而仅被使用了唯一一次,所以使用匿名内部类的语法可省去该类的单独定义,即匿名内部类

public class RunnableLambda {
    public static void main(String[] args){
      
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("读线程任务执行");
            }
        }).start();
    }
}
           

匿名内部类的好处与弊端

一是匿名内部类可以省区实现类的定义,另外是匿名内部类语法较复杂

语义分析

仔细分析代码中的语义, Runnable 接口只有一个run 方法的定义

public abstract void run();

即定制了一种做事情的方案(其实就是一个函数):

无参数:不需要任何条件即可执行该方案

无返回值: 该方案不生产任何结果

代码块:该方案具体执行步骤

同样语义体现在 Lambda语法中,要更加简单:

() -> System.out.println(“读线程任务执行”)

前面一堆小括号即 run 方法的参数(无),代表不需要任何条件;

中间的一个箭头代表将前面的参数传递给后面的代码;

后面的输出语句就是业务逻辑

  1. Lambda标准格式

    Lambda 省去面向对象的结构,格式由三部分组成:

一些参数

一个箭头

一段代码

Lambda 表达式的标准格式:

(参数类型 参数名称)-> {代码语句}
public class LambdaDemoOne {
    public static void main(String[] args){
        inCook(() -> {
            System.out.println("吃饭了");
        });
    }
    private static void inCook(Cook cook){
        cook.makeFood();
    }
}

interface Cook{
    void makeFood();
}
           

输出:

java中的线程池及Lambda表达式(初学java笔记)

Lambda的参数与返回值

以前的写法:

import java.util.Arrays;
import java.util.Comparator;

public class CompratorDemo {
    public static void main(String[] args){
        Person[] array = {
                new Person("小明",12),
                new Person("小张",10),
                new Person("小红",11)
        };
        //创建 Comparator 比较器对象
        Comparator<Person> cp = new Comparator<Person>(){ 
            @Override
            public int compare(Person o1,Person o2){ //为了指定compare 方法,不得不使用 Comparator 接口实现类
                return o1.getAge() - o2.getAge();  //大于零为正序
            }
        };
        Arrays.sort(array,cp);//第二个参数为排序规则,就是comparator 接口实例  
		//Arrays.stor()方法需要排序规则,即Comparator接口实例,收i选抽象方法compre 是关键
        for (Person person : array){ //遍历数组
            System.out.println(person);
        }
    }
}

class Person{
    private String name;
    private int age;
    //重写 toString 不然输出为一串看不懂的字符
    @Override
    public String toString(){
        return age + "岁的" + name;
    }
    //构造方法无需写 void
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName(){
        return this.name;
    }

    public int getAge() {
        return this.age;
    }
}
           

输出:

java中的线程池及Lambda表达式(初学java笔记)

下面是Lambda 写法

import java.util.Arrays;
public class ComparatorLambdaDemo {
    public static void main(String[] args){
        Persons[] arrays = {
          new Persons("Alice",9),
          new Persons("Bob",11),
          new Persons("Tom",13)
        };
		//Lambda 格式
        Arrays.sort(arrays,(Persons o1,Persons o2) -> {
            return o2.getAge() - o1.getAge(); //采用倒序
        });
        
        for (Persons persons : arrays){
            System.out.println(persons);
        }
    }
}
class Person{
  //skip 内容同上
}
           

输出:

java中的线程池及Lambda表达式(初学java笔记)

注意: 使用Lambda 必须有接口,且接口中有且仅有一个抽象方法, 使用 Lambda 必须具有上下文推断,就是方法的参数或局部变量必须为lambda 对应的忌口类型