Java基础-多线程

Java基础-多线程

什么是进程

  • 进程就是正在运行中的程序,驻留在内存中
  • 进程是系统资源分配和调度的独立单元
  • 每一个进程都有属于自己的存储空间和系统资源
  • 进程之间内存独立、不共享
  • 多进程:操作系统能同时运行多个任务(程序)

什么是线程

  • 线程(Thread)是一个程序内部的一条执行路径,进程中的单个顺序控制流
  • 之前所学,启动程序执行后,main方法的执行其实就是一条单独的执行路径
  • 程序中如果只有一条执行路径,那么这个程序就是单线程的程序
  • 多线程:一个进程中包含多个顺序控制流(多条执行路径)
image-20240203103059099

线程和进程都分为 5 个阶段: 创建就绪运行阻塞终止;

并发并行

  • 正在运行的程序(软件)就是一个独立的进程,线程是属于进程的,多个线程其实是 并发与并行 同时进行的

  • 并发

    • CPU同时处理线程的数量有限
    • CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,但这些线程并非同时执行
  • 并行

    • 在同一时刻,同时有多个线程在被CPU处理并执行

线程分类

  • Java中线程分为两大类:用户线程( 主线程main方法 )和守护线程(后台线程 代表:垃圾回收线程)

守护线程特点

  • 一般守护线程是一个死循环,所有的用户线程只要结束 ,守护线程自动结束。

守护线程用在什么地方

  • 每天00:00系统数据自动备份
    • 这个需要使用定时器,并且我们可以将定时器设置为守护线程。一直在哪里看着,没到 00:00的时候就备份一份。
    • 所有 用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。
1.1 线程状态

线程的6种状态互相转换

image-20240203110007695
  • new (新建) : 线程刚被创建,但是并未启动

  • Runnable(可运行):线程已经调用了start()等待CPU调度

  • Blocked(锁阻塞):线程在执行的时候,未竞争到锁对象,则该线程进入Blocked状态

  • Waiting(无线等待):一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒

  • 阻塞状态 是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
    (一)等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
    (二)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
    (三)其他阻塞:运行的线程执行sleep()join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)

  • Timed Waiting(计时等待):同Waiting状态,有几个方法有超时参数,调用它们进入 Timed Waiting状态。带有超时参数的常用方法有 Thread.sleep、Object.wait

  • Teminated(被终止):因run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡

1.2 多线程实现方式
1.2.1 继承Thread

方法(一)extends Thread类

  • 1.定义一个子类 MyThread继承线程类 java.lang.Thread,重写run方法

  • 2.创建MyThread类的对象

  • 3.调用线程对象的start()方法启动线程(启动后还有执行run方法的)

继承 Thread 优缺点

  • 优点:编码简单
  • 缺点:线程类继承了 Thread , 无法多继承其他类,不利于拓展
public class CreateThreadDemo1 {
    public static void main(String[] args) {
        //1.创建线程对象
        Thread thread = new MyThread();
        //2.start 启动线程,自动运行run方法(线程任务)
        thread.start();
        for (int i = 0; i < 5; i++) {
            System.out.println("主线程运行:" + i);
        }
    }
}
/**
 *  定义一个线程类 继承 Thread类
 */
class MyThread extends Thread{
    /**
     * 重写 run 方法
     */
    @Override
    public void run() {
        for (int i = 0; i < 8; i++) {
            System.out.println("子进程运行:" + i);
        }
    }
}

运行结果:

主线程运行:0
主线程运行:1
主线程运行:2
主线程运行:3
主线程运行:4
主线程运行:5
主线程运行:6
子进程运行:0
子进程运行:1
子进程运行:2
主线程运行:7
主线程运行:8
主线程运行:9
子进程运行:3
子进程运行:4
子进程运行:5
子进程运行:6
子进程运行:7
子进程运行:8
子进程运行:9

  • 为什么不直接调用run方法,而是调用start方法?

    • 直接调用run方法会当成普通方法执行,不会启动线程,不会分配新的分支栈,此时相当于 单线程执行
    • 只有调用start方法才是启动一个新的线程执行,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。线程就启动成功了。启动成功的线程会自动调用 run 方法(由JVM线程调用机制来运作的),并且run方法在分支栈的栈底部(压栈)。run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。单纯使用run()方法是不能多线程并发的。
  • 把主线程方法子线程之前, JVM主线程顺序跑完,相当于 单线程的效果

  • main方法也是一个线程(主线程);在Java中,每次程序运行至少启动 2个线程。一个是main线程,一个垃圾收集线程。

1.2.2 实现Runnable

implements Runnbale ,实现 java.lang.Runnable接口

  • 定义一个线程 任务类 MyRunnable 实现 Runnable接口 ,重写run()方法
  • 创建 MyRunnable 任务对象
  • 把 MyRunnbale 任务对象交给 Thread 处理
  • 调用线程对象的 start() 方法启动线程
Thread构造器说明
public Thread(String name)可以为当前线程指定名称
public Thread(Runnable target)封装Runnable任务对象,成为线程对象
public Thread(Runnable target,String name)封装Runnable任务对象,成为线程对象,并指定线程名称
public class CreateThreadDemo2 {
    public static void main(String[] args) {
        //2.创建任务对象
        MyRunnable target = new MyRunnable();
        //3.将任务对象 交给Thread处理
        Thread thread = new Thread(target);
        //4.调用线程对象的start()方法启动线程
        thread.start();
//        new Thread(new MyRunnable()).start();
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程运行:" + i);
        }
    }
}
/**
 *  1.继承Runnable接口,实现任务类
 */
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程运行:" + i);
        }
    }
}

运行结果:

主线程运行:0
主线程运行:1
主线程运行:2
主线程运行:3
子线程运行:0
子线程运行:1
子线程运行:2
子线程运行:3
子线程运行:4
子线程运行:5
子线程运行:6
子线程运行:7
子线程运行:8
子线程运行:9
主线程运行:4
主线程运行:5
主线程运行:6
主线程运行:7
主线程运行:8
主线程运行:9

实现 Runnable 优缺点

  • 优点:线程任务类 只是实现接口,可以 继续 继承类和实现接口,扩展性强
  • 缺点:编程多一层对象包装,如果线程有执行结果是 不可以直接返回的
1.2.3 Thread和Runnable

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

区别

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

  • 适合多个相同的程序代码的线程去处理同一个资源
  • 可以避免 java 中的单继承的限制
  • 增加程序的健壮性 , 代码可以被多个线程共享,代码和数据独立
  • 线程池 只能放入实现Runnablecallable 类线程,不能直接放入继承 Thread 的类

两者创建方法都存在一个问题

  • 它们重写 run 方法 均不能直接返回结果
  • 不适合需要返回线程执行结果的业务场景
  • 解决:
    • JDK 5.0提供了 CallableFutureTask 来实现(可以得到线程执行结果)
1.2.4 Callable和FutureTask

Callable源码

public interface Callable<V> {
    V call() throws Exception;
}

FutureTask

public class FutureTask<V> implements RunnableFuture<V> 
public interface RunnableFuture<V> extends Runnable, Future<V>
  • 得到任务对象
    • 1.定义类实现Callable接口,重写call方法,封装要做的事情
    • 2.用FutureTask把Callable对象封装成线程任务对象
  • 把线程任务对象交给 Thread 处理
  • 调用 Thread 的start 方法启动线程,执行任务
  • 线程执行完毕后,通过FutureTaskget方法,去获取任务执行的结果
//1.实现Callable接口
class MyCallable implements Callable<String>{
    private int n;

    public MyCallable(int n){
        this.n = n;
    }
    /**
     * 2.重写call方法
     * @return 前n个数的累加
     * @throws Exception
     */
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }
        return "子线程求和为:"+sum;
    }
}

public class CreateThreadDemo3 {
    public static void main(String[] args) {
        //3.创建 任务类对象
        Callable<String> ca = new MyCallable(300);
        //4.使用FutureTask 包装 任务类对象
        //class FutureTask<V> implements RunnableFuture , interface RunnableFuture<V> extends Runnable
        //FutureTask作用1: 是 Runnable实现类,可以用于创建线程;
        //FutureTask作用2: 可以在线程执行完毕后,调用get方法得到线程执行完毕后的结果
        FutureTask<String> ft = new FutureTask<>(ca);
        //5.创建线程
        Thread thread = new Thread(ft);
        //6.启动线程
        thread.start();

        System.out.println("================");
        Callable<String> ca1 = new MyCallable(10);
        //4.使用FutureTask 包装 任务类对象
        FutureTask<String> ft1 = new FutureTask<>(ca1);
        //5.创建线程
        Thread thread1 = new Thread(ft1);
        //6.启动线程
        thread1.start();

        try {
            //如果 任务 没有执行完毕,这里代码会等待,直到线程跑完才提取结果
            System.out.println("第一个结果:"+ft.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            System.out.println("第二个结果:"+ft1.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

================
第一个结果:子线程求和为:45150
第二个结果:子线程求和为:55

三种方法对比

image-20240204102129908
1.3 Thread常用API

常用方法

方法名说明
String getName()获取当前线程的名称,默认线程名称为 Thread-索引
String setName()设置线程名称
public static Thread currentThread()返回对当前正在执行的线程对象的引用
public static void sleep(long time)让线程休眠指定的时间,单位为毫秒
public void run()线程任务方法
public void start()线程启动方法

构造器

构造器说明
public Thread(String name)可以为当前线程指定名称
public Thread(Runnable target)把Runnable对象交给线程对象
public Thread(Runnable target , String name)把Runnable对象交给线程对象,并指定线程名称

线程的控制方法

  • 调整线程优先级,理论上优先级高的,获得较多的运行机会。

    • Thread类的setPriority()getPriority()方法分别用来设置获取线程的优先级。
  • 线程睡眠:Thread.sleep(long millis)方法,使线程转为 计时等待状态 , 睡眠结束后,转为 就绪(Runnable)状态

  • 线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify()notifyAll()方法唤醒。

  • 线程让步:Thread.yield()方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程

    • yield()方法不是阻塞方法,而是由运行状态转为就绪状态
  • 线程加入:Thread.join()方法,当前线程中调用另一个线程的join()方法,则当前线程转为阻塞状态,直到另一个线程运行结束,当前线程再由 阻塞状态转为就绪状态。

    • 主线程利用子线程 执行完毕后的结果
  • 线程唤醒:Object类中的notify方法。针对线程通过 wait方法后在对象的监视器中等待,notify方法唤醒在此对象监视器上等待的单个线程,而获得锁对象。

模拟两个用户 对 同一个账户,取出余额的情况

public class Account {
    private String accId;
    private double money;
    public Account(){}
    public Account(String accId, double money) {
        this.accId = accId;
        this.money = money;
    }
    /**
     * 线程操作,账户取钱功能
     * @param money 请求的取款金额
     */
    public void drawMoney(double money){
        //1.获取当前线程的名称
        Thread thread = Thread.currentThread();
        //2.判断余额是否足够
        if (this.money >= money){
            System.out.println(thread.getName() + "取款" + money + "成功!!!");
            this.money -= money;
            System.out.println("当前账户余额为:" + this.money);
        }else {
            System.out.println("抱歉,当前余额不足");
            System.out.println("当前余额为:"+this.money);
        }
    }
	//省略了getXxx/setXxx方法
}
public class ThreadSafeTest {
    public static void main(String[] args) {
        //1.创建账户对象
        Account acc = new Account("GBK",100000);
        //2.启动两个线程,操作同一账户
        new MyThread(acc,"小明").start();
        new MyThread(acc,"小红").start();
    }
}
//继承Thread , run方法 调用 相应accId取钱功能
class MyThread extends Thread{
    //线程操作的当前账户
    private Account acc ;
    public MyThread(Account acc , String name){
        super(name);
        this.acc = acc;
    }
    @Override
    public void run() {
        //取钱功能
        acc.drawMoney(100000.0);
    }
}

运行结果:

小明取款100000.0成功!!!
当前账户余额为:0.0
小红取款100000.0成功!!!
当前账户余额为:-100000.0

出现了 线程安全的问题!!!

1.4 线程安全

线程安全问题发生的原因是什么?

  • 多个线程同时访问同一个共享资源且存在修改该资源
1.5 线程同步

线程同步解决安全问题的思想是什么?

  • 加锁:让多个线程实现先后依次访问共享资源,这样就解决了安全问题
1.5.1 同步代码块

同步代码块:

  • 作用:把出现线程安全问题的核心代码 给 上锁

  • 原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行

格式

synchronized(同步锁对象){
	操作共享资源的代码(核心代码)
}
public void drawMoney(double money){
    //1.获取当前线程的名称
    Thread thread = Thread.currentThread();
    //同步代码块
    /**
        小明 小红
        this == acc 共享资源
     */
    //对出现线程安全问题的核心代码上锁,每次只能一个线程进入,执行完毕后自动解锁
    //synchronized(同步锁对象) , 同一个锁对象的线程需要排队
    //实例方法 this ; 静态方法用 共享资源类名.class
    synchronized (this) {
        //2.判断余额是否足够
        if (this.money >= money){
            System.out.println(thread.getName() + "取款" + money + "成功!!!");
            this.money -= money;
            System.out.println("当前账户余额为:" + this.money);
        }else {
            System.out.println("抱歉,"+this.getAccId()+"余额不足");
        }
    }
}

运行结果:

小明取款100000.0成功!!!
当前账户余额为:0.0
小黑取款100000.0成功!!!
抱歉,ASCII余额不足
当前账户余额为:0.0
抱歉,ASCII1余额不足

锁对象要求

  • 理论上:锁对象只要对于当前 同时执行的线程来说是同一个对象
  • 同一个锁的线程之间要排队,避免无关线程排队

锁对象用任意唯一的对象好不好

  • 不好,会影响其他无关线程的执行

锁对象的规范要求

  • 规范上:建议使用共享资源 作为 锁对象
  • 对于实例方法 建议 使用 this 作为锁对象
  • 对于静态方法建议使用字节码(类名.class)对象作为锁对象。
1.5.2 同步方法

同步方法

  • 不同方法使用同一把锁,也是要排队
  • 实例方法,调用 this.成员 变量/方法,访问资源
  • 作用把出现 线程安全问题的核心方法给上锁
  • 原理每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行

格式

修饰符 synchronized 返回值类型 方法名称(形参列表){
	操作共享资源的代码
}

同步方法是如何保证线程安全的

  • 对出现问题的核心方法 使用 synchronized修饰
  • 每次只能 一个线程 占锁 进入访问

同步方法的同步锁对象的原理

  • 对于实例方法默认使用 this 作为锁对象
  • 对于静态方法默认使用 类名.class 对象作为锁对象
//synchronized 指明 为同步方法
public synchronized void drawMoney(double money){
    //1.获取当前线程的名称
    Thread thread = Thread.currentThread();
    //2.判断余额是否足够
    if (this.money >= money){
        System.out.println(thread.getName() + "取款" + money + "成功!!!");
        this.money -= money;
        System.out.println("当前账户余额为:" + this.money);
    }else {
        System.out.println("抱歉,当前余额不足");
        System.out.println("当前余额为:"+this.money);
    }
}
1.5.3 Lock 锁

Lock锁

  • 为了更清晰的表达如何 加锁 和 释放锁,JDK5以后提供了一个新的锁对象Lock , 更加灵活、方便
  • Lock 实现提供 比使用 synchronized 方法和语句 可以获得更广泛的锁定操作
  • Lock 是接口不能直接实例化,这里采用它的实现类 RenntrantLock 来构建 Lock 锁对象

获取Lock锁对象

  • public ReentrantLock() 获得Lock锁的实现类对象

Lock的API

  • void lock() 获得锁
  • void unlock() 释放锁
//成员变量 Lock锁对象
private final ReentrantLock lock = new ReentrantLock();
 public void drawMoney(double money){
     //1.获取当前线程的名称
     Thread thread = Thread.currentThread();
     try {
         //上锁
         lock.lock();
         //2.判断余额是否足够
         if (this.money >= money){
             System.out.println(thread.getName() + "取款" + money + "成功!!!");
             this.money -= money;
             System.out.println("当前账户余额为:" + this.money);
         }else {
             System.out.println("抱歉,当前余额不足");
             System.out.println("当前余额为:"+this.money);
         }
     }catch (Exception e){
         e.printStackTrace();
     } finally {
         //解锁
         lock.unlock();
     }
 }
1.6 线程通信

什么是线程通信

  • 所谓线程通信就是 线程间 想和发送数据,线程通信 通常共享一个数据的方法实现(注意 线程安全)
  • 线程间 会 根据共享数据的情况决定自己该怎么做,以及通过其他线程怎么做

线程通信的常见模型

  • 生产者与消费者模型:生产者线程负责生产数据,消费者线程负责消费数据
    • 要求:生产者线程生产完数据后,唤醒消费者,然后等待自己; 消费者消费完该数据后,唤醒生产者,然后等待自己

同步锁对象的调用方法(Object 类的等待和唤醒方法先唤醒所有,在挂起自己

方法名称说明
void wait()让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或notifyAll()的方法
void notify()唤醒正在等待的单个线程
void notifyAll()唤醒正在等待的所有线程
//账户实体类
public class Account {
    private String accId;
    private double money;
    public Account(){}
    public Account(String accId, double money) {
        this.accId = accId;
        this.money = money;
    }
    /**
     * 账户取款
     * @param money 取款金额
     */
    public synchronized void drawMoney(double money) {
        try {
            String name = Thread.currentThread().getName();
            //余额是否足够
            if (this.money >= money){
                System.out.println(name + "成功取出" + money +"当前余额为:"+(this.money-money));
                this.money -= money;
            }else {
                //先唤醒所有线程,当前线程再等待  !!!!!!!!
                this.notifyAll();//唤醒
                this.wait();	 //挂起
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 账户存款
     * @param money 存款金额
     */
    public synchronized void deposit(double money) {
        try {
            String name = Thread.currentThread().getName();
            //余额是否为0
            if (this.money == 0.0){
                //为0
                System.out.println(name + "成功存款" + money +"当前余额为:"+(this.money+money));
                this.money += money;
            }
            this.notifyAll();
            this.wait();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    //省略getXxx/setXxx方法
}    
/**
 *  目标:模拟多个存储与多个消费 , 理解线程通信流程
 *  线程通信,类似生产者与消费者,生产数据与消费数据(挂起与唤醒)
 */
public class ThreadSafeTest {
    public static void main(String[] args) {
        //1.创建账户对象
        Account acc = new Account("ASCII",100000);
        //2.启动两个消费者线程
        new Consumption(acc,"小明").start();
        new Consumption(acc,"小红").start();
        //3.启动三个生产者线程
        new Conduct(acc,"亲爹").start();
        new Conduct(acc,"干爹").start();
        new Conduct(acc,"岳父").start();
    }
}
//生产者线程
class Conduct extends Thread{
    //线程操作的当前账户
    private Account acc ;
    public Conduct(Account acc , String name){
        super(name);
        this.acc = acc;
    }
    @Override
    public void run() {
        //存钱功能
        while (true) {
            acc.deposit(100000.0);
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
//消费者线程
class Consumption extends Thread{
    //线程操作的当前账户
    private Account acc ;
    public Consumption(Account acc , String name){
        super(name);
        this.acc = acc;
    }
    @Override
    public void run() {
        //取钱功能
        while (true) {
            acc.drawMoney(100000.0);
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果

小明成功取出100000.0当前余额为:0.0
岳父成功存款100000.0当前余额为:100000.0
小红成功取出100000.0当前余额为:0.0
岳父成功存款100000.0当前余额为:100000.0
小明成功取出100000.0当前余额为:0.0
岳父成功存款100000.0当前余额为:100000.0
小红成功取出100000.0当前余额为:0.0
干爹成功存款100000.0当前余额为:100000.0
小红成功取出100000.0当前余额为:0.0
亲爹成功存款100000.0当前余额为:100000.0
小明成功取出100000.0当前余额为:0.0
岳父成功存款100000.0当前余额为:100000.0
小明成功取出100000.0当前余额为:0.0
岳父成功存款100000.0当前余额为:100000.0
小明成功取出100000.0当前余额为:0.0

1.7 线程池(重点)

什么是线程池

  • 线程池:首先创建一些线程,它们的集合称之为线程池

  • 线程池在系统启动时会创建 一些空闲线程,程序将一个任务传递给线程池,线程池就会启动一条线程来执行这个任务,执行任务后线程不会销毁(死亡),而是再次返回到线程池中成为空闲状态,等待执行下一个任务。

  • 线程池就是 一个 可以复用线程的技术

为什么要使用线程池

  • 如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能

线程池的工作机制

  • 在线程池的编程模式下,任务是分配给整个线程池的,而不是直接提交给某个线程。线程池拿个任务后,就会在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲线程。
image-20240204201936245

线程池代表:JDK5.0 ExecutorService

如何获取线程池对象

  • 1 使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象

  • 2 使用Exceutors(线程池的工具类)调用方法返回不同特点的线程池对象

1.7.1 ThreadPoolExceutor
public ThreadPoolExecutor
(int corePoolSize,          //核心线程数量      
int maximumPoolSize,		//可支持的最大线程数
long keepAliveTime,			//临时线程的 最长存活时间
TimeUnit unit,              //存活时间的单位  TimeUnit.SECONDS
BlockingQueue<Runnable> workQueue,  //任务队列   new ArrayBlockingQueue<>(5)
ThreadFactory threadFactory,		//线程工厂,创建线程 Executors.defaultThreadFactory()
RejectedExecutionHandler handler    //任务满时,处理,  new ThreadPoolExecutor.AbortPolicy()
)

参数实例: 正式员工数,最大员工数,临时工”不工作“最长时间,时间单位,可排队(等待)任务队列,招工头,达到最大员工数和任务队列后的处理方式(拒绝)

  • 临时线程 = 最大线程数- 核心线程
  • 总任务 = 最大线程 + 任务队列(等待)

临时线程什么时候创建

  • 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

什么时候开始拒绝任务

  • 核心线程和临时线程都在忙,任务队列也满了。新任务过来的时候才会开始任务拒绝。

新任务拒绝策略

image-20240204225058180

常用方法

  • image-20240204224209612

Runnable任务

public class ThreadPoolExecutorDemo {
    public static void main(String[] args) {
        //1.创建线程池
        ExecutorService pool = new ThreadPoolExecutor(
                3,5,6, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        //2.给线程池任务
        Runnable target = new MyRunnable();
        //核心线程
        //执行任务 , pool.execute(Runnable r)
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        //等待队列
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        //临时工 两位
        pool.execute(target);
        pool.execute(target);
        //拒绝任务
//        pool.execute(target);   //RejectedExecutionException
        pool.shutdown();    //任务执行完毕后,关闭线程池(常用)
//        pool.shutdownNow(); //当前立即停止,返回队列中未执行的任务
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("当前线程" + Thread.currentThread().getName() + "处理次数:" + i);
        }
        try {
            System.out.println("线程" + Thread.currentThread().getName() + "与任务绑定,并进入睡眠");
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

结果:

当前线程pool-1-thread-1处理次数:1
当前线程pool-1-thread-1处理次数:2
当前线程pool-1-thread-1处理次数:3
当前线程pool-1-thread-1处理次数:4
当前线程pool-1-thread-1处理次数:5
线程pool-1-thread-1与任务绑定,并进入睡眠
当前线程pool-1-thread-4处理次数:1
当前线程pool-1-thread-4处理次数:2
当前线程pool-1-thread-2处理次数:1
当前线程pool-1-thread-2处理次数:2
当前线程pool-1-thread-2处理次数:3
当前线程pool-1-thread-2处理次数:4
当前线程pool-1-thread-2处理次数:5
线程pool-1-thread-2与任务绑定,并进入睡眠
当前线程pool-1-thread-3处理次数:1
当前线程pool-1-thread-3处理次数:2
当前线程pool-1-thread-4处理次数:3
当前线程pool-1-thread-5处理次数:1
当前线程pool-1-thread-5处理次数:2
当前线程pool-1-thread-4处理次数:4
当前线程pool-1-thread-4处理次数:5
线程pool-1-thread-4与任务绑定,并进入睡眠
当前线程pool-1-thread-3处理次数:3
当前线程pool-1-thread-3处理次数:4
当前线程pool-1-thread-3处理次数:5
线程pool-1-thread-3与任务绑定,并进入睡眠
当前线程pool-1-thread-5处理次数:3
当前线程pool-1-thread-5处理次数:4
当前线程pool-1-thread-5处理次数:5
线程pool-1-thread-5与任务绑定,并进入睡眠
当前线程pool-1-thread-5处理次数:1
当前线程pool-1-thread-5处理次数:2
当前线程pool-1-thread-5处理次数:3
当前线程pool-1-thread-5处理次数:4
当前线程pool-1-thread-5处理次数:5
线程pool-1-thread-5与任务绑定,并进入睡眠
当前线程pool-1-thread-1处理次数:1
当前线程pool-1-thread-1处理次数:2
当前线程pool-1-thread-1处理次数:3
当前线程pool-1-thread-1处理次数:4
当前线程pool-1-thread-3处理次数:1
当前线程pool-1-thread-3处理次数:2
当前线程pool-1-thread-3处理次数:3
当前线程pool-1-thread-3处理次数:4
当前线程pool-1-thread-3处理次数:5
线程pool-1-thread-3与任务绑定,并进入睡眠
当前线程pool-1-thread-4处理次数:1
当前线程pool-1-thread-4处理次数:2
当前线程pool-1-thread-4处理次数:3
当前线程pool-1-thread-4处理次数:4
当前线程pool-1-thread-4处理次数:5
线程pool-1-thread-4与任务绑定,并进入睡眠
当前线程pool-1-thread-1处理次数:5
线程pool-1-thread-1与任务绑定,并进入睡眠
当前线程pool-1-thread-2处理次数:1
当前线程pool-1-thread-2处理次数:2
当前线程pool-1-thread-2处理次数:3
当前线程pool-1-thread-2处理次数:4
当前线程pool-1-thread-2处理次数:5
线程pool-1-thread-2与任务绑定,并进入睡眠

Callable任务

public class ThreadPoolExecutorDemo1 {
    public static void main(String[] args) throws Exception{
        //1.创建线程池
        ExecutorService pool = new ThreadPoolExecutor(
                3,5,6, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        //2.给线程池任务
        //<T> Future<T> submit(Callable<T> task);
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));
        Future<String> f5 = pool.submit(new MyCallable(500));
        Future<String> f6 = pool.submit(new MyCallable(1000));
        //获取 任务结果
        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
        System.out.println(f5.get());
        System.out.println(f6.get());
        pool.shutdown();
    }
}
class MyCallable implements Callable<String>{
    private int n;
    public MyCallable(int n){
        this.n = n;
    }
    /**
     * 2.重写call方法
     * @return
     * @throws Exception
     */
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }
        return "线程"+Thread.currentThread().getName()+"对1-"+n+"的求和为:"+sum;
    }
}

结果:

线程pool-1-thread-1对1-100的求和为:5050
线程pool-1-thread-2对1-200的求和为:20100
线程pool-1-thread-3对1-300的求和为:45150
线程pool-1-thread-3对1-400的求和为:80200
线程pool-1-thread-3对1-500的求和为:125250
线程pool-1-thread-3对1-1000的求和为:500500

1.7.2 Executors

Executors : 线程池的工具类,通过调用方法返回不同类型的线程池对象

image-20240204225615622

Executors使用可能存在的陷阱:

  • 大型并发系统环境中,使用Executors 如果不注意可能会出现系统风险
image-20240204225818009
public class ExecutorsDemo {
    public static void main(String[] args) {
        //四种类型的线程池
        ExecutorService pool = Executors.newFixedThreadPool(3);

        pool.execute(new MyRunnable());
        pool.execute(new MyRunnable());
        pool.execute(new MyRunnable());
        pool.execute(new MyRunnable());
        pool.shutdown();
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <=3; i++) {
            System.out.println("当前线程" + Thread.currentThread().getName() + "处理次数:" + i);
        }
        try {
            System.out.println("线程" + Thread.currentThread().getName() + "与任务绑定,并进入睡眠");
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

结果:

当前线程pool-1-thread-1处理次数:1
当前线程pool-1-thread-1处理次数:2
当前线程pool-1-thread-1处理次数:3
当前线程pool-1-thread-3处理次数:1
当前线程pool-1-thread-2处理次数:1
当前线程pool-1-thread-2处理次数:2
当前线程pool-1-thread-2处理次数:3
线程pool-1-thread-2与任务绑定,并进入睡眠
当前线程pool-1-thread-3处理次数:2
线程pool-1-thread-1与任务绑定,并进入睡眠
当前线程pool-1-thread-3处理次数:3
线程pool-1-thread-3与任务绑定,并进入睡眠
当前线程pool-1-thread-1处理次数:1
当前线程pool-1-thread-1处理次数:2
当前线程pool-1-thread-1处理次数:3
线程pool-1-thread-1与任务绑定,并进入睡眠

1.8 定时器

Timer定时器

  • 构造器: public Timer() 创建Timer定时器对象

  • 方法public void schedule(TimerTask task , long delay , long period) 开启一个定时器,按照计划处理TimerTask任务

Timer定时器的特点和存在的问题:

  • 1 Timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入。
  • 2 可能因为其中的某个任务的异常使 Timer 线程死掉,从而影响后续任务执行
public class TimerDemo1 {
    public static void main(String[] args) {
        //1.创建 空构造器
        Timer timer = new Timer();
        //2.使用 schedule方法
        // void schedule(TimerTask task, long delay, long period)
        //参数一:任务类对象;参数二:延长时间(毫秒);参数三:周期间隔
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                for (int i = 1; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName()+"执行AAA第" + i + "次");
                }
                try {
                    Thread.sleep(5000);
                    System.out.println(10 / 0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },2000,2000);
        //问题1存在验证影响 其他定时器的按时运行
        //问题2存在线程异常,直接抛出,影响后续运行
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                for (int i = 1; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName()+"执行BBB第" + i + "次");
                }
            }
        },0,3000);
    }
}

结果:

Timer-0执行BBB第1次
Timer-0执行BBB第2次
Timer-0执行BBB第3次
Timer-0执行BBB第4次
Timer-0执行AAA第1次
Timer-0执行AAA第2次
Timer-0执行AAA第3次
Timer-0执行AAA第4次
Exception in thread "Timer-0" java.lang.ArithmeticException: / by zero

最优 : 使用线程池

  • ScheduledExecutorService是JDK1.5中引入了并发包
  • 目的是为了 弥补Timer的缺陷,ScheduledExecutorService内部为线程池

获取线程池对象

  • public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

周期调用方法

  • public ScheduledFuture< ? > scheduleAtFixedRate(Runnable command , long initalDelay , long period , TimeUnit unit)
public class ScheduledExecutorServiceDemo1 {
    public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");

    public static void main(String[] args) {
        //1.创建 空构造器
        //public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(3); //核心线程数量
        //2.定时
        //public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
        //                                                  long initialDelay,
        //                                                  long period,
        //                                                  TimeUnit unit);
        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                for (int i = 1; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName()+"执行AAA第" + i + "次"+sdf.format(new Date()));
                }
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },0,5, TimeUnit.SECONDS);

        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                for (int i = 1; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName()+"执行BBB第" + i + "次"+sdf.format(new Date()));
                }
                System.out.println(10 / 0);
            }
        },0,2, TimeUnit.SECONDS);

        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                for (int i = 1; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName()+"执行CCC第" + i + "次"+sdf.format(new Date()));
                }
            }
        },0,2, TimeUnit.SECONDS);
    }
}

结果:

pool-1-thread-1执行AAA第1次2024:02:04 23:36:55
pool-1-thread-2执行BBB第1次2024:02:04 23:36:55
pool-1-thread-1执行AAA第2次2024:02:04 23:36:55
pool-1-thread-2执行BBB第2次2024:02:04 23:36:55
pool-1-thread-1执行AAA第3次2024:02:04 23:36:55
pool-1-thread-2执行BBB第3次2024:02:04 23:36:55
pool-1-thread-1执行AAA第4次2024:02:04 23:36:55
pool-1-thread-2执行BBB第4次2024:02:04 23:36:55
pool-1-thread-2执行CCC第1次2024:02:04 23:36:55
pool-1-thread-2执行CCC第2次2024:02:04 23:36:55
pool-1-thread-2执行CCC第3次2024:02:04 23:36:55
pool-1-thread-2执行CCC第4次2024:02:04 23:36:55
pool-1-thread-2执行CCC第1次2024:02:04 23:36:57
pool-1-thread-2执行CCC第2次2024:02:04 23:36:57
pool-1-thread-2执行CCC第3次2024:02:04 23:36:57
pool-1-thread-2执行CCC第4次2024:02:04 23:36:57
pool-1-thread-2执行CCC第1次2024:02:04 23:36:59
pool-1-thread-2执行CCC第2次2024:02:04 23:36:59
pool-1-thread-2执行CCC第3次2024:02:04 23:36:59
pool-1-thread-2执行CCC第4次2024:02:04 23:36:59

参考

Java多线程学习

Java多线程

  • 22
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值