03 - Java核心类库—多线程

一、多线程的基础知识

1. 线程与进程

1.1 进程与线程

进程:【应用程序(软件)】

  • 是指一个内存中运行的应用程序(软件),每个进程都有一个独立的内存空间

线程:【应用程序(软件)里面的执行路径】

  • 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程
  • 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程
  • 线程包括守护线程和用户线程组成

1.2 线程调度

目的是为了更合理的利用CPU
在这里插入图片描述
分时调度

  • 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

抢占式调度【是Java 的调度机制】

  • 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度
  • CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使用率更高。

2. 同步与异步&并发与并行

2.1 同步与异步

同步:排队执行 , 效率低但是安全

异步:同时执行 , 效率高但是数据不安全

2.2 并发与并行

并发:指两个或多个事件在同一个时间段内发生。

并行:指两个或多个事件在同一时刻发生(同时发生)。

二、Java实现多线程的技术

实现多线程方式有以下常见的二种

1. 继承Thread

1.1 程序实例

在这里插入图片描述在这里插入图片描述

1.2 时序图

在这里插入图片描述

1.3 小结

  • 用继承Thread方式实现多线程技术,一定要重写Run方法,并直接用start方法来调用;
  • 每个线程都拥有自己的栈空间,共用一份堆空间。

2. 实现Runnable接口

2.1 使用方法

另一种实现多线程的方法

  • 创建自定义类实现Runnable接口,并重写run方法;
  • 用自定义类创建一个对象r;
  • 用Thread类创建一个对象t,并将r作为t构造方法的参数;
    在这里插入图片描述

2.2 实现Runnable与继承Thread

1)实现Runnable与继承Thread相比有如下优势

  • 1,通过创建任务,然后给线程分配任务的方式实现多线程,更适合多个线程同时执行任务的情况;
  • 2,可以避免单继承所带来的局限性(Java允许实现多个接口,但不允许继承多个父类);
  • 3,任务与线程是分离的,提高了程序的健壮性;
  • 4,后期学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的线程。

2)Thread也有一定的好处
在这里插入图片描述

2.3 小结

使用Runnable接口实现多线程技术,也是我们今后经常用到的方式之一,一定要注意无论用以上两种的其中一种方式,都需要使用Thread类。

3. Thread类

3.1 概述

在这里插入图片描述

3.2 构造方法

在这里插入图片描述

3.3常用方法

在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述

停止线程的方法:声明一个变量,线程不断监控这个变量,一旦变量达到某种条件调用return即可

在这里插入图片描述在这里插入图片描述

所有的用户线程结束,程序才能结束。守护线程是为了守护用户线程,用户线程可以自动结束,所有用户线程结束后,守护线程便会像没有一样。

在这里插入图片描述

4. 设置和获取线程名称

调用 Thread.currentThread()方法以及getName()/setName()
在这里插入图片描述

5. 线程休眠sleep

== 调用Thread.sleep()方法==
在这里插入图片描述

6. 线程阻塞

线程阻塞不只是线程休眠,线程睡了,所有比较消化时间的操作都称其为线程阻塞(eg:读取文件、接收任务输入等),说白了就是耗时操作。

7. 线程中断

一个线程是一个独立的执行路径,它是否应该结束,应该由自身决定,是否死亡由try-catch的catch块决定

过时的stop方法可以直接中断线程,但是如果线程来不及释放资源,会造成一部分垃圾无法回收;

这里采用添加中断标记的方法:调用interrupt方法,子线程执行时捕获中断异常,并在catch块中,添加处理释放资源的代码;

在这里插入图片描述在这里插入图片描述

8. 守护线程 & 用户线程

8.1 概述

线程分为守护线程和用户线程;

  • 用户线程:当一个进程不包含任何存活的用户线程时,进程结束;
  • 守护线程:是守护用户线程,当最后一个用户线程结束后,所有守护线程自动死亡;

直接创建的都是用户线程

设置守护线程线程对象.setDaemon(true);

8.2 实例

1)不设置守护线程
在这里插入图片描述在这里插入图片描述
2)设置为守护线程
在这里插入图片描述在这里插入图片描述

9. 线程安全问题

9.1 线程不安全的原因

多个线程争抢同一个数据,使得数据在判断使用时出现不一致的情况。解决方法,保证一段数据同时只能被一个线程使用(排队使用)。
在这里插入图片描述

所以,为了保证线程安全,将采取排队执行,采用加锁机制,两大类含三种方式

  • 隐式锁

a. 同步代码块

//格式
synchronize(锁对象){
    
    
 ...
}

b. 同步方法

  • 显式锁

c. 接口Lock 子类 ReentrantLock

下面以卖票操作进行举例(3个线程卖10张票),具体实现代码块如下:

9.2 线程安全1-同步代码块

格式:

synchronize(锁对象){
    
    
 
 
}

tip:上面指的“锁对象”是Java中任何对象都可存,必须是同一对象(同一把锁)

在这里插入图片描述
具体代码:

/**
 * 线程不安全问题的解决方式一:
 * 同步代码块
 */
public class Demo_xiancheng_5 {
    
    
    public static void main(String[] args) {
    
    
        Runnable run = new Ticket();// 采用多态的上转型进行实例化对象
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }
}

class Ticket implements Runnable{
    
    
    //票数
    private int count = 10;
    Object o = new Object();// 声明一个锁对象
    @Override
    public void run() {
    
    
        //Object o = new Object();// 这里不是同一把锁,所以锁不住
        while(true){
    
    
            // 同步代码块
            synchronized (o) {
    
     // 加锁
                if (count > 0) {
    
    
                    System.out.println("正在卖票");
                    try {
    
     // 休息
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName() + "卖票成功,剩余票数为" + count);
                } else {
    
    
                    System.out.println("票已卖完。");
                    break;
                }
            }
        }
    }
}

9.3 线程安全2-同步方法

同步代码块粒度较细,可以给一行代码单独加锁,同步方法顾名思义,是给方法加锁;
在这里插入图片描述
具体代码

/**
 * 线程不安全问题的解决方式二:
 * 同步代码块
 */
public class Demo_xiancheng_6 {
    
    
    public static void main(String[] args) {
    
    
        Runnable run_1 = new Ticket_1();
        new Thread(run_1).start();
        new Thread(run_1).start();
        new Thread(run_1).start();
    }
}

class Ticket_1 implements Runnable{
    
    
    // 票数
    private int count = 10;
    @Override
    public void run() {
    
    
        while(true){
    
    
            boolean flag = sale();
            if(!flag){
    
    
               break;
            }
        }
    }

    public synchronized boolean sale(){
    
    
        if(count > 0){
    
    
            System.out.println("正在卖票");
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            count -- ;
            System.out.println(Thread.currentThread().getName() + "卖票成功,剩余票数为" + count);
            return true;
        }
        return false;
    }
}

给方法上锁,对应的锁对象就是this, 如果是静态修饰方法的话,锁对象为类名.class(比如这里sale方法若被修饰为静态方法的话,锁对象为Ticket_1.class,也就是字节码文件对象)

针对以上代码来说 ,锁对象如下:
在这里插入图片描述

9.4 线程安全3-显式锁Lock

同步方法和同步代码块都属于隐式锁,显式锁则是程序员手动加锁、解锁;显示锁使用的是Lock类下的一个子类ReentrantLock类。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 线程不安全问题的解决方式三:
 * 显示锁 接口Lock 子类 ReentrantLock
 */
public class Demo_xiancheng_7 {
    
    
    public static void main(String[] args) {
    
    
        Runnable run_2 = new Ticket_2();
        new Thread(run_2).start();
        new Thread(run_2).start();
        new Thread(run_2).start();
    }
}
class Ticket_2 implements Runnable{
    
    
    private int count = 10;
    //参数为true表示公平锁    默认是false 不是公平锁
    private Lock l = new ReentrantLock(); // 上转型
    @Override
    public void run() {
    
    
        while(true){
    
    
            l.lock(); // 加锁
            if(count > 0){
    
    
                System.out.println("正在准备卖票");
                count--;
                try {
    
    
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖票成功,剩余票数为" + count);
            }else{
    
    
                break;
            }
            l.unlock(); // 解锁
        }
    }
}

9.5 显式锁和隐式锁的区别(待补充)

10. 公平锁与非公平锁

10.1 区别

公平锁:先来先得,遵循排队;

非公平锁:大家一起抢(同步代码块,同步方法,显式锁都属于非公平锁);

10.2 实现方法

在显式锁实例化时,传入参数true()

在这里插入图片描述
具体代码

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 线程不安全问题的解决方式三:
 * 显示锁 接口Lock 子类 ReentrantLock
 */
public class Demo_xiancheng_7 {
    
    
    public static void main(String[] args) {
    
    
        Runnable run_2 = new Ticket_2();
        new Thread(run_2).start();
        new Thread(run_2).start();
        new Thread(run_2).start();
    }
}
class Ticket_2 implements Runnable{
    
    
    private int count = 10;
    //参数为true表示公平锁    默认是false 不是公平锁
    private Lock l = new ReentrantLock(true); // 上转型
    @Override
    public void run() {
    
    
        while(true){
    
    
            l.lock(); // 加锁
            if(count > 0){
    
    
                System.out.println("正在准备卖票");
                count--;
                try {
    
    
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖票成功,剩余票数为" + count);
            }else{
    
    
                break;
            }
            l.unlock(); // 解锁
        }
    }
}

运行结果:
在这里插入图片描述

11. 多线程通信问题

主要借助于wait和notify函数实现
在这里插入图片描述
接下来会举一个具体实例:生产者和消费者问题

11.1 实例:生产者与消费者

11.1.1 前提条件

厨师cook为生产者线程,服务员waiter为消费者线程,食物为生产与消费的物品;

假设目前只有一个厨师,一个服务员,一个盘子。理想状态是:厨师生产一份饭菜,服务员端走一份,且饭菜的属性未发生错乱;

厨师可以制作两种口味的饭菜,制作100次;

服务员可以端走饭菜100次;

11.1.2 问题一:饭菜的属性错乱

1)实验代码

public class Demo1 {
    
    
    public static void main(String[] args) {
    
    
        //多线程通信    生产者与消费者问题
        Food f = new Food();
        new Cook(f).start();
        new Waiter(f).start();
    }
    //厨师
    static class Cook extends Thread{
    
    
        private Food f;
        public Cook(Food f) {
    
    
            this.f = f;
        }
 
        @Override
        public void run() {
    
    
            for (int i = 0; i < 100; i++) {
    
    
                if(i%2==0){
    
    // 设计两种菜色
                    f.setNameAndTaste("老干妈小米粥","香辣味");
                }else {
    
    
                    f.setNameAndTaste("煎饼果子","甜辣味");
                }
            }
        }
    }
    //服务员
    static class Waiter extends Thread{
    
    
        private Food f;
        public Waiter(Food f) {
    
    
            this.f = f;
        }
 
        @Override
        public void run() {
    
    
            for (int i = 0; i < 100; i++) {
    
    
                try {
    
    
                    Thread.sleep(100);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                f.get();
            }
        }
    }
    //食物
    static class Food{
    
    
        private String name;
        private String taste;
        public void setNameAndTaste(String name,String taste){
    
    // 生产
            this.name = name;
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            this.taste = taste;
 
        }
        public void get(){
    
      // 消费
            System.out.println("服务员端走的菜的名称是:"+name+",味道是:"+taste);
        }
    }
} 

2)错误现象
在这里插入图片描述
3)错误原因
在这里插入图片描述

11.1.3 问题二:一次性消费/生产多个菜品

1)实验代码

为了防止在生产过程中setNameAndTaste出现时间片切换,可以用synchronized修饰此方法;

public synchronized void setNameAndTaste(String name,String taste){
    
    // 生产
            this.name = name;
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            this.taste = taste;
 
        }
        public synchronized void get(){
    
      // 消费
            System.out.println("服务员端走的菜的名称是:"+name+",味道是:"+taste);
        }

2)运行效果
在这里插入图片描述
3)原因分析

synchronized只是确保了方法内部不会发生线程切换,但并不能保证生产一个消费一个的逻辑关系;

11.1.4 解决方法

厨师做完饭后喊醒服务员,自己睡着。服务员送完饭后喊醒厨师,自己睡着;

主要修改的部分为setNameAndTaste与get方法:

public synchronized void setNameAndTaste(String name,String taste){
    
    // 生产
            if(flag) {
    
    
                this.name = name;
                try {
    
    
                    Thread.sleep(100);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                this.taste = taste;
                flag = false;        // 表示饭菜生产完毕
                this.notifyAll();    // 叫醒服务员
                try {
    
    
                    this.wait();     // 睡着
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
        public synchronized void get(){
    
      // 消费
            if(!flag){
    
    
                System.out.println("服务员端走的菜的名称是:"+name+",味道是:"+taste);
                flag = true;
                this.notifyAll();
                try {
    
    
                    this.wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
 
        }

可以看出饭菜是交替产生并消费的;
在这里插入图片描述

12. 线程的六种状态

在这里插入图片描述
在这里插入图片描述
tips:学习线程的状态,目的是为了更好理解线程具体执行的流程,不需要去理解这些状态是如何实现的,只需明白有这些状态(可运行状态与非运行状态的切换等)

13. 带返回值的线程Callable(理解)

新的创建线程的方式。之前的创建线程方式:实现Thread的子类、实现Runnable接口,可以看成是和主线程并发执行的;

这里要讲的线程更像是主线程指派的一个任务,主线程可以获得其返回值;

(ps:该方法使用的很少,但面试官有可能会问到如何实现这个线程的方法)

13.1 Runnable 与 Callable的区别

1)接口定义

接口定义 
 
//Callable接口 
public interface Callable<V> {
    
     
    V call() throws Exception; 
}
 
//Runnable接口 
public interface Runnable {
    
     
    public abstract void run(); 
}

2)相同点

  • 都是接口
  • 都可以编写多线程程序
  • 都采用Thread.start()启动线程

3)不同点

  • Runnable没有返回值;Callable可以返回执行结果
  • Callable接口的call()允许抛出异常;Runnable的run()不能抛出

13.2 Callable使用步骤

  1. 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {
    
     
    @Override 
    public <T> call() throws Exception {
    
     
        return T; 
    } 
} 
  1. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable); 
  1. 通过Thread,启动线程
new Thread(future).start();

补充FutureTask类的知识点:

  • 概述
    在这里插入图片描述
    这种异步的意思是 发布任务和执行任务不是同一个线程执行的,主线程负责分配任务到一个任务的队列,子线程从队列中提取任务去执行。子线程完成任务后,返回结果给主线程
  • 构造方法
    在这里插入图片描述

13.3 常用方法

1)方法介绍
在这里插入图片描述
2)代码示例

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
 
public class Demo1 {
    
    
    public static void main(String[] args) {
    
    
        Callable<Integer> c = new MyCallable();
        FutureTask<Integer> f = new FutureTask<>(c);
        new Thread(f).start();
        for(int i = 0; i < 10; i++) {
    
    
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
    static class MyCallable implements Callable<Integer> {
    
    
 
        @Override
        public Integer call() throws Exception {
    
    
//            Thread.sleep(100);// 睡眠过后 给出结果
            for(int i = 0; i < 10; i++) {
    
    
                try {
    
    
                    Thread.sleep(100);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() +" " + i);
            }
            return 100;
        }
    } 
} 

未使用get方法时,主线程和另一个线程交替执行,且没有执行子线程的return语句
在这里插入图片描述

使用get方法
在这里插入图片描述在这里插入图片描述

14. 线程池(了解)

14.1 为什么需要线程池

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间.

线程池就是一个容纳多个线程的容 器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

Java中的四种线程池(对象均为ExecutorService)

14.2 缓存线程池

1)概述

/*** 缓存线程池. 
* (长度无限制) 
* 执行流程: 
* 1. 判断线程池是否存在空闲线程 
* 2. 存在则使用 
* 3. 不存在,则创建线程 并放入线程池, 然后使用 
*/ 
ExecutorService service = Executors.newCachedThreadPool(); 
//向线程池中 加入 新的任务 
service.execute(new Runnable() {
    
     
    @Override 
    public void run() {
    
     
        System.out.println("线程的名称:"+Thread.currentThread().getName()); 
    } 
}); 
service.execute(new Runnable() {
    
     
    @Override 
    public void run() {
    
     
        System.out.println("线程的名称:"+Thread.currentThread().getName()); 
    } 
}); 
service.execute(new Runnable() {
    
     
    @Override 
    public void run() {
    
     
        System.out.println("线程的名称:"+Thread.currentThread().getName()); 
    } 
});

2)代码示例

import java.util.concurrent.*;
 
public class Demo1 {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        /*** 缓存线程池.
         * (长度无限制)
         * 执行流程:
         * 1. 判断线程池是否存在空闲线程
         * 2. 存在则使用
         * 3. 不存在,则创建线程 并放入线程池, 然后使用
         */
        ExecutorService service = Executors.newCachedThreadPool();
        // 指挥线程池执行新的任务
        service.execute(new Runnable() {
    
    // 匿名内部类
            @Override
            public void run() {
    
    
                System.out.println(Thread.currentThread().getName() + "滴滴滴");
            }
        });
        service.execute(new Runnable() {
    
    // 匿名内部类
            @Override
            public void run() {
    
    
                System.out.println(Thread.currentThread().getName() + "滴滴滴");
            }
        });
        service.execute(new Runnable() {
    
    // 匿名内部类
            @Override
            public void run() {
    
    
                System.out.println(Thread.currentThread().getName() + "滴滴滴");
            }
        });
 
    }
} 

在这里插入图片描述
睡眠一段时间后,添加新的任务,查看是否能利用线程池中已存在的线程:
在这里插入图片描述

14.3 定长线程池

1)概述

/**
* 定长线程池. 
* (长度是指定的数值) 
* 执行流程:
* 1. 判断线程池是否存在空闲线程 
* 2. 存在则使用 
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用 
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程 
*/ 
ExecutorService service = Executors.newFixedThreadPool(2); 
service.execute(new Runnable() {
    
     
    @Override 
    public void run() {
    
     
        System.out.println("线程的名称:"+Thread.currentThread().getName()); 
    } 
}); 
service.execute(new Runnable() {
    
     
    @Override 
    public void run() {
    
     
        System.out.println("线程的名称:"+Thread.currentThread().getName()); 
    } 
});

2)代码示例

设定线程池大小为2,即线程池中最多只允许存在两个线程;

前两个线程执行时,均sleep三秒钟;

第三个任务由于线程池已满,不能开辟新的线程,所以必须等线程池中有空闲线程出现才可以执行;

import java.util.concurrent.*;
 
public class Demo1 {
    
    
    /*定长线程池
    长度是指定的线程池
    加入任务后的执行流程
        1 判断线程池是否存在空闲线程
        2 存在则使用
        3 不存在空闲线程  且线程池未满的情况下  则创建线程  并放入线程池中  然后使用
        4 不存在空闲线程  且线程池已满的情况下  则等待线程池的空闲线程
    **/
    public static void main(String[] args) {
    
    
        ExecutorService service = Executors.newFixedThreadPool(2);
        service.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
                try {
    
    
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
 
            }
        });
        service.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
                try {
    
    
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
 
            }
        });
        service.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
    }
} 

在这里插入图片描述

14.4 单线程线程池

1)概述

效果与定长线程池 创建时传入数值1 效果一致. 
/**
* 单线程线程池. 
* 执行流程: 
* 1. 判断线程池 的那个线程 是否空闲 
* 2. 空闲则使用 
* 3. 不空闲,则等待 池中的单个线程空闲后 使用 
*/ 
ExecutorService service = Executors.newSingleThreadExecutor(); 
service.execute(new Runnable() {
    
     
    @Override 
    public void run() {
    
     
        System.out.println("线程的名称:"+Thread.currentThread().getName()); 
    } 
}); 
service.execute(new Runnable() {
    
     
    @Override 
    public void run() {
    
     
        System.out.println("线程的名称:"+Thread.currentThread().getName()); 
    } 
});

2)代码示例

import java.util.concurrent.*;
 
public class Demo1 {
    
    
    /*单线程线程池
    执行流程
        1 判断线程池的那个线程是否空闲
        2 空闲则使用
        3 不空闲则等待它空闲后再使用
    **/
    public static void main(String[] args) {
    
    
        ExecutorService service = Executors.newSingleThreadExecutor();
        service.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        service.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        service.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
    }
} 

在这里插入图片描述

14.5 周期定长线程池

1)概述

public static void main(String[] args) {
    
     
    /**
    * 周期任务 定长线程池. 
    * 执行流程: 
    * 1. 判断线程池是否存在空闲线程 
    * 2. 存在则使用 
    * 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用 
    * 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程 
    *
    * 周期性任务执行时: 
    * 定时执行, 当某个时机触发时, 自动执行某任务 .
    */ 
    ScheduledExecutorService service = Executors.newScheduledThreadPool(2); 
 
    /**
    * 定时执行 
    * 参数1. runnable类型的任务 
    * 参数2. 时长数字 
    * 参数3. 时长数字的单位 
    */ 
 
    /*
    service.schedule(new Runnable() { 
        @Override 
        public void run() { 
            System.out.println("俩人相视一笑~ 嘿嘿嘿"); 
        } 
    },5,TimeUnit.SECONDS); 
    */
 
    /**
    * 周期执行 
    * 参数1. runnable类型的任务 
    * 参数2. 时长数字(延迟执行的时长) 
    * 参数3. 周期时长(每次执行的间隔时间) 
    * 参数4. 时长数字的单位 
    */ 
    service.scheduleAtFixedRate(new Runnable() {
    
     
        @Override 
        public void run() {
    
     
            System.out.println("俩人相视一笑~ 嘿嘿嘿"); 
        } 
    },5,2,TimeUnit.SECONDS); 
}

2)代码示例

import java.util.concurrent.*;
 
public class Demo1 {
    
    
    /*周期任务  定长线程池
    执行流程
        1 判断线程池是否存在空闲线程
        2 存在则使用
        3 不存在空闲线程  且线程池未满的情况下  则创建线程  并放入线程池中  然后使用
        4 不存在空闲线程  且线程池已满的情况下  则等待线程池的空闲线程
        周期性任务执行时
                定时执行 当某个任务触发时  自动执行某任务
    **/
    public static void main(String[] args) {
    
    
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        //定时执行一次
        //参数1:定时执行的任务
        //参数2:时长数字
        //参数3:2的时间单位    Timeunit的常量指定
       /* scheduledExecutorService.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        },5, TimeUnit.SECONDS);      //5秒钟后执行*/
 
        /*
        周期性执行任务
            参数1:任务
            参数2:延迟时长数字(第一次执行延迟的时间)
            参数3:周期时长数字(每隔多久执行一次)
            参数4:时长数字的单位
        * **/
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        },5,1,TimeUnit.SECONDS);
    }
}
 

15. Lambda表达式

Lambda表达式属于函数式编程的其中一种方式。

15.1 为什么要用Lambda表达式

对于某些应用场景,我们更注重于结果,而不注重过程。如果能用一个方法解决,那么通过创建对象、调用方法的方式可能会更加繁琐;

例如:
1)冗余的Runnable方法

public class Demo1 {
    
    
    /**
     * lambda表达式
     * 函数式编程思想(注重结果,而面向对象则是通过创建对象,解决问题)
     * @param args
     */
    public static void main(String[] args) {
    
    
        // 冗余的Runnable代码
        Runnable r = new MyRunnable();
        Thread t = new Thread(r);
        t.start();  // 写了这么多 只为了完成一个简单的任务
    }
    static class MyRunnable implements Runnable{
    
    
 
        @Override
        public void run() {
    
    
            System.out.println("任务完成!");
        }
    }
}

2)通过匿名内部类简化代码

public class Demo1 {
    
    
    /**
     * lambda表达式
     * 函数式编程思想(注重结果,而面向对象则是通过创建对象,解决问题)
     * @param args
     */
    public static void main(String[] args) {
    
    
        // 冗余的Runnable代码
        Thread t = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("任务完成!");
            }
        });
        t.start();  // 写了这么多 只为了完成一个简单的任务
    }
}

在这里插入图片描述

15.2 使用实例

1)使用lambda表达式一

/**
 * 函数式编程
 */
public class Demo_xiancheng_10 {
    
    
	 /**
     * lambda表达式
     * 函数式编程思想(注重结果,而面向对象则是通过创建对象,解决问题)
     * @param args
     */
    public static void main(String[] args) {
    
    
        // 调用打印方法
        //print((x,y) ->x + y,100,200);
        print(new MyMath() {
    
    
            @Override
            public int sum(int x, int y) {
    
    
                return x + y;
            }
        },100,200);
    }

    // 用于计算并打印总成绩的方法
    public static  void print(MyMath m, int x, int y){
    
    
        int num = m.sum(x,y); // 实现接口的抽象方法
        System.out.println(num);
    }
}

//定义一个抽象接口
interface MyMath{
    
    
    int sum(int x,int y);
} 

2)使用lambda表达式二:(简便)

不需要实现接口、实例化对象;

 /**
 * 函数式编程
 */
public class Demo_xiancheng_10 {
    
    
	 /**
     * lambda表达式
     * 函数式编程思想(注重结果,而面向对象则是通过创建对象,解决问题)
     * @param args
     */
    public static void main(String[] args) {
    
    
        // 调用打印方法
        print((x,y) ->x + y,100,200);
    }
    
    // 用于计算并打印总成绩的方法
    public static  void print(MyMath m, int x, int y){
    
    
        int num = m.sum(x,y); // 实现接口的抽象方法
        System.out.println(num);
    }
}

//定义一个抽象接口
interface MyMath{
    
    
    int sum(int x,int y);
}

tip:想更深层次了解Lambda表达式以及Java函数式编程

可以点击[Java之函数式编程]

猜你喜欢

转载自blog.csdn.net/weixin_46312449/article/details/114661826