多线程万字详解

进程线程是计算机程序执行的两个重要概念。

1.进程: 进程是操作系统分配资源的基本单位,每个进程都有自己独立的地址空间,每启动一个进程,系统就会为它分配内存。进程间通信比较复杂,需要用到IPC(InterProcess Communication,进程间通信)机制。

进程有五种状态:创建、就绪、运行、阻塞和终止。

2.线程: 线程是进程中的一个执行单元,一个进程可以包含多个线程,它们共享进程的地址空间和资源。线程间通信比进程间通信要简单很多,因为它们可以直接读写进程数据段(如全局变量)来进行通信。

线程也有五种状态:创建、就绪、运行、阻塞和终止。

3.进程和线程的区别

  • 独立性:进程间的内存空间是独立的,而线程共享进程的内存空间。

  • 资源消耗:创建或销毁进程时,系统性能开销明显,而线程的资源消耗远小于进程。

  • 通信方式:进程间通信需要使用IPC机制,而线程可以直接读写全局变量进行通信。

  • 影响:一个进程崩溃后,其他进程不受影响;而一个线程崩溃,会导致整个进程崩溃。

4.多线程和多进程的选择: 在需要进行频繁通信的情况下,多线程更有优势,因为它们可以直接读写内存进行通信。而在需要大量计算且相互独立的情况下,多进程可能更合适,因为它们不会因为一个进程的崩溃而影响其他进程。

并发和并行

并发:在同一时刻,有多个指令在单个CPU上交替执行。

并行:在同一时刻,有多个指令在多个CPU上同时执行。

实现多线程方式一:继承Thread类

方法介绍
方法名说明
void run()在线程开启后,此方法将被调用执行
void start()使此线程开始执行,Java虚拟机会调用run方法()

实现步骤

  • 定义一个类MyThread继承Thread类

  • 在MyThread类中重写run()方法

  • 创建MyThread类的对象

  • 启动线程

  • 代码演示

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"Hello World");
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        /*
        多线程第一种启动方式
        1.自己定义一个类继承Thread
        2.重写run方法
        3.创建子类的对象,并启动线程
         */
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("线程一");
        t2.setName("线程二");
        //开启线程
        t1.start();
    }
}

 

实现多线程方式二:实现Runnable接口

Thread构造方法

方法名说明
Thread(Runnable target)分配一个新的Thread对象
Thread(Runnable target, String name)分配一个新的Thread对象

 

实现步骤

  • 定义一个类MyRunnable实现Runnable接口

  • 在MyRunnable类中重写run()方法

  • 创建MyRunnable类的对象

  • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数

  • 启动线程

public class MyRun implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //获取到当前线程的对象
            Thread t = Thread.currentThread();

            System.out.println(t.getName()+"Hello World");
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        /*
        多线程第二种启动方式
        1.自己定义一个类继承Runnable接口
        2.重写run方法
        3.创建自己类的对象
        4.创建一个Thread类的对象,并开启线程
         */

        //创建MyRun的对象
        //表示多线程要执行的任务
        MyRun mr = new MyRun();

        //创建线程对象
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);

        t1.setName("线程一");
        t2.setName("线程二");

        //开启线程
        t1.start();
    }
}

 

实现多线程方式三: 实现Callable接口

方法介绍

方法名说明
V call()计算结果,如果无法计算结果,则抛出一个异常
FutureTask(Callable<V> callable)创建一个 FutureTask,一旦运行就执行给定的 Callable
V get()如有必要,等待计算完成,然后获取其结果

 

实现步骤

  • 定义一个类MyCallable实现Callable接口

  • 在MyCallable类中重写call()方法

  • 创建MyCallable类的对象

  • 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数

  • 创建Thread类的对象,把FutureTask对象作为构造方法的参数

  • 启动线程

  • 再调用get方法,就可以获取线程结束之后的结果。

代码演示

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum = sum + i;
        }
        return sum;
    }
}

 

public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /*
        多线程的第三种实现方式
        特点:可以获取到多线程运行到的结果
        1.创建一个类MyCallable实现Callable接口
        2.重写call(有返回值,表示多线程运行的结果)
        3.创建MyCallable的对象(表示多线程要执行的任务)
        4.创建FutureTask的对象(作用管理多线程运行的结果)
        5.创建Thread类的对象,并启动

         */
        MyCallable mc = new MyCallable();
        FutureTask<Integer> ft = new FutureTask(mc);
        Thread thread = new Thread(ft);
        thread.start();

        //获取多线程运行的结果
        Integer result = ft.get();
        System.out.println(result);


    }
}

 Java中多线程的三种主要实现方式各有其特点和适用场景。具体来说:

  • 继承Thread类:通过继承Thread类并重写run方法来实现多线程,这种方法简单直观,但在Java的单继承体系下,可能会导致类的扩展性受限。
  • 实现Runnable接口:通过实现Runnable接口并将该实现类的实例传递给Thread对象来创建线程,这种方式避免了单继承的限制,提高了代码的可读性和可维护性。
  • 实现Callable接口:与Runnable类似,但可以返回执行结果,通常与FutureTask结合使用,允许线程执行完毕后获取返回值,增加了线程的使用场景。

总的来说,如果需要简单的线程逻辑并且不需要额外的返回结果,可以选择继承Thread类或实现Runnable接口。而当需要更复杂的线程管理和控制,或者希望利用线程池等高级特性时,应该考虑使用Executor框架。

线程休眠

相关方法

方法名说明
static void sleep(long millis)使当前正在执行的线程停留(暂停执行)指定的毫秒数

 代码演示

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        /*System.out.println("睡觉前");
        Thread.sleep(3000);
        System.out.println("睡醒了");*/

        MyRunnable mr = new MyRunnable();

        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);

        t1.start();
        t2.start();
    }
}

线程优先级

线程调度

在Java中,除了上述两种基本的调度方式,还可以通过编程手段对线程的执行顺序进行一定程度的控制。例如,可以在需要等待或者后执行的线程中加入sleep()方法,使其在进入CPU调度初期就进入休眠状态,从而给其他线程执行的机会。

优先级相关方法代码演示

  • 线程调度是指在多线程环境中,操作系统或运行时环境如何分配CPU资源给各个线程的过程。

    线程调度主要有两种调度方式:

  • 分时调度:这种调度方式下,CPU的时间被平均分配给所有线程。每个线程轮流获得CPU的使用权,执行一段时间后再切换到下一个线程。这种方式简单公平,但可能不是最高效的方式,因为它不考虑线程的优先级和实际需求。
  • 抢占式调度:这种调度方式会根据线程的优先级来决定哪个线程获得CPU的使用权。优先级高的线程会得到更多的执行机会。如果多个线程的优先级相同,通常会随机选择一个线程来执行。Java使用的就是抢占式调度,这种方式能够更好地满足高优先级任务的需求。
方法名说明
final int getPriority()返回此线程的优先级
final void setPriority(int newPriority)更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
        return "线程执行完毕了";
    }
}

 

public class Demo {
    public static void main(String[] args) {
        //优先级: 1 - 10 默认值:5
        MyCallable mc = new MyCallable();

        FutureTask<String> ft = new FutureTask<>(mc);

        Thread t1 = new Thread(ft);
        t1.setName("飞机");
        t1.setPriority(10);
        //System.out.println(t1.getPriority());//5
        t1.start();

        MyCallable mc2 = new MyCallable();

        FutureTask<String> ft2 = new FutureTask<>(mc2);

        Thread t2 = new Thread(ft2);
        t2.setName("坦克");
        t2.setPriority(1);
        //System.out.println(t2.getPriority());//5
        t2.start();
    }

守护线程

相关方法代码演示

方法名说明
void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
public class MyThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
public class MyThread2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        t1.setName("女神");
        t2.setName("备胎");

        //把第二个线程设置为守护线程
        //当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
        t2.setDaemon(true);

        t1.start();
        t2.start();
    }
}

线程的生命周期

线程同步

卖票

案例需求

电影院卖100张票,

而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

实现步骤

public class SellTicket extends Thread {
    static int ticket = 0;

    @Override
    public void run() {

        while (true) {
            if (ticket < 100) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket++;
                System.out.println(getName() + "正在卖" + ticket + "票");
            } else {
                break;
            }

        }
    }
}
public class ThreadDemo {                       
    public static void main(String[] args) {
        /*
    某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
     */
        SellTicket s1 = new SellTicket();
        SellTicket s2 = new SellTicket();
        SellTicket s3 = new SellTicket();


        s1.setName("窗口一");
        s2.setName("窗口二");
        s3.setName("窗口三");

        s1.start();
        s2.start();
        s3.start();
    }

}

卖票案例的问题

  • 卖票出现了问题

    • 相同的票出现了多次

    • 出现了多的票

  • 问题产生原因

    线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题

同步代码块解决数据安全问题

  • 安全问题出现的条件

    • 是多线程环境

    • 有共享数据

    • 有多条语句操作共享数据

  • 如何解决多线程安全问题呢?

    • 基本思想:让程序没有安全问题的环境

  • 怎么实现呢?

    • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可

    • Java提供了同步代码块的方式来解决

同步代码块格式:

synchronized(任意对象) { 
	多条语句操作共享数据的代码 
}

synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

同步的好处和弊端

  • 好处:解决了多线程的数据安全问题

  • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

代码演示

public class SellTicket extends Thread {
    static int ticket = 0;
    //锁对象,一定是唯一的
   

    @Override
    public void run() {

       synchronized (SellTicket.class){
           while (true) {
               if (ticket < 100) {
                   try {
                       Thread.sleep(50);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   ticket++;
                   System.out.println(getName() + "正在卖" + ticket + "票");
               } else {
                   break;
               }

           }
       }
    }
}

同步方法解决数据安全问题

同步方法的格式

同步方法:就是把synchronized关键字加到方法上

修饰符 static synchronized 返回值类型 方法名(方法参数) { 
	方法体;
}

同步静态方法的锁对象是什么呢?

类名.class

非静态:this

同步静态方法的锁对象是当前类的Class对象

在Java中,当使用synchronized关键字修饰一个静态方法时,这意味着这个方法被同步化,并且所有线程在访问这个方法时必须先获得锁。对于静态同步方法,这个锁就是当前类的Class对象。这与其他非静态同步方法使用的锁不同,后者使用的是实例对象作为锁。

具体来说,当一个线程尝试访问一个类的静态同步方法时,它会尝试获取该类对应的Class对象的锁。如果该锁已被其他线程持有,则当前线程必须等待直到锁被释放。这种机制确保了同一时刻只有一个线程能够执行同一个类的任何静态同步方法。

此外,静态同步方法与非静态同步方法之间不会发生竞态条件,因为它们使用的是不同的锁对象。静态同步方法使用的是类对象本身的锁,而非静态同步方法使用的是实例对象的锁。

了解同步静态方法的锁对象对于编写多线程程序和理解Java内存模型中的线程安全和并发控制非常重要。

Lock锁

Lock锁是Java并发编程中的一种同步机制,它提供了比synchronized关键字更加灵活的锁操作

首先,Lock锁的优势在于它能够提供更广泛的锁操作。与synchronized相比,Lock锁可以实现更细粒度的锁控制,例如可重入锁、公平锁等。具体来说:

  • 可重入锁:允许同一个线程多次获得锁,而不会导致自己被阻塞。
  • 公平锁:保证等待时间最长的线程能够先获得锁,避免线程饥饿现象。
  • 读写锁:允许多个读线程同时访问,但在写线程访问时会独占锁。

此外,Lock锁的使用通常需要手动释放,这要求开发者在finally块中释放锁以确保资源的正确释放。而synchronized则不需要手动释放。

总的来说,Lock锁提供了更多的功能和灵活性,但也带来了更高的复杂性。在使用Lock锁时,需要注意正确管理锁的获取和释放,以避免死锁或资源泄露。

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

ReentrantLock构造方法

方法名说明
ReentrantLock()创建一个ReentrantLock的实例

加锁解锁方法

方法名说明
void lock()获得锁
void unlock()释放锁

代码演示

  public class Ticket implements Runnable {
      //票的数量
      private int ticket = 100;
      private Object obj = new Object();
      private ReentrantLock lock = new ReentrantLock();

      @Override
      public void run() {
          while (true) {
              //synchronized (obj){//多个线程必须使用同一把锁.
              try {
                  lock.lock();
                  if (ticket <= 0) {
                      //卖完了
                      break;
                  } else {
                      Thread.sleep(100);
                      ticket--;
                      System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                  }
              } catch (InterruptedException e) {
                  e.printStackTrace();
              } finally {
                  lock.unlock();
              }
              // }
          }
      }
  }

死锁

死锁是指两个或多个执行单元(例如进程、线程或事务)在等待彼此持有的资源时,导致它们都无法 progress。产生死锁的情况通常涉及以下四个方面:

  1. 互斥条件:指资源至少由一个执行单元持有,且在释放之前其他执行单元无法使用。
  2. 占有和等待条件:指执行单元已经持有至少一个资源,但又提出了新的资源请求,被阻塞的执行单元仍然保持着它的资源。
  3. 不剥夺条件:指一个执行单元获得的资源在未使用完之前不能被强行剥夺,即使该资源有可能满足其他等待资源的执行单元的需求。
  4. 循环等待条件:存在一种环形链,每个执行单元都在等待下一个执行单元所占有的资源。

在实际开发中,为了避免死锁,可以采取一些策略,如尽量减少事务的大小和持续时间,避免长时间占用锁资源,以及尽量保持事务之间访问资源的一致性和顺序,避免交叉锁定。

下面是可以产生死锁的一段代码:~

public class DeadlockDemo {
    private static Object lock1 = new Object();
    private static Object lock2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread 1: Holding lock 1...");
                try {
                    Thread.sleep(100); // 模拟耗时操作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 1: Waiting for lock 2...");
                synchronized (lock2) {
                    System.out.println("Thread 1: Holding lock 1 & 2...");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread 2: Holding lock 2...");
                try {
                    Thread.sleep(100); // 模拟耗时操作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 2: Waiting for lock 1...");
                synchronized (lock1) {
                    System.out.println("Thread 2: Holding lock 1 & 2...");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

生产者消费者

概述

生产者消费者模式是一种经典的多线程协作模型,它涉及两类线程:生产者和消费者。

生产者:负责生产数据或对象,并将它们放入一个共享的队列中。生产者不直接与消费者交互,而是将产品放入队列后继续生产,这样可以保证生产者的效率不会因为等待消费者而降低。 消费者:负责从队列中取出数据或对象并进行消费。消费者不需要等待生产者生产,只需检查队列中是否有产品可消费。这样,消费者可以保持持续的工作状态,提高整体的处理效率。

此外,在实现生产者消费者模式时,通常需要考虑以下几个关键点:

  • 共享队列:生产者和消费者通过这个队列进行间接通信。生产者将产品放入队列,消费者从队列中取出产品。
  • 同步机制:需要确保当队列满时,生产者停止生产;当队列空时,消费者停止消费。这通常通过使用信号量、锁或其他同步机制来实现。
  • 线程通信:在某些情况下,生产者生产了新的产品后,需要通知消费者来消费。这种通信可以通过条件变量或其他同步工具来实现。

总的来说,生产者消费者模式有效地解决了生产者和消费者之间的速度匹配问题,允许两者以不同的速度运行,同时确保数据的安全传输和处理。

方法名说明
void wait()导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void notify()唤醒正在等待对象监视器的单个线程
void notifyAll()唤醒正在等待对象监视器的所有线程

为方便理解采用以下示例

来源://https://www.bilibili.com/video/BV17F411T7Ao/

桌子

public class Desk {
    /*
    作用:控制生产者和消费者的执行
     */

    //是否有面条 0:没有 1:有面条
    public static int foodFlag = 0;
    
    //总个数
    public static int count = 10;

    //锁对象
    public static Object lock = new Object();

}

生产者

public class Cook extends Thread{
    @Override
    public void run() {

        while(true){
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else {
                    //判断桌子上是否有食物
                    if(Desk.foodFlag == 1){
                        //如果有,就等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else {
                        //如果没有,就制作食物
                        System.out.println("厨师做了一碗面条");
                        //修改桌子上的食物状态
                        Desk.foodFlag = 1;
                        //叫醒等待的消费者开吃
                        Desk.lock.notifyAll();

                    }

                }
            }
        }

    }
}

消费者

public class Foodie extends Thread{
    @Override
    public void run() {
        while(true){
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else {
                    if(Desk.foodFlag == 0){
                        //用锁的对象调用wait()
                        try {
                            //让当前线程跟锁进行绑定
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else {
                        Desk.count--;
                        //如果有,就开吃
                        System.out.println("吃货正在吃面条,还能再吃"+Desk.count+"碗!!!");
                        //吃完之后,唤醒厨师继续做
                        Desk.lock.notify();
                        Desk.foodFlag = 0;

                    }
                }
            }
        }
    }
}

二:阻塞队列完成等待唤醒机制

生产者

public class Cook extends Thread{
    ArrayBlockingQueue<String> queue;

    public Cook(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while(true){
            //不断的把面条放在阻塞队列当中
            try {
                queue.put("面条");
                System.out.println("厨师放了一碗面条");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
public class Foodie extends Thread{
    ArrayBlockingQueue<String> queue;

    public Foodie(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while(true){
            //不断从阻塞队列中获取面条
            try {
                String food = queue.take();
                System.out.println(food);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        /*
        利用阻塞队列完成生产者消费者(等待唤醒机制)
        //细节:生产者消费者必须使用同一个阻塞队列
         */

        //1.创建阻塞队列的对象
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);

        //创建线程的对象,并把阻塞队列传递过去
        Cook c = new Cook(queue);
        Foodie f = new Foodie(queue);

        c.start();
        f.start();

    }
}

线程状态

线程池

线程池是一种用于管理线程的资源池,它可以显著提高多线程程序的性能和可靠性

线程池的实现基于生产者-消费者模式,任务提交方作为生产者将任务放入线程池,而线程池中的工作线程作为消费者来执行这些任务。以下是线程池的一些关键特点:

  1. 线程复用:线程池通过重用现有线程来执行新任务,减少了频繁创建和销毁线程所带来的开销。
  2. 任务管理:线程池内部通常有一个工作队列,用于存储等待执行的任务,这样可以实现任务的有序执行。
  3. 性能提升:通过减少线程创建的开销和优化线程调度,线程池能够提高程序的响应速度和吞吐量。
  4. 资源控制:线程池可以限制同时运行的线程数量,防止系统过载,同时也可以减少上下文切换的开销。
  5. 编程简化:使用线程池可以简化多线程编程,开发者只需关注任务的实现,而不必处理线程的创建和管理细节。
  6. 异步执行:线程池支持异步执行任务,可以在不阻塞主线程的情况下执行耗时操作,提升用户体验。
  7. 灵活性:现代编程语言如C++提供了丰富的API和库来支持线程池的实现,使得开发者可以根据需要定制线程池的行为。
  8. 并发控制:线程池可以帮助开发者更好地控制并发级别,避免资源竞争和死锁等问题。

综上所述,线程池是一种高效的线程管理机制,它通过池化技术优化了线程的创建、管理和调度,为并发编程提供了强大支持。在实际应用中,合理地使用线程池可以带来显著的性能提升和更好的资源利用率。

线程池代码实现

  1. 创建线程池

  2. 提交任务

  3. 所有的任务全部执行完毕,关闭线程池

线程池-Executors默认线程池

我们可以使用Executors中所提供的静态方法来创建线程池

static ExecutorService newCachedThreadPool() 创建一个默认的线程池 ​ static newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+ i);
        }
    }
}
public class test8 {
    public static void main(String[] args) {
        //获取线程池对象
        ExecutorService pool1 = Executors.newCachedThreadPool();
        //ExecutorService pool1 = Executors.newCachedThreadPool(3);
        //提交任务
        pool1.submit(new MyRunnable());
        //销毁线程池
       /* pool1.shutdown();*/
    }
}

线程池-参数详解

Java中的线程池通过ThreadPoolExecutor类实现,它提供了灵活的参数配置来满足不同场景的需求。以下是该类构造方法中的七个主要参数及其作用:

  1. corePoolSize:这是线程池的核心线程数量,即线程池中始终保持的最小线程数。即使这些线程处于空闲状态,也不会被销毁,除非设置了allowCoreThreadTimeOut属性。
  2. maximumPoolSize:这是线程池允许创建的最大线程数量。当任务数量超过核心线程数时,线程池会尝试创建新的线程来处理任务,但总数不会超过这个参数指定的值。
  3. keepAliveTime:当线程池中的线程数量超过核心线程数时,多余的空闲线程在被销毁之前可以存活的时间。这个参数可以帮助控制资源使用,避免长时间持有不必要的线程资源。
  4. unit:与keepAliveTime参数配合使用,用于指定keepAliveTime的时间单位,如TimeUnit.SECONDS表示秒。
  5. workQueue:用于存放待执行任务的阻塞队列。不同的队列实现有不同的特性,如ArrayBlockingQueueLinkedBlockingQueue等,选择合适的队列对线程池的性能有重要影响。
  6. threadFactory:用于创建新线程的工厂。可以通过实现ThreadFactory接口来自定义线程的创建过程,如设置线程名称、守护状态等。
  7. handler:当线程池和队列都满了,无法接受新任务时的拒绝策略。常见的策略有AbortPolicy(默认,抛出异常)、CallerRunsPolicy(调用者运行)和DiscardOldestPolicy(丢弃最旧任务)等。

综上所述,合理配置这些参数对于提高线程池的效率和稳定性至关重要。例如,根据任务的特性和系统的资源状况来调整核心线程数和最大线程数,以及选择适合的拒绝策略,可以在保证性能的同时避免资源的过度消耗

为方便理解看下图 图源://​​黑马程序员Java零基础视频教程_上部(Java入门,含斯坦福大学练习题+力扣算法题和大厂java面试题)_哔哩哔哩_bilibili

 

public class test8 {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,//核心线程数量,不能小于0
                6,//最大线程数,不能小于0,最大数量 >= 核心线程数量
                60, //空闲线程最大存活时间
                TimeUnit.SECONDS,//时间单位
                new ArrayBlockingQueue<>(3),//任务队列Executors.defaultThreadFactory(),
                Executors.defaultThreadFactory(),//创建线程工厂
                new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
        );
        
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/417731.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Day07:基础入门-抓包技术全局协议封包监听网卡模式APP小程序PC应用

目录 非HTTP/HTTPS协议抓包工具 WireShark 科来网络分析系统 WPE封包 思维导图 章节知识点&#xff1a; 应用架构&#xff1a;Web/APP/云应用/三方服务/负载均衡等 安全产品&#xff1a;CDN/WAF/IDS/IPS/蜜罐/防火墙/杀毒等 渗透命令&#xff1a;文件上传下载/端口服务/Sh…

Vue3使用JSX/TSX

文章目录 1. 什么是 JSX & TSX?JSX&#xff08;JavaScript XML&#xff09;TSX&#xff08;TypeScript XML&#xff09; 2.Vue3 中使用 TSX基本渲染 & 响应式 & 事件 3.JSX 和 template 哪个好呢&#xff1f;总结 1. 什么是 JSX & TSX? 提示&#xff1a;JSX…

springboot231基于SpringBoot+Vue的乡政府管理系统

乡政府管理系统设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装乡政府管理系统软件来发挥其高效…

LeetCode:2867. 统计树中的合法路径数目(筛质数+ DFS Java)

目录 2867. 统计树中的合法路径数目 题目描述&#xff1a; 实现代码与思路&#xff1a; 筛质数 DFS 原理思路&#xff1a; 2867. 统计树中的合法路径数目 题目描述&#xff1a; 给你一棵 n 个节点的无向树&#xff0c;节点编号为 1 到 n 。给你一个整数 n 和一个长度为 …

市场复盘总结 20240229

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 二进三&#xff1a; 进级率中 60% 最常用…

LeetCode 刷题 [C++] 第102题.二叉树的层序遍历

题目描述 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 题目分析 题目中要求层序遍历二叉树&#xff0c;即二叉树的广度优先搜索(BFS)。BFS一般使用队列的先入先出特性实现&#…

弹窗内容由后端返回,如何让点击按钮的事件交由前端控制?

一、场景 背景&#xff1a;因为系统里经常有新活动或者公告需要通知所有用户&#xff0c;希望前端维护的这个弹窗里的内容可以由后端接口返回。这样就不需要每次上新活动的时候&#xff0c;前端项目都发版了。因此&#xff0c;前端维护了这个弹窗和它的关闭事件&#xff0c;至…

SDWAN异地组网难在哪?怎么解决?

SD-WAN作为一种先进的网络技术&#xff0c;为企业提供了更加灵活和高效的网络连接方案。然而&#xff0c;在异地组网的过程中&#xff0c;SD-WAN也面临一些挑战。本文将探讨SD-WAN异地组网所面临的难题&#xff0c;并提供相应的解决方案。 挑战一&#xff1a;网络延迟和不稳定性…

fork创建子进程及僵尸进程的产生及规避

本篇文章的学习与总结来源于 https://www.bilibili.com/cheese/play/ep182659?csourcecommon_hp_history_null&t3&spm_id_from333.1007.top_right_bar_window_history.content.click 通常使用fork()函数产生新的子进程&#xff0c;需要包含两个头文件<sys/types.h…

在Windows中安装PyTorch

文章目录 1. 创建虚拟环境2. 检查显卡版本和CUDA3. 下载链接4. 下载5. 等待6. 检测 1. 创建虚拟环境 具体查看我之前写的 《在Windows中利用Python的venv和virtualenv创建虚拟环境》 2. 检查显卡版本和CUDA 这种情况是需要电脑上有单独的英伟达的显卡、或者英伟达的显卡和集显…

Rocky Linux 运维工具 mv

一、mv的简介 ​​mv​是Linux系统中的命令&#xff0c;用于移动文件或重命名文件。它可以在同一文件系统内将文件从一个目录移动到另一个目录&#xff0c;也可以修改文件的名称。 二、mv的参数说明 1、 三、mv的实战示例 1、重命名 ###查看目录/root/下的文件列表 [rootloc…

Aws Ec2服务器设置密码登录

通过密钥&#xff0c;ssh登录到服务器 切换到root sudo -i开始设置root的新密码 passwd root输入并确认新密码即可 5.修改ssh配置文件 vim /etc/ssh/sshd_config6.重启sshd配置 systemctl restart sshd

Linux:Makefile的相关知识

背景&#xff1a; 一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;makefile定义了一系列的 规则来指定&#xff0c;哪些文件需要先编译&#xff0c;哪些文件需要后编译&#xff0c;哪些文件需要重新编译&#xff0c;甚至于进行更复…

周鸿祎首堂免费课与千万网友分享“AGI趋势”

“我讲课不割韭菜&#xff0c;宗旨是免费、分享、科普、交流。AI时代技术发展迅速&#xff0c;AI知识普及尤为重要。”2月29日&#xff0c;360公司创始人周鸿祎免费课正式开启&#xff0c;全网多平台直播了AI系列第一讲“预见AGI”&#xff0c;千万网友观看。免费课上&#xff…

算法 -【从前序与中序遍历序列构造二叉树】

从前序与中序遍历序列构造二叉树 题目示例1示例2 分析代码 题目 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例1 输入: preorder [3,9,20,1…

【三维重建】【SLAM】SplaTAM:基于3D高斯的密集RGB-D SLAM

题目&#xff1a;SplaTAM: Splat, Track & Map 3D Gaussians for Dense RGB-D SLAM 地址&#xff1a;spla-tam.github.io 机构&#xff1a;CMU&#xff08;卡内基梅隆大学&#xff09;、MIT&#xff08;美国麻省理工&#xff09; 总结&#xff1a;SplaTAM&#xff0c;一个新…

【Leetcode每日一刷】动态规划算法: 62. 不同路径、63. 不同路径 II

博主简介&#xff1a;努力学习和进步中的的22级计科生博主主页&#xff1a; Yaoyao2024每日一句: “ 路虽远&#xff0c;行则将至。事虽难&#xff0c;做则可成。” 前言 前言&#xff1a;动规五部曲 以下是《代码随想录》作者总结的动规五部曲 确定dp数组&#xff08;dp tab…

[LeetCode]143.重排链表

143. 重排链表 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/reorder-list/description/ 题目 示例 解题思路 寻找链表中点 链表逆序 合并链表 注意到目标链表即为将原链表的左半端和反转后的右半端合并后的结果。 这样我们的任务即可划分为三步&a…

Git命令操作

什么是Git&#xff1f; Git是⼀个免费的&#xff0c;开源的分布式版本控制软件系统 git区域 存储区域&#xff1a;Git软件⽤于存储资源得区域。⼀般指得就是.git⽂件夹 ⼯作区域&#xff1a;Git软件对外提供资源得区域&#xff0c;此区域可⼈⼯对资源进⾏处理。 暂存区&am…

安卓开发1- android stdio环境搭建

安卓开发1-android stdio环境搭建 Jdk环境搭建 1. 准备Jdk,这边已经准备好了jdk1.8.0,该文件直接使用即可 2. 系统变量添加 %JAVA_HOME%\bin JAVA_HOME 3. 系统变量&#xff0c;Path路径添加 4. 添加完成后&#xff0c;输入命令javac / java -version&#xff0c;验证环…