当前位置: 首页 > news >正文

JAVA EE_多线程-初阶(三)

我对未来没有底气

我也不知道当下该如何做

那就活着,活着就能把日子过下去

                                                                                                                                    ---------陳長生.


1.多线程案例

1.1.单例模式

        单例模式是常见的设计模式之一

        设计模式:一些编程大佬制定的一些通用代码,再特定的场景下能套用进去,即使是小白也能使用。

        单例模式能保证类在程序中只有一个实例,不会创建多个实例。

        单例模式中常用的便是”饿汉模式“和”懒汉模式“。

1.1.1饿汉模式

//饿汉模式
class Singleton{
    //创建类时就创建实例
    private static Singleton instance =new Singleton();
    //提供方法获取实例
    public static Singleton getInstance(){
        return instance;
    }
    //构造方法私有化,防止外部创建实例
    private Singleton(){}
}

饿汉模式在创建实例时就创建实例化对象。   

1.1.2懒汉模式

//懒汉模式
class Singletonlan{
    //创建实例赋值为空
    private static Singletonlan instance =null;
    //静态的锁对象,保证原子性
    //volatile关键字:保证可见性,禁止指令重排序
    //防止指令重排序:在创建实例时,可能会发生指令重排序,导致其他线程获取到未初始化的实例
    private static volatile Object lock=new Object();
    //构造方法私有化,防止外部创建实例
    public static Singletonlan getInstance(){
        //先判断是否为空,为空则创建
        if(instance==null){
            //保证原子性
            synchronized(lock){
                //再次判断是否为空,为空则创建
                if(instance==null){
                    instance =new Singletonlan();
                }
            }
        }
        //返回实例
        return  instance;
    }
}

懒汉模式则是在需要的时候才实例化对象

懒汉模式逐步解析:
synchronized:

        因为线程运行是随机性的,所以我们要加上synchronized保证线程的原子性。

双重if:

        内部if语句:但instance为空时则为其实例化,反之直接返回,这个好理解。

        外部if语句:我们只需要在第一次没有实例化赋值的时候获取锁就好了,因为单例模式只需要只需要一个实例。假设我们去掉外部的if语句,那是不是每次不管他有没有创建实例都得进行获取锁和解锁操作,这就大大降低了时间效率,这时候我们就需要在外部加个if语句判断它是否为空。

指令重排序:

        指令重排序是编译器提供的优化功能,能够调整一些指令的运行顺序

         我们在创建实例时,在编译器背后会有三条指令:1.为对象申请空间,2.初始化对象,3.将内存地址保存在引用变量中。

        那我们设想一下,本来1,2,3的执行顺序被指令重排序后,线程先执行了1,3这两条指令后,线程2一下执行了三条指令,这时候的线程2拿到的是线程1没有初始化的变量,那么运行出来的结果我们是不可想象的。        

1.2.阻塞队列

1.2.1.阻塞队列是什么

  • 阻塞队列是一种特殊的队列,遵循着“先进先出”原则
  • 阻塞队列是一种线性安全的数据结构
  • 当队列为空的时候,继续出队列会堵塞,直到有其他线程往队列中插入元素
  • 当队列为满的时候,继续入队列会堵塞,直到有其他线程往队列中取出元素
  • 是一种典型的“生产者和消费者模型”

1.2.2.生产者消费者模型

        生产者消费者模型就是通过一个容器,存储生产者生产的东西,然后消费者在从其中取出

阻塞队列相当于一个缓存区,平衡了生产者和消费者之间的平横关系

        例如:双十一淘宝做活动的时候,有大量的人进入淘宝进行购物操作,如果是生产者和消费者直接沟通,那么如此庞大的任务量就会造成系统崩溃

        这种情况就像我们抢选修课的时候,一大批人进入选课系统进行选课操作,就会导致很多人加载不进选课系统,这种就是生产者和消费者直接沟通

        

        但是淘宝在生产者和消费者之间构建了阻塞队列,所以在双十一这种活动的时候并不会出现我们选课系统的这种情况。

阻塞队列也能使生产者和消费者之间“解耦”:

        例如:在工厂中的某一条流水线作业上,长生员工(生产者)负责组装工作,陈依(消费者)负责产品的贴码和扫码录入操作

        高耦合:陈依等待着长生将产品组装完再进行贴码和扫码,如果长生装的慢的话,陈依就得等长生;如果装的快的话,陈依跟不上长生的速度,长生就得等陈依,这就称为高耦合,大大降低了时间效率。

        低耦合:假设在长生和陈依之间放个框子,长生将做好的产品放入框子中,陈依则直接从框子中取得产品直接进行贴码和扫码操作,这样长生装的快的话就不用一件一件的给陈依了,这就称为低耦合,提高了时间效率。

1.2.3.标准库中的阻塞队列

JAVA的标准库中自带了阻塞队列BlockingQueue,我们需要的时候直接使用就好了

  • BlockingQueue是接口,真正实现的是LinkedBlockingQueue
  • 该类的 put() 和 take() 方法提供了阻塞功能 
  • 虽然也有其他的方法,但是他们不提供阻塞功能
生产者消费者模型创建:
package Thread;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class text1 {
    public static void main(String[] args) {
        //生产者模型
        //实例化阻塞队列
        BlockingQueue<Integer> blockingQueue=new LinkedBlockingQueue<>();
        //生产线程
        Thread t1=new Thread(()->{
            //生产数据
            int count=0;
            //一直执行任务
            while(true){   
                try{
                    //将数据放入阻塞队列
                    blockingQueue.put(count);
                    System.out.println("生产数据"+count);
                    //持续生产数据
                    count++;
                    //线程休眠(0.5秒执行一次任务)
                    Thread.sleep(500);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        });
        //消费者模型
        Thread t2=new Thread(()->{
            while(true){
                try{
                    //从阻塞队列中取出数据
                    int n=blockingQueue.take();
                    System.out.println("消费数据"+n);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }

        });

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

1.2.4.阻塞队列的实现

  • 通过“循环队列”来实现
  • put();插入元素时,如果队列满了就堵塞,反之就加入数据
  • take():取出元素时,如果队列为空就堵塞,反之就取出数据
package Thread;

public class text2 {
    //模拟实现阻塞队列
    private int[] array=new int[1000];
    //定义头和尾
    private int head=0;
    private int tail=0;
    //定义队列长度
    private int size=0;
    //定义锁
    private volatile Object lock=new Object();
    //模拟实现构造put()方法
    public void put(int val) throws InterruptedException{
        //保证原子性
        synchronized(lock){
            //如果队列满了,就堵塞
            while(size==array.length){
                lock.wait();
            }
            //如果队列没满,就放入数据
            array[tail]=val;
            //更新tail
            tail++;
            //如果tail到达数组末尾,就从头开始
            if(tail==array.length){
                tail=0;
            }
            //更新队列长度
            size++;
            //唤醒等待线程
            lock.notify();
        }
    }

    //模拟实现take()方法
    public int take() throws InterruptedException {
        //保证原子性
        synchronized(lock){
           //如果队列为空,就堵塞
            while(size==0){
             lock.wait();
           }
           //如果队列不为空,就定义一个返回值取出队列
           int ret=array[head];
           //更新head
           head++;
           //如果head到达数组末尾,就从头开始
           if(head==array.length){
               head=0;
           }
           //更新队列长度
           size--;
           //唤醒等待线程
           lock.notify();
           //返回值
           return ret;
        }
    }

    public static void main(String[] args) {
        //生产者模型
        //实例化阻塞队列
       text2 blockingQueue=new text2();
        //生产线程
        Thread t1=new Thread(()->{
            //生产数据
            int count=0;
            //一直执行任务
            while(true){   
                try{
                    //将数据放入阻塞队列
                    blockingQueue.put(count);
                    System.out.println("生产数据"+count);
                    //持续生产数据
                    count++;
                    //线程休眠(0.5秒执行一次任务)
                    Thread.sleep(500);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        });
        //消费者模型
        Thread t2=new Thread(()->{
            while(true){
                try{
                    //从阻塞队列中取出数据
                    int n=blockingQueue.take();
                    System.out.println("消费数据"+n);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }

        });

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

1.3.线程池

1.3.1.线程池是什么

概念:
  • 线程池就是几个线程的集合
  • 一个线程池中分为核心线程和临时线程
  • 核心线程:相当于公司的正式员工
  • 核心线程:相当于公司的实习生,在需要的时候用,不需要的时候直接抄鱿鱼~
为什么使用线程池:

        假设长生要去出差,那么出去的花销公司要报销

        情况一(不使用线程池):做一件事报销一次,住宿提交一次财务申请,吃饭提交一次财务申请,材料提交一次财务申请·····

        情况二(使用线程池):事先获得一笔经费,当需要的时候直接使用这笔钱,最后回到公司报告时多退少补。

        所以使用线程池之后就能提高效率,大大节省时间的开销。

1.3.2.标准库中的线程池

    JAVA提供了ExecutorService管理多线程的接口

newFixedThreadPool()创建固定线程池
newCachedThreadPool()创建缓存线程池,长度为“int的最大值”
newSingleThreadPool()创建单个线程池,长度为1
newScheduledThreadPool()       创建定时线程池,长度为动态增长
package Thread;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo3 {
    public static void main(String[] args) {
        //创建固定值为10的线程池
        ExecutorService pool1=Executors.newFixedThreadPool(10);
        //创建缓存线程池,长度为“int的最大值”
        ExecutorService pool2=Executors.newCachedThreadPool();
        //创建单个线程池,只有一个线程
        ExecutorService pool3=Executors.newSingleThreadExecutor();
        //创建一个定时任务线程池,长度为动态增长
        ExecutorService pool4=Executors.newScheduledThreadPool(10);
    }
}

1.3.3.实现线程池

  • 使用BlickingQueue阻塞队列存储任务
  • 定义submit()方法读取任务放入阻塞队列中
  • 在MyFixThreadPool类中创建线程并读取队列中的任务执行
package Thread;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

class MyFixThreadPool{ 
    //定义一个阻塞队列
    private BlockingQueue<Runnable> queue =new LinkedBlockingQueue<>();
    //定义构造方法,创建线程池,n为线程池的长度
    public MyFixThreadPool(int n){
        //创建n个线程,执行任务
        for(int i=0;i<n;i++){ 
            //创建线程
            Thread t=new Thread(()->{
                try{
                    //重阻塞队列取出任务执行
                    while(true){
                        Runnable task=queue.take();
                        task.run();
                    }
                }catch(InterruptedException e){
                    e.printStackTrace();
                }

            });
            //启动线程
            t.start();
        }
    }
    //定义submit()方法,提交任务
    public void submit(Runnable task) throws InterruptedException {
        //往阻塞队列中放入任务
        queue.put(task);
    }
}

public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        MyFixThreadPool pool=new MyFixThreadPool(10);
        pool.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello world");
            }
        });
    }
}   

1.4.定时器

1.4.1.定时器是什么

  • 定时器相当于现实中的闹钟,到达某个指定的时间时,就执行任务。
  • 定时器是开发中非常常用的组件,例如对方在500ms内没有回复,就断开连接

1.4.2.标准库中的定时器

  • 标准库中提供了一个Timer类,并提供了schedule()方法
  • schedule()方法包含两个参数,一个参数为要执行的任务,一个为指定的时间(毫秒为单位)
import java.util.Timer;
import java.util.TimerTask;

public class Demo5 {
    public static void main(String[] args) {
        //创建定时器
        Timer timer=new Timer();
        //创建定时任务,调用schedule方法
        timer.schedule(new TimerTask() {
            //执行任务
            @Override
            public void run() {
                System.out.println("定时任务执行了");
            }
            //指定时间执行
        }, 1000 );
    }
}

相关文章:

  • 驱动开发硬核特训 · Day 6 : 深入解析设备模型的数据流与匹配机制 —— 以 i.MX8M 与树莓派为例的实战对比
  • 第十六届蓝桥杯大赛软件赛省赛 Python 大学 B 组 部分题解
  • 辛格迪客户案例 | 西藏奇正藏药MES项目
  • 【Docker基础】深入解析 Docker 存储卷:管理、绑定与实战应用
  • 安宝特新闻丨Vuzix Core™波导助力AR,视角可调、高效传输,优化开发流程
  • echarts地图添加涟漪波纹点位
  • PostgreSQL技术大讲堂 - 第86讲:数据安全之--data_checksums天使与魔鬼
  • 多模态学习分析(MLA)驱动高中差异化教学策略研究
  • 单卡4090微调大模型 DeepSeek-R1-32B
  • 人工智能时代教育主体性的哲学反思与技术治理
  • 【PostGresql】-----PG按本月、本年数据统计并且行数据转列字段数据查询
  • Kubernetes-如何进入某POD中
  • (六)深入了解AVFoundation-播放:AirPlay、画中画后台播放
  • SQLyog 小记
  • 2021第十二届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组
  • 从零构建一个全栈AI应用:Next.js + FastAPI + OpenAI API
  • AbortController:让异步操作随时说停就停
  • Linux:37信号lesson26(未完)
  • 模板引擎语法-标签
  • 【C#】线程回调
  • 东明网站建设推广/成功的网络营销案例有哪些
  • 建设网站需要多大域名空间/线上营销活动主要有哪些
  • 杭州网站改版公司电话/人工智能培训师
  • 石家庄网站排名/网站改版公司哪家好
  • 微营销平台/郑州seo方案
  • 常州行业网站/营销策划有限公司经营范围