异常与多线程复习

一、 异常

1.1 异常的概念

异常:是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。

在java等面向对象的编程语言中,异常本身就是一个类,产生异常就是创建异常对象并抛出一个异常对象。

java处理异常的方式是中断处理

异常并不是语法错误,语法错了,编译时不会通过的,不通过就不会产生class(字节码)文件,根本运行不了。

1.2异常的体系

异常的根类是java.lang.Throwable,其下边有类两个子类继承:java.lang.Errorjava.lang.Exceptionjava.lang.Exception为平常所说的异常。

Throwable体系

  • Error:严重错误error,无法通过处理的错误。
  • Exception:表示编译期异常,异常产生后可以通过代码的方式纠正。

1.3 异常产生过程解析

在这里插入图片描述

二、异常处理

java异常处理的五个关键字:try、catch、finally、throw、throws

2.1 抛出异常throw

throw是用在方法内,用来抛出异常对象,将异常对象传递到调用者处,并结束当前方法的执行。

使用格式:

throw new 异常类名(参数)

2.2 Objects非空判断

查看指定引用对象是否为null

public static<T>T requireNonNull(T obj) 

2.3 声明异常throws

关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常)。

**声明异常:**将问题表识出来,报告给调用者。

格式:

修饰符 返回值类型 方法名(参数)throws 异常类名1,异常类名2...{...}
public class ThrowsDemo{
    public static void main(String[] args) throws FileNotFoundException{
        read.("a.txt");
    }
    
    public static void read(String path) throws FileNotFoundException{
        if(!path.equals("a.txt")){
            throw new FileNotFoundException("文件不存在!");
        }
    }
}

2.4 捕获异常try catch

如果异常出现的话,会立刻终止程序,所以我们得处理异常:

  1. 不处理,声明抛出。使用throws关键字
  2. 在方法中使用try catch是处理异常

try catch的方式就是捕获异常。

捕获异常:java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理

格式:

try{
    //可能产生异常的代码
} catch{
    //异常处理的逻辑
}

补充:

  1. try中可能会抛出多个异常 ,就需要多个catch来处理异常。
  2. try中出现异常,就会执行catch异常处理逻辑,并会对catch之后的代码正常运行;try中不出现异常,就不会执行catch,并继续执行之后的代码。

2.5 finally

finally不能单独使用,且无论是否出现异常都会执行

与try catch 搭配使用。

Throwable补充

  • public String getMessage() :返回此throwable的详细消息字符串(字符串可能为null)

  • public String toString()

    返回此可抛出的简短描述。结果是:

    • 这个对象的类的name
    • “:”(一个冒号和一个空格)
    • 调用这个对象的getLocalizedMessage()方法的结果

    如果getLocalizedMessage返回null ,那么只返回类名。

  • public void printStackTrace():JVM打印异常对象,默认此方法,打印异常信息最全面的。

在这里插入图片描述

三、多线程

3.1 并发与并行

并发:是指同一个时间段内多个任务同时都在执行,并且都没有执行结束。并发任务强调在一个时间段内同时执行,而一个时间段由多个单位时间累积而成,所以说并发的多个任务在单位时间内不一定同时在执行 。
并行:是说在单位时间内多个任务同时在执行 。

  • 并发:指两个或者多个事件同一时间段内发生。(交替执行)
  • 并行:指两个或者多个事件同一时刻发生。(同时执行)

3.2 线程与进程

  • 进程:是程序的一次执行,是操作系统资源分配和调度的最小单位(基本单位)。系统运行一个程序即是一个进程从创建、运行到消亡的过程。

  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

两者的关系:进程是指程序执行时的一个实例,线程是进程的一个实体。

3.2.1 线程与进程区别

进程:

  • 拥有独立的堆栈空间和数据段,系统开销大
  • 由于进程之间是独立的特点 使得进程的安全性比较高 有独立的地址空间 一个进程崩溃 不影响其他进程
  • 进程的通信机制相对复杂 譬如管道、信号、消息队列、套接字等

线程:

  • 线程拥有独立的堆栈空间 但是共享数据段,它们彼此之间使用相同的地址空间,比进程开销小
  • 线程是一个进程中不同的执行路径 ,线程的死亡就等于整个进程的死亡。
  • 通信相对方便

3.2.2 线程的生命周期

  1. 新建状态(new):当线程对象创建后,即进入了新建状态,如:Thread t = new Thread();
  2. 就绪状态(Runnable):当调用线程对象的start()方法(例:t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经准备好,随时等待CPU调度执行,并不是线程立即执行。
  3. 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
  4. 阻塞状态(Blocked):处于运行状态的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入到阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
    • 等待阻塞:运行状态中的线程执行了wait()方法,使线程进入到等待阻塞状态;
    • 同步阻塞:线程在获取synchronized同步锁失败(锁被其他线程锁占用),就会进入到同步阻塞状态;
    • 其他阻塞:通过调用线程的sleep()或者join(),又或者发出I/O请求时,线程就会进入到阻塞状态。当sleep()状态超时、join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态;
  5. 死亡状态(Dead):线程执行完或者因异常退出了run()方法,该线程结束周期。

3.2.3 线程调度

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

  • 抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),java使用抢占式调度

3.3 创建线程类

java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例

java中通过继承Thread类来创建并启动多线程的步骤如下:

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表线程所需要完成的任务。因此run()方法被称为线程执行体。
  2. 创建Thread子类的实例,即创建线程对象。
  3. 调用线程对象的start()方法来启动线程。

3.3.1 多线程的原理

在这里插入图片描述

3.4 Thread类

线程是程序中执行的线程。Java虚拟机允许应用程序同时执行多个执行线程。

每个线程都有优先权。 具有较高优先级的线程优先于优先级较低的线程执行。 每个线程可能也可能不会被标记为守护程序。 当在某个线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初设置为等于创建线程的优先级,并且当且仅当创建线程是守护进程时才是守护线程。

当Java虚拟机启动时,通常有一个非守护进程线程(通常调用某些指定类的名为main的方法)。 Java虚拟机将继续执行线程,直到发生以下任一情况:

  • 已经调用了Runtime类的exit方法,并且安全管理器已经允许进行退出操作。
  • 所有不是守护进程线程的线程都已经死亡,无论是从调用返回到run方法还是抛出超出run方法的run

构造方法

在这里插入图片描述

常用方法:

  • public String getName();– 获取当前线程名称
  • public String setName(String name);– 更改当前线程名称为参数name
  • public void start(); – 导致此线程开始执行; Java虚拟机调用此线程的run方法。
  • public void run(); – 如果这个线程使用单独的Runnable运行对象构造,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回。
  • public static void sleep(long millis);– 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
  • public static Thread currentThread();– 返回对当前正在执行的线程对象的引用。
  • public void setPriority(int newPriority); --更改此线程的优先级,最小为1,最大为10。

3.5 创建线程方式二

采用java.lang.Runnable常见的一种方式

步骤如下:

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
  3. 调用线程对象的start()方法来启动线程。

案例代码如下:

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("小区"+i);
            try {

            Thread.sleep(1000);
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}
public class RunnableDemo {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable).start();
    }
}

3.6 Thread类和接口Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runnable接口的话,则容易的实现资源共享。

实现Runnable接口比继承Thread类所具有的优势

  1. 适合多个相同程序代码的线程去共享一个资源。
  2. 可以避免java中单继承的局限性。
  3. 增强程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
  4. 线程池只能放入实现Runnable或者Callable类线程,不能直接放入继承Thread的类。

扩充:在java中,每次程序运行至少启动2个线程。一个主(main)线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每个JVM就是操作系统中启动了一个进程。

3.7匿名内部类方式实现线程的创建

public class InnerClassThread {
    /*
    匿名内部类方式实现线程的创建
    匿名内部类作用:把子类继承父类,重写父类方法,创建子类对象一步完成,接口相同。

    格式:
    new 父类/接口(){
        重写方法;
    }
     */

    public static void main(String[] args) {
        //创建线程的第一种方式:
        //MyThread extend Thread
        //MyThread mt = new MyThread();
        //mt.start();
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName()+"-->"+i);
                }
            }
        }.start();
        //第二种方式:Runnable接口
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName()+"程序员"+i);
                }
            }
        }).start();
    }
}

3.8 线程池

/**
 * Java通过Executors提供四种线程池,分别为:
 * newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
 * newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
 * newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
 * newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
 */
public class TestPool {
    public static void main(String[] args) {
        //创建服务,创建线程池
        //newFixedThreadPool线程池的大小
        ExecutorService service = Executors.newFixedThreadPool(10);


        service.execute(new MyThreade());
        service.execute(new MyThreade());
        service.execute(new MyThreade());
        //结束服务
        service.shutdown();

    }
}

class MyThreade implements Runnable{
    @Override
    public void run() {
       System.out.println(Thread.currentThread().getName());
    }
}

四、线程安全

4.1 线程安全案例

案例:电影院卖票

public class RunnableImp implements Runnable{
    //定义票数
    private Integer ticket = 100;

    /**
     * 卖票操作
     */
    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (ticket > 0){
                System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"票");
                ticket--;
            }
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        
        RunnableImp runnableImp = new RunnableImp();

        new Thread(runnableImp,"窗口1").start();
        new Thread(runnableImp,"窗口2").start();
        new Thread(runnableImp,"窗口3").start();
        //可以使用匿名内部类来实现
    }
}
窗口1正在卖第100票
窗口3正在卖第100票
窗口2正在卖第100票
窗口2正在卖第97票
窗口1正在卖第97票
窗口3正在卖第97票
窗口2正在卖第94票
窗口3正在卖第94票
窗口1正在卖第94票
窗口1正在卖第91票
窗口3正在卖第91票

发现程序出现了两个问题:

  1. 相同的票数,比如91这张票被卖了两回。
  2. 不存在的票,比如0票与-1票,是不存在的。
    这种问题,几个窗口(线程)票数不同步了,这种问题称为线程不安全。

4.2 线程同步

当使用多个线程去访问同一个资源时,且多线程中对资源有写的操作,就会容易出现线程安全问题。

要解决上述电影案例多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。

为了保证每个线程都能正常执行原子操作,java引入了线程同步机制。

三种方式完成同步操作:

  1. 同步代码块。
  2. 同步方法。
  3. 锁机制。

4.3 同步代码块

  • 同步代码块synchronized关键字可以用与方法中的某个区块中,表示只对这个区域的资源实现互斥访问。

    格式:

    synchronized(同步锁){
        需要同步操作的代码
    }
    

    同步锁

    对象的同步锁只是一个概念,可以想像在对象上标记了一个锁。

    1. 锁对象可以是任意类型。

    2. 多个线程对象要使用同一把锁。

      注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁谁进代码块,其他线程只能在外面等着释放锁。

public class RunnableImp implements Runnable{
    //定义票数
    private Integer ticket = 100;
	//定义锁
    Object obj = new Object();
    /**
     * 卖票操作
     */
    @Override
    public void run() {
        while(true){
            synchronized(obj){
                if (ticket > 0){
                    System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"票");
                    ticket--;
                }
            }
        }
    }
}

注意:程序会频繁去判断锁,释放锁,就会导致程序效率低。

4.4 同步方法

  • 同步方法:使用synchronized修饰的方法,叫做同步方法,保证线程A执行该方法时,其他线程只能在方法外等待锁的释放。

格式:

public synchronized void method(){
    可能会产生线程安全问题的代码
}

同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

/**
 * 使用步骤:
 *  1. 把访问了共享数据的代码抽取出来,放到一个方法中。
 *  2. 在方法上添加synchronized修饰符
 */
public class RunnableImp implements Runnable{
    //定义票数
    private Integer ticket = 100;

    /**
     * 卖票操作
     */
    @Override
    public void run() {
        while(true){
           payTicket();
        }
    }

    public synchronized void payTicket(){
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (ticket > 0){
            System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"票");
            ticket--;
        }
    }
}

4.5 Lock锁

java.util.concurrent.locks.lock机制提供了比synchronized代码块synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有。

Lock锁也称同步锁,加锁与释放锁方法化,如下:

  • public void lock();加同步锁。
  • public void unlock;释放同步锁。
public class RunnableImp implements Runnable{
    //定义票数
    private Integer ticket = 100;
    Lock l = new ReentrantLock();

    /**
     * 卖票操作
     */
    @Override
    public void run() {
        while(true){
            l.lock();  //获取锁
            try {
                Thread.sleep(20);
                if (ticket > 0){
                    System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"票");
                    ticket--;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                l.unlock(); //释放锁
            }
            
        }
    }
}

五、线程状态

在API中java.lang.Thread.State 这个枚举类中给出了六种线程状态:

线程状态导致状态发生条
NEW(新建)线程刚被创建,但是并未启动。还未调用start()方法。
Runnable(可运行)可运行线程的线程状态。 可运行状态的线程正在Java虚拟机中执行,但它可能正在等待来自操作系统(例如处理器)的其他资源。
Blocked(锁阻塞)一个线程的线程状态阻塞等待监视器锁定。 处于阻塞状态的线程正在等待监视器锁定进入同步块/方法,或者在调用Object.wait后重新输入同步的块/方法。
Waiting(无限等待)等待线程的线程状态由于调用以下方法之一,线程处于等待状态: 1.Object.wait没有超时。 2.Thread.join没有超时。3. LockSupport.park 。 等待状态的线程正在等待另一个线程执行特定的动作。 例如,已经在对象上调用Object.wait()线程正在等待另一个线程调用该对象上Object.notify() Object.notifyAll()或 调用Thread.join()的线程正在等待指定的线程终止。
Timed Waiting(计时等待)同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

六、等待与唤醒机制

6.1 线程间通信

概念:多个线程在处理同一个资源,但线程任务是不相同的。

6.1.1 为什么要处理线程间的通信

多个线程并发执行时,默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务时,并且有规律的执行,那么就需要多线程协调通信。

6.2 等待唤醒机制

6.2.1 什么是等待唤醒机制

在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。

wait/notify 就是线程间的一种协作机制。

6.2.2 等待唤醒的方法

等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:

  1. wait:线程进入到休眠,不再参与调度,进入到wait set中,因此不会浪费CPU资源,也不会去竞争锁了,这时的线程即是WAITING。它还要等着别的线程执行 通知(notify),在这个对象上等待的线程从wait set释放出来,重新进入到调度队列(ready queue)中
  2. notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
  3. notifyAll:则释放所通知对象的 wait set 上的全部线程。

注意:

哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以它需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。

总结如下:

  • 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
  • 否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态

调用wait和notify方法需要注意的细节

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

6.3 生产者与消费者问题

等待唤醒机制其实就是经典的“生产者与消费者”的问题。

包子案例分析:

包子铺线程生产包子,吃货线程消费包子。当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。接下来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。

案例代码:

public class Main {
    public static void main(String[] args) {
        Bun bun = new Bun();

        new Producer(bun).start();
        new Consumer(bun).start();
    }
}

public class Consumer extends Thread{

    private Bun bun;

    public Consumer(Bun bun){
        this.bun = bun;
    }


    @Override
    public void run() {
        while (true){
            synchronized (bun){
                if (bun.flag == false){
                    //没有包子
                    try {
                        bun.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //被唤醒线程
                System.out.println("消费者正在购买"+bun.BunSkin+bun.Stuffing+"包子");
                System.out.println("=================================================");
                //修改状态
                bun.flag = false;
                bun.notify();
                System.out.println("要生产包子");
            }
        }
    }
}
public class Producer extends Thread{
    //创建包子变量
    private Bun bun;
    //构成方法为包子赋值
    public Producer(Bun bun){
        this.bun = bun;
    }

    @Override
    public void run() {
        int count = 0;
        while (true){
            synchronized(bun){
                //有包子就等待
                if (bun.flag == true){
                    try {
                        bun.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //被唤醒后执行,包子铺生产包子,交替生产两个包子
                if (count%2 == 0){
                    //生产肉包
                    bun.BunSkin ="脆皮";
                    bun.Stuffing="海鲜馅";
                } else {
                    bun.BunSkin ="薄皮";
                    bun.Stuffing="韭菜馅";
                }
                count++;
                System.out.println("包子正在生产中"+bun.BunSkin+bun.Stuffing+"包子");
                System.out.println("=================================================");
                //生产包子需要等待5秒
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //包子铺生产好包子,修改包子状态并唤醒消费者线程
                bun.flag = true;
                bun.notify();
                System.out.println("包子铺包子生产好,"+bun.BunSkin+bun.Stuffing+"包子消费者购买");
            }
        }
    }
}

七、线程池

**线程池:**其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
在这里插入图片描述

合理利用线程池的好处:

  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. ==提高响应速度。==当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. ==提高线程的可管理性。==可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

7.1线程池的使用

Java里面线程池的顶级接口是java.util.concurrent.Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService

官方建议使用Executors工程类来创建线程池对象。Executors类中有个创建线程池的方法如下:

  • public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)。参数 int nThreads是代表创建线程池中包含的线程数量。

获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:

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

  • void shutdown():启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。 如果已经关闭,调用没有额外的作用。

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

public class Demo {
    public static void main(String[] args) {
        //1.使用线程池的工厂类Executors里边提供静态方法newnewFixedThreadPool(int nThreads)生产指定的线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(2);
        //2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
        //3.调用ExecutorService 中的submit方法,传递线程任务(实现类),开启线程,执行run方法。
        //线程池一直开启,使用完了线程,会自动把线程归还到线程池,线程还可以继续使用。
        service.submit(new RunnableImp());
        //最后可以调用shutdown方法销毁线程池。(不建议去销毁)
    }
}

八、Lambda表达式

public class Demo {
    public static void main(String[] args) {
        //匿名内部类
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"线程1");
            }
        }).start();

        //lambda表达式
        new Thread(() ->{
            System.out.println(Thread.currentThread().getName()+"线程2");
        }).start();

        //优化lambda表达式
        new Thread(() ->System.out.println(Thread.currentThread().getName()+"线程2")).start();
    }
}

8.1 Lambda的使用前提

Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:

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

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

内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+“线程1”);
}
}).start();

    //lambda表达式
    new Thread(() ->{
        System.out.println(Thread.currentThread().getName()+"线程2");
    }).start();

    //优化lambda表达式
    new Thread(() ->System.out.println(Thread.currentThread().getName()+"线程2")).start();
}

}


## 8.1 Lambda的使用前提

Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值