JavaEE 【知识改变命运】05 多线程(4)

news2024/12/12 11:27:36

文章目录

  • 单例模式
    • 什么是单例模式
    • 饿汉模式
    • 懒汉模式
    • 多线程- 懒汉模式
      • 分析多线程问题
      • 第一种添加sychronized的方式
      • 第二种添加sychronized的方式
      • 改进第二种添加sychronized的方式(DCL检查锁)
  • 阻塞队列
    • 什么是阻塞队列
    • 什么是消费生产者模型
    • 标准库中的阻塞队列
    • 消息队列应用的场景
    • 自己模拟实现阻塞队列
  • 定时器
    • 标准库中的定时器
    • 实现定时器
  • 工厂模式
  • 线程池
    • 线程池的一些问题
    • 实现一个线程池
    • 创建系统自带的线程池
  • wait和sleep的区别

单例模式

什么是单例模式

  • 单例模式能保证某个类在程序中只存在唯⼀⼀份实例, ⽽不会创建出多个实例
  • 单例模式实现方式很多,最常用饿汉模式和懒汉模式实现

饿汉模式

  • 创建过程:
    – 1. 定义一个static修饰的变量,就可以包子这个变量全局唯一
    – 2.构造方法私有化,防止变量被修改
    – 3.提供一个获取变量的get静态方法,通过类名的方式去调用
public class Singleton {
    //懒汉模式
    //创建一个私有静态属性,并且把对象new出来
    private static Singleton instance =new Singleton();
    //私有化构造器
    private Singleton() {
    }
    //提供一个公共的静态方法,返回单例对象
    public static Singleton getInstance() {
        return instance;
    }

    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2); // true
    }
}

  • 把这种类加载时候就完成对象的初始化的创建方式,就叫”饿汉模式“
  • 这种模式存在的问题是,可能对象创建了但是没有使用,从而导致资源浪费。

懒汉模式

public class SingLetonLazy {
    //创建一个对象不去new对象
    private static SingLetonLazy instance;
    //私有化构造器
    private SingLetonLazy() {
    }
    //提供一个公共的静态方法,返回单例对象
    public static SingLetonLazy getInstance() {
        if(instance==null) {
            instance=new SingLetonLazy();
        }
        return instance;
    }

    public static void main(String[] args) {
        SingLetonLazy s1 = SingLetonLazy.getInstance();
        SingLetonLazy s2 = SingLetonLazy.getInstance();
        System.out.println(s1 == s2); // true
    }
}

  • 懒汉模式创建对象,在要获得单例对象的时候,创建,避免了资源的浪费但是存在多线程安全问题。

多线程- 懒汉模式

public class SingLetonLazy {
    private static SingLetonLazy instance;
    //私有化构造器
    private SingLetonLazy() {
    }
    //提供一个公共的静态方法,返回单例对象
    public static SingLetonLazy getInstance() {
        if(instance==null) {
            instance=new SingLetonLazy();
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Thread t1 =new Thread(()->{
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                SingLetonLazy s1 = SingLetonLazy.getInstance();
                System.out.println(s1);
            });
            t1.start();
        }
    }
}

在这里插入图片描述
出现了多线程问题。

分析多线程问题

在这里插入图片描述

第一种添加sychronized的方式

public static SingLetonLazy getInstance() {
      if(instance==null) {
       synchronized (SingLetonLazy.class){
               instance=new SingLetonLazy();
           }
       }
       return instance;
 }

在这里插入图片描述

  • 这种写法不能保证多线程安全

第二种添加sychronized的方式

public static SingLetonLazy getInstance() {
        synchronized (SingLetonLazy.class){
            if(instance==null) {
                instance=new SingLetonLazy();
            }
        }
        return instance;
    }

在这里插入图片描述

  • 这种写法似乎可以保证多线程安全,但是还是存在一个问题,当一个线程进行这个方法,如果没有初始化,则获取锁进行初始化操作,此时单例对象被第一个线程创建完成,后面的线程以后永远不会在执行new对象的操作,synchronized就没必要添加了,第二次线程开始这个加锁解锁都是无效的操作,lock和unlock对应的锁指令是互斥锁,比较消耗系统资源。
  • 添加锁本质就会消耗很多资源

改进第二种添加sychronized的方式(DCL检查锁)

 private volatile static SingLetonLazy instance;
 //给共享变量加上volatile

在这里插入图片描述

public static SingLetonLazy getInstance() {
		//第一次判断是否加锁
        if(instance==null) {
            synchronized (SingLetonLazy.class) {
            	判断是否创建了一个对象
                if (instance == null) {
                    instance = new SingLetonLazy();
                }
            }

        }
        return instance;
    }

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

阻塞队列

什么是阻塞队列

  1. 阻塞队列本质还是队列,遵循”先进先出“的原则
  2. 阻塞队列是一种线程安全的数据结构,有以下特征
    – 当队列满的时候,继续入队就会发生阻塞等待,直到队列中有其他线程取出元素后,队列有空位才会再次入队
    – 当队列空的时候,继续出队就会放生阻塞等待,知道队列中有其他线程插入元素时候,队列有元素才会再次出队
  3. 阻塞队列适用于一种典型场景‘消费生产者模型’

什么是消费生产者模型

  1. 生产者消费者模式就是通过一个容器解决消费者和生产者的强耦合问题。
  2. 生产者和消费者不会直接影响,生产者生产的资源直接放入容器(阻塞队列)中,消费者消费的资源,直接从容器(阻塞队列)中拿。从而保证生产者不会生产资源等待消费者消费,消费者也不会等待生产者生产资源。
  3. 阻塞队列相当于一个缓冲区,平衡生产者和消费者的处理能力
    – 比如双11时候,会涌入大量的支付订单,这时候如果服务器直接处理这些订单,可能就会把服务器挤爆,这时候中间设置一个阻塞队列,把产生的大小支付订单扔进阻塞队列里面,然后服务器根据自己的处理能力,从队列里面取出要处理的订单,从而达到削峰的效果,防止服务器被挤爆。
  4. 阻塞队列也能使生产者和消费者之间 解耦
    – 过年期间大家都会包饺子,擀饺子皮相当于生产者,包饺子相当于消费者,中间放个案板,所有的饺子皮都放在案板上,包饺子皮的人直接从案板上取,擀饺子皮的可能是妈妈可能是爸爸可能是我,无论是谁擀饺子皮消费者都不关心,因为都是从案板上取的饺子皮。

标准库中的阻塞队列

  1. 在 Java 标准库中内置了阻塞队列. 如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可.
    – BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.
    – put 方法用于阻塞式的入队列, take 用于阻塞式的出队列.
    – BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.
  2. 创建一个BlockingQueue
    在这里插入图片描述
    在这里插入图片描述
    – 其中capacity是这个队列的大小。

在这里插入图片描述
– 这里设置一个三个大小的阻塞队列,当第四个元素入队时候就会发生阻塞等待
在这里插入图片描述
– 这里取出三个元素后,队列为空,队列阻塞等待
在这里插入图片描述
在这里插入图片描述
– put和take都会抛出一个InterrupteException异常

  1. 其他补充常问的方法
    在这里插入图片描述
    – add()
    在这里插入图片描述
    – offer()
    在这里插入图片描述
    – remove()
    在这里插入图片描述
    – poll
    在这里插入图片描述

消息队列应用的场景

  1. 解耦
    – 高内聚,低耦合:业务强相关的代码组织在一起,不相关的单独定义便于以后的维护,以为要把重复的代码尽量抽象出来,封装成一个公共方法,在需要的地方直接调用这个方法即可
    – 生产消息的应用程序把消息写进消息队列(生产者),使用消息的应用程序从消息队列里面取出消息(消费者)
    在这里插入图片描述
    在这个模型中,服务器A要时刻感应到服务器B,在调用的过程中双方都要知道对方需要调用的参数和调用方式
    ,在ABC整个调用的链路中秒如果其中一个出现了问题,就会影响整个业务执行
    在这里插入图片描述

  2. 削峰填谷(流量)
    – 针对流量暴增的时候使用消息队列来进行缓冲
    在这里插入图片描述
    在这里插入图片描述
    – 实例:
    在这里插入图片描述

  3. 异步操作
    周末:我和我女朋友取买包子

  • 同步操作:她一直等我买包子回来,开始,中间这个过程啥也不干,同步发出请求后,必须要等待响应才能- 进行下一步操作
  • 异步操作:她让我去之后,在家做点别的事情,比如,做点小菜,熬点稀饭,异步操作,发出请求之后,不需要等待响应,而做其他的事情,等待响应主动通知自己

自己模拟实现阻塞队列

public class MyBlockingDeque {
    int [] arr;
    volatile int head=0;
    volatile int tail=0;
    volatile int size=0;
    MyBlockingDeque(int capacity){
        if(capacity<=0) {
            throw new RuntimeException("capacity must be positive");
        }
        arr = new int[capacity];
    }
    public void put(int val) throws InterruptedException {
        while(size>=arr.length) {
            synchronized (this){
                this.wait();
            }
        }
        arr[tail]=val;
        tail++;
        if(tail>=arr.length) {
            tail=0;
        }
        size++;
        synchronized (this){
            this.notifyAll();
        }

    }
    public synchronized int take() throws InterruptedException {
        while(size==0) {
                this.wait();
        }
        int val =arr[head];
        head++;
        if(head>=arr.length) {
            head=0;
        }
        size--;
        this.notifyAll();
        return val;
    }
}
class Main{
    public static void main(String[] args) throws InterruptedException {
        MyBlockingDeque myBlockingDeque = new MyBlockingDeque(10);
        int i=0;
        new Thread(()->{
            while (true){
                try {
                    sleep(1000);
                    int val = myBlockingDeque.take();
                    System.out.println(Thread.currentThread().getName()+"取出成功"+val);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        while (true){
            myBlockingDeque.put(i);
            System.out.println(Thread.currentThread().getName()+"添加成功"+i);
            i++;
        }

    }
}

在这里插入图片描述

  • put时候
    在这里插入图片描述
  • take时候
    在这里插入图片描述
  • 我们上锁可以锁代码块也可以方法
    在这里插入图片描述
  • if改为while的原因是防止大量现场
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

定时器

标准库中的定时器

  • 标准库中定义一个TImer类。Timer类的核心方法为schedule
  • schedule包含两个参数,第一个参数指定要执行的代码任务,第二个参数指定多场实际之后执行。
import java.util.Timer;
import java.util.TimerTask;

public class Demo_801 {
    public static void main(String[] args) {
        // 使用jdk中提供的类,创建一个定器
        Timer timer = new Timer();
        //向定时器添加任务
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("Hello World!");
            }
        },1000);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务1");
            }
        },1500);timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务2");
            }
        },2000);timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务3");
            }
        },2500);

    }
}

ctrl+p查看方法的参数列表
在这里插入图片描述
定义自己的任务
在这里插入图片描述
延迟多久执行的任务
在这里插入图片描述
任务具体执行的时间
在这里插入图片描述

实现定时器

  1. 设计思路
  • 用一个类描述任务和执行任务的时间
    – 具体任务逻辑用Runable表示,执行时间可以用一个long型delay表示
  • 组织任务和时间对应的对象
    – 可以考虑用一个阻塞队列,我们选择用PriorityBlockingQueue(),保证扫描任务时候,延时最少的任务先执行

- 提供一个方法,
在这里插入图片描述

  • 提供一个方法,提交任务
    在这里插入图片描述

  • 要有一个线程执行任务
    – 在哪里定义扫描线程?
    –在构造方法里面直接定义线程
    – 1.取出队首元素,2.判断一下任务到执行的时间没有,3,如果到了就执行,4.没有就放回队列
    在这里插入图片描述

  1. 代码实现
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;


public class MyTimer {
    //用一个阻塞队列来组织任务
    private BlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    private Object lock = new Object();

    public MyTimer() {
        //创建线程
        Thread thread =new Thread(()->{
            while(true){
                try {
                    //从队列中取出任务
                    MyTask task=this.queue.take();
                    //判断有没有到执行的时间
                    long currentTime=System.currentTimeMillis();
                    if(currentTime>=task.getTime()){
                        //时间到了执行
                        task.getRunnable().run();
                    } else{
                        //时间没到,将任务放回队列中
                        this.queue.put(task);
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        thread.start();
    }

    /**
     * 添加定时任务
     * @param runnable 任务
     * @param delay 延时
     * @throws InterruptedException
     */
    public void schedule(Runnable runnable,long delay) throws InterruptedException {
        //根据传的参数构造一个MyTask对象
        MyTask task=new MyTask(runnable,delay);
        //将这个MyTask对象阻塞放入队列中
        queue.put(task);
    }
}

//MyTask类,用于封装任务和执行时间
class MyTask implements Comparable<MyTask>{
    //任务
    private Runnable runnable;
    //执行时间
    private long time;

    public MyTask(Runnable runnable, long delay) {
        //增强代码健壮性
        if(runnable==null){
            throw new IllegalArgumentException("任务不能为空");
        }
        if(delay<0) {
            throw new IllegalArgumentException("延迟时间不能为负数");
        }
        this.runnable = runnable;
        //计算出任务的执行时间
        this.time = delay+System.currentTimeMillis();
    }

    public Runnable getRunnable() {
        return runnable;
    }
    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTask o) {
        if(this.getTime()<o.getTime()){
            return -1;
        } else if(this.getTime()==o.getTime()){
            return 0;
        }else {
            return 1;
        }
        //万一时间超过了long的范围溢出,怎么办?用上面的比较比较好
        //return (int)(this.getTime()-o.getTime());
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable(){
            @Override
            public void run() {
                System.out.println("任务1");
            }
        },1000);
        timer.schedule(new Runnable(){
            @Override
            public void run() {
                System.out.println("任务2");
            }
        },500);
        timer.schedule(new Runnable(){
            @Override
            public void run() {
                System.out.println("任务3");
            }
        },2000);
        //timer.schedule(null,-100);
        //任务加强健壮性
    }
}

  • 注意事项:
    – 注意我们要实现Conparable接口指定排序规则
    在这里插入图片描述
    在这里插入图片描述

– 我们要添加校验,防止非法的输入
在这里插入图片描述

– 解决数据可能会溢出的问题,比如设置的时间
在这里插入图片描述

  1. 再次深度优化我们代码
    在这里插入图片描述
  • 以上代码我们存在“忙等”的情况
  • 优化后的代码
    在这里插入图片描述
    – 这里注意一下这个lambda表达式中的this引用的是他所在对象的实例。
  • 新的问题:当任务1在等待时候,这时候如果又put进来一个新的任务,这个等待的时间就有问题。再次优化
    在这里插入图片描述
    每添加新的任务都进行一次唤醒,保证执行的永远是最少延时的任务。
  • 从CPU调度的过程中可以会产生的执行顺序的问题,或当一个线程执行到一半的时间被掉调度走的现象。
    在这里插入图片描述
    这个线程造成的原因就是没有保证原子性。
  • 优化代码
    在这里插入图片描述

在这里插入图片描述

  • 再次观察一种极端情况
    在这里插入图片描述
    – 我们发现当我们把三个任务的延时时间设置为0的时候,结果只执行了任务1,我们进行调试
    在这里插入图片描述
    – 调试之后我们又发现是正常情况,但是运行时候不符合我们的预期结果,这时候我们不要慌,我们用jconsole工具去查看下扫描情况
    在这里插入图片描述
  • 我们发现在MyTimer。java22行出现了问题
  • 在这里插入图片描述

1.创建一个定时器
2.向定时器添加任务1
3.第一个任务被添加到阻塞队列中
4.扫描线程启动,处理第一个任务
5.扫描线程1循环,获得第二个任务时候,队列为空,开始等待,同时扫描线程获得锁
6.主线程向阻塞队列添加任务时候,等待扫描对象的对象,由于扫描线程无法释放锁对象,主线程也就获取不到锁对象,造成相互等待,造成死锁

  • 我们再次优化代码创造一个后台扫描线程,只做定时唤醒操作,定时1秒或者10ms,唤醒一次

在这里插入图片描述
-最终的代码

public class MyTimer {
    //用一个阻塞队列来组织任务
    private BlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    private Object lock = new Object();

    public MyTimer() {
        //创建线程
        Thread thread =new Thread(()->{
            while(true) {
                try {
                    synchronized (this) {
                    //从队列中取出任务
                        MyTask task = this.queue.take();
                        //判断有没有到执行的时间
                        long currentTime = System.currentTimeMillis();
                        if (currentTime >= task.getTime()) {
                            //时间到了执行
                            task.getRunnable().run();
                        } else {
                            //时间没到,将任务放回队列中
                            long waitTime = task.getTime() - currentTime;
                            this.queue.put(task);
                            this.wait(waitTime);
                        }
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        thread.start();
        //创建守护线程,定时唤醒一次
        Thread deamonThread=new Thread(()->{
            synchronized (this) {
                //唤醒一次
                this.notifyAll();
                //每隔100ms唤醒一次
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        //设置为守护线程
        deamonThread.setDaemon(true);
        deamonThread.start();
    }

    /**
     * 添加定时任务
     * @param runnable 任务
     * @param delay 延时
     * @throws InterruptedException
     */
    public void schedule(Runnable runnable,long delay) throws InterruptedException {
        //根据传的参数构造一个MyTask对象
        MyTask task=new MyTask(runnable,delay);
        //将这个MyTask对象阻塞放入队列中
        queue.put(task);
        System.out.println("任务添加成功");
    }
}

//MyTask类,用于封装任务和执行时间
class MyTask implements Comparable<MyTask>{
    //任务
    private Runnable runnable;
    //执行时间
    private long time;

    public MyTask(Runnable runnable, long delay) {
        //增强代码健壮性
        if(runnable==null){
            throw new IllegalArgumentException("任务不能为空");
        }
        if(delay<0) {
            throw new IllegalArgumentException("延迟时间不能为负数");
        }
        this.runnable = runnable;
        //计算出任务的执行时间
        this.time = delay+System.currentTimeMillis();
    }

    public Runnable getRunnable() {
        return runnable;
    }
    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTask o) {
        if(this.getTime()<o.getTime()){
            return -1;
        } else if(this.getTime()==o.getTime()){
            return 0;
        }else {
            return 1;
        }
        //万一时间超过了long的范围溢出,怎么办?用上面的比较比较好
        //return (int)(this.getTime()-o.getTime());
    }
public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyTimer timer = new MyTimer();
        timer.schedule(new Runnable(){
            @Override
            public void run() {
                System.out.println("任务1");
            }
        },0);
        timer.schedule(new Runnable(){
            @Override
            public void run() {
                System.out.println("任务2");
            }
        },0);
        timer.schedule(new Runnable(){
            @Override
            public void run() {
                System.out.println("任务3");
            }
        },0);
        //timer.schedule(null,-100);
        //任务加强健壮性
    }
}

工厂模式

  • 先看出现的问题在这里插入图片描述
    我们这里造成了重载参数的相同,但是我们就是要这样的构造方法我们怎么解决呢?

在这里插入图片描述
工厂方法模式。根据不同的业务需求定义不同的方法来获取对象。

线程池

线程池的一些问题

  • 什么是线程池
    1.线程池就是一次创建多个线程,把这些线程放进一个池中,用的时候从池中取出,用完就还回去
  • 为什么要用线程池
    我们首先要明白,线程的创建和销毁都会消耗大量的资源,线程池中的线程当有任务的时候,就会执行任务,没有任务的时候就阻塞等待,并不销毁线程,线程池最⼤的好处就是减少每次启动、销毁线程的损耗。
  • 为什么使用线程池可以提升效率
    少量创建,少量销毁,创建一个线程要分为内核态和用户态,用户态相当于jvm层面,内核太相当于操作系统层面,当我们在jvm层面创建一个线程,就要在操作系统层面创建对应指令,就会消耗大量资源,消耗线程也如此,所以线程池减少了频繁的销毁和创建,用的时候就直接在线程池里面用已经创建多的,从而提升效率。
  • 怎么用?
    – jdk给我们提供了一组针对不同场景的线程池实例
public static void main(String[] args) {
        //1.用来处理大量短时间的任务的线程池,如果池没有可用的线程将创建线程,如果线程空闲60秒将收回并移除缓存
        ExecutorService cachedThreadpool= Executors.newCachedThreadPool();
        //2.创建一个操作无界队列,线程池大小固定的线程池
        ExecutorService fixedThreadpool= Executors.newFixedThreadPool(5);//可以指定线程数量
        //3.创建一个操作无界队列,只有一个线程的线程池
        ExecutorService singleThreadExecutor= Executors.newSingleThreadExecutor();
        //4.创建一个单线程执行器,可以加时间给定时间后执行或者定期执行
        ScheduledExecutorService singleThreadScheduledExecutor= Executors.newSingleThreadScheduledExecutor();
        //5.创建一个指定大小的线程池,可以加时间给定时间后执行或者定期执行
        ScheduledExecutorService scheduledThreadpool= Executors.newScheduledThreadPool(5);
        //6.创建一个指定大小(不传参,为当前机器的cpu核数)的线程池,并行处理任务,不保证处理顺序
        Executors.newWorkStealingPool();
}
Runtime.getRuntime().availableProcessors()
获取系统的cpu核数

实现一个线程池

  • 先构思思路(先描述,再组织)
  1. 用Runable描述任务
  2. 组织管理任务可以使用一个队列,可以使用阻塞队列取实现
  3. 提供一个向队列的添加任务的方法
  4. 创建多个线程,扫描队列里面的任务,有任务时候执行,没有任务时候等待
public class MyExectorService {
    //定义阻塞队列阻止任务
    private BlockingQueue<Runnable> queue=new LinkedBlockingQueue(100);
    private  static Object lock=new Object();
    public MyExectorService(int threadNum){
        for (int i = 0; i < threadNum; i++){
            Thread thread=new Thread(()->{
                //不停扫描队列
                while (true) {
                    try {
                        synchronized (lock){
                            Runnable runable=  queue.take();
                            runable.run();
                        }
                        TimeUnit.MILLISECONDS.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }//take()方法会阻塞,直到队列中有任务
            });
            //启动线程
            thread.start();
        }
    }

    /**
     * 提交任务到线程池中
     * @param runnable 具体的任务
     * @throws InterruptedException
     */
    public void sumbit(Runnable runnable) throws InterruptedException {
        if(runnable==null){
            throw new IllegalArgumentException("任务不能为空");
        }
        //把任务加入队列中
        queue.put(runnable);
    }

}
class Test1{
    public static void main(String[] args) throws InterruptedException {
        MyExectorService myExectorService=new MyExectorService(3);
        AtomicInteger j= new AtomicInteger();
        for (int i = 0; i < 10; i++) {
            myExectorService.sumbit(() -> {
                System.out.println(Thread.currentThread().getName() + " " + j);
                j.getAndIncrement();
            });
            if(i%3==0){
                TimeUnit.SECONDS.sleep(1);
            }
        }
    }
}

创建系统自带的线程池

  • 前面jdk提供的线程池比较固定,也就是说我们不能自己定制,但是我们看底层代码时发现,这些线程池都是对ThreadPoolExecutor的封装
    在这里插入图片描述
  • 那我们可以根据ThreadPoolEecutor创建一个自定义线程池

在这里插入图片描述
用现实的两个例子去模拟线程工作的原理
周末去吃饭
在这里插入图片描述
银行办理业务
在这里插入图片描述

  • 线程池的拒绝策略详解
    在这里插入图片描述
    在这里插入图片描述
  • 我们注意一下,3和4是不会抛出异常的,1和2是会抛出异常的,放弃的任务永远都找不回来,所以指定拒绝策略的时候,要关注任务是不是必须要执行,如果必须要执行,就指定“返回调用者”,否则选1,3,4一个即可
public static void main(String[] args) {
        ThreadPoolExecutor threadPool=new ThreadPoolExecutor(
                2,5,10, TimeUnit.SECONDS
                ,new LinkedBlockingQueue<>(7)
                ,new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i < 100; i++) {
            int takeI=i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+" 执行任务 "+takeI);
                }
            });
        }
    }
  • 直接拒绝
    实际只执行几个后面的都没执行
    在这里插入图片描述
    在这里插入图片描述
  • 返回给调用者
    有一部分代码返回给调用者main执行了
  public static void main(String[] args) {
        ThreadPoolExecutor threadPool=new ThreadPoolExecutor(
                2,5,10, TimeUnit.SECONDS
                ,new LinkedBlockingQueue<>(7)
                ,new ThreadPoolExecutor.CallerRunsPolicy());
        for (int i = 0; i < 100; i++) {
            int takeI=i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+" 执行任务 "+takeI);
                }
            });
        }
    }

在这里插入图片描述

  • 放弃最早的任务
public static void main(String[] args) {
        ThreadPoolExecutor threadPool=new ThreadPoolExecutor(
                2,5,10, TimeUnit.SECONDS
                ,new LinkedBlockingQueue<>(7)
                ,new ThreadPoolExecutor.DiscardOldestPolicy());
        for (int i = 0; i < 100; i++) {
            int takeI=i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+" 执行任务 "+takeI);
                }
            });
        }
    }

在这里插入图片描述

  • 放弃最新的任务
public static void main(String[] args) {
        ThreadPoolExecutor threadPool=new ThreadPoolExecutor(
                2,5,10, TimeUnit.SECONDS
                ,new LinkedBlockingQueue<>(7)
                ,new ThreadPoolExecutor.DiscardPolicy());
        for (int i = 0; i < 100; i++) {
            int takeI=i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+" 执行任务 "+takeI);
                }
            });
        }
    }

在这里插入图片描述

wait和sleep的区别

1.共同点,让线程休眠一会
2.从实现使用上来说是两种不同的方法
wait是Object类的方法,与锁相关,配合sychronized一起使用,调用wait之后会释放锁
sleep是Thread类的方法,与锁无关
wait可以通过notify和指定直线的方法唤醒,唤醒之后重新竞争锁资源
sleep只能通过超时时间唤醒

  • 补充
    在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RPC设计--从reactor设计 (IOthread)

主从reactor架构 一般的一个网络IO库都是主从reactor模式&#xff0c;即主线程中有一个MainReactor&#xff0c;其负责监听ListenFd&#xff0c;当接受到新的用户连接时&#xff0c;返回的clientfd并不会加入的MainReacotr&#xff0c;而是在子线程&#xff08;这里称为IO线程&…

STM32 出租车计价器系统设计(一) 江科大源码改写

STM32 出租车计价器系统设计 功能目标 驱动步进电机模拟车轮旋转&#xff0c;并实现调速功能。 设置车轮周长和单价&#xff0c;检测车轮转速和运转时间。 计算并显示行驶里程和价格。 硬件材料 28BYJ48 五线四相步进电机和 ULN2003 驱动板模块 测速传感器模块 嵌入式小系统…

威胁驱动的网络安全方法论

摘要 目前的网络安全风险管理实践很大程度上是由合规性要求驱动的&#xff0c;这使得公司/组织不得不在安全控制和漏洞上投入人力/物力。&#xff08;风险管理涉及多个方面&#xff0c;包括资产、威胁、漏洞和控制&#xff0c;并根据事故发生的可能性及造成的影响进行评估。威胁…

AUTOSAR:SOME/IP 概念

文章目录 1. 用例与需求1.1 典型用例1.2 对中间件的要求 2. 协议栈示例3. SOME/IP 概念3.1 中间件整体功能与架构3.2 服务组成元素详细解释 4. 服务发现机制深入剖析5. 总结 1. 用例与需求 1.1 典型用例 信息娱乐系统&#xff1a; 后座娱乐系统连接&#xff1a;允许后排乘客连…

如何将自己的PHP类库发布到composer仓库

将自己的 PHP 类库发布到 Composer 仓库&#xff0c;需要经过一系列的准备和操作步骤&#xff0c;以下是详细说明&#xff1a; 准备工作 创建类库项目&#xff1a;确保你的 PHP 类库项目具有清晰的目录结构&#xff0c;遵循 PSR-4 等 PHP 编码规范。通常&#xff0c;类文件应…

Formality:set_svf命令

相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 svf文件的全称是Setup Verification for Formality&#xff0c;即Design Compiler提供给Formality的设置验证文件&#xff0c;它的作用是为Formality的指导模式(Gui…

线性dp—acwing

题目&#xff1a;数字三角形 898. 数字三角形 - AcWing题库 看某个点&#xff0c;是从那些路径过来的去分析 分析1&#xff1a; 代码1&#xff1a;&#xff08;顺序正推&#xff0c;二维dp数组&#xff09; #include<bits/stdc.h> using namespace std;const int N 5…

Selenium WebDriver:自动化网页交互的利器

Selenium WebDriver&#xff1a;自动化网页交互的利器 在当今快速发展的Web开发领域&#xff0c;自动化测试已经成为确保应用程序质量和用户体验的重要手段。Selenium WebDriver&#xff0c;作为Selenium工具包中的核心组件&#xff0c;正是这一领域的佼佼者。本文将详细介绍S…

对于相同问题大模型的生成为什么会不同?

AI因你而升温&#xff0c;记得加个星标哦&#xff01; 大模型的输出是一个token一个token的进行逐步输出&#xff0c;在输出策略上可分为两大类&#xff1a; 贪心解码&#xff1a;直接选择概率最高的单词。这种方法简单高效&#xff0c;但是可能会导致生成的文本过于单调和重复…

12.11作业

1.脑图 定义一个数组&#xff0c;用来存放从终端输入的5个学生的信息【学生的信息包含学生的姓名、年纪、性别、成绩】 1>封装函数 录入5个学生信息 2>封装函数 显示学生信息 3>封装函数 删除第几个学生信息&#xff0c;删除后调用显示学生信息函数 显示 4> 封…

C++11 常用-新特性

一、原始字面量——原文链接 原始字面量 R可以直接得到其原始意义的字符串&#xff08;用于简化&#xff1a;win路径转换、字符串换行需要加连接符&#xff09; 定义方式 //R “xxx(原始字符串)xxx”//这种情况原本在 win下是需要使用\\的 string str2 R"(D:\hello\worl…

独家首发 | 基于 2D-SwinTransformer + BiGRU-GlobalAttention的并行故障诊断模型

往期精彩内容&#xff1a; Python-凯斯西储大学&#xff08;CWRU&#xff09;轴承数据解读与分类处理 基于FFT CNN - BiGRU-Attention 时域、频域特征注意力融合的轴承故障识别模型-CSDN博客 基于FFT CNN - Transformer 时域、频域特征融合的轴承故障识别模型-CSDN博客 P…

ESP32-C3 入门笔记07: ESP-NOW动态绑定MAC地址. (ESP-IDF + VSCode)

ESP-NOW 简介 ESP-NOW [gitbuh] ESP-NOW 是一种由乐鑫公司定义的无连接 Wi-Fi 通信协议。在 ESP-NOW 中&#xff0c;应用程序数据被封装在各个供应商的动作帧中&#xff0c;然后在无连接的情况下&#xff0c;从一个 Wi-Fi 设备传输到另一个 Wi-Fi 设备。 CTR 与 CBC-MAC 协…

《Java核心技术I》并行数组算法

并行数组算法 Arrays类提供了大量并行化操作。 Arrays.parallelSort方法可以对一个基本类型值或对象的数组排序。 package arrays;import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import …

智创 AI 新视界 -- AI 助力金融风险管理的新策略(16 - 10)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

ASP.NET |日常开发中连接Oracle数据库详解

ASP.NET &#xff5c;日常开发中连接Oracle数据库详解 前言一、安装和配置 Oracle 数据访问组件1.1 安装ODP.NET&#xff08;Oracle Data Provider for.NET&#xff09;&#xff1a;1.2 引用相关程序集&#xff1a; 二、配置连接字符串2.1 连接字符串的基本组成部分&#xff1a…

ChatGPT 4:解锁AI文案、绘画与视频创作新纪元

文章目录 AI文案&#xff1a;激发文字的魅力&#xff0c;重塑营销与传播AI绘画&#xff1a;解锁艺术的无限可能&#xff0c;激发创意灵感AI视频&#xff1a;重塑视频创作流程&#xff0c;提升制作效率GPTs&#xff1a;构建个性化AI应用&#xff0c;赋能各行各业《ChatGPT 4 应用…

使用html 和javascript 实现微信界面功能1

1.功能说明&#xff1a; 搜索模块: 提供一个搜索框&#xff0c;但目前没有实现具体的搜索功能。 好友模块: 在左侧的“好友”部分有一个“查看好友”按钮。点击左侧的“查看好友”按钮时&#xff0c;会在右侧显示所有好友的列表。列表中每个好友可以点击查看详情&#xff0c;包…

常用的注解

RequestMapping 用于映射请求路径 可以添加在类或方法上 请求类型 请求类型包括GET、POST、PUT、DELETE等 默认支持GET和POST两种方式 简写&#xff1a;GetMapping、PostMapping、PutMapping、DeleteMapping PostMapping("/buy") 等价 RequestMapping("/buy&quo…

【操作系统】实验二:观察Linux,使用proc文件系统

实验二 观察Linux&#xff0c;使用proc文件系统 实验目的&#xff1a;学习Linux内核、进程、存储和其他资源的一些重要特征。读/proc/stat文件&#xff0c;计算并显示系统CPU占用率和用户态CPU占用率。&#xff08;编写一个程序使用/proc机制获得以及修改机器的各种资源参数。…