【多线程】Java如何实现多线程?如何保证线程安全?如何自定义线程池?

在这里插入图片描述

个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~
个人主页:.29.的博客
学习社区:进去逛一逛~

在这里插入图片描述

多线程

  • Java多线程
    • 1. 进程与线程
    • 2. 多线程
      • 1) 相关概念
      • 2) 多线程实现方式
        • ①继承Thread类
        • ②实现Runnable接口
        • ③利用Callable接口和Future接口
        • ④ 比较优缺点
    • 3. Thread类 常用方法
    • 4. 线程安全
      • 1) 同步代码块
      • 2) 同步方法
      • 3) Lock锁
    • 5. 等待唤醒机制(生产者和消费者)
      • 1) 生产者
      • 2) 消费者
      • 测试:
      • 3) 等待唤醒 —— 阻塞队列方式
    • 6. 线程状态
    • 7. 线程池
      • 自定义线程池


Java多线程


1. 进程与线程

线程

  • 线程是操作系统能够进行运算调度的最小单位。它被包含在进程中,是进程中的实际运作单位。

  • 生命周期:

  • 在这里插入图片描述


进程

  • 进程是程序的基本执行实体。



2. 多线程


1) 相关概念

相关概念

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

2) 多线程实现方式

①继承Thread类

多线程第一种实现方式

  • ①继承Thread类

  • ②重写run方法

  • ③创建子类的对象,并使用start()方法启动线程

  • /**
     * @author .29.
     * @create 2023-10-17 16:21
     */
    public class extendThread {
        //1.自定义类,继承Thread类
        static class myThread extends Thread{
            //2.重写run方法,编写执行逻辑
            @Override
            public void run() {
                for(int i = 0;i < 100; ++i){
                    System.out.println("执行:" + getName());
                }
            }
        }
    
        public static void main(String[] args){
            //3. 实现Thread子类对象,使用start()启动线程
            myThread t1 = new myThread();
            myThread t2 = new myThread();
            t1.setName("线程1号");
            t2.setName("线程2号");
            t1.start();
            t2.start();
        }
    }
    
②实现Runnable接口

多线程第二种实现方式

  • ①自定义类,实现Runnable接口。
  • ②重写接口中的run方法。
  • ③实例化Runnable实现类
  • ④以Runnable实现类对象为参数,创建Thread实例,开启线程。
public class implementsRunnable {
    //1.自定义类,实现Runnable接口
    static class myRun implements Runnable{
        //2.重写抽象方法
        @Override
        public void run() {
            //编写需要执行的程序
            for(int i = 0;i < 100; ++i){
                //获取当前执行的线程
                Thread t = Thread.currentThread();
                System.out.println("执行:" + t.getName());
            }

        }
    }

    public static void main(String[] args){
        //3.实例化Runnable实现类
        myRun mr = new myRun();

        //4.创建线程对象,使用start()启动线程
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        t1.start();
        t2.start();

    }
}
③利用Callable接口和Future接口

多线程第三种实现方式

  • 特点: 可以获取到多线程运行的结果
  • ①创建一个Callable接口的实现类
  • ②重写Callable接口中的call方法(返回多线程执行的结果)
  • ③创建Callable实现类对象(表示多线程要实现的任务)
  • ④创建Future实现类FutureTask的对象(用于管理多线程运行的结果)
  • ⑤创建Thread对象,并启动
/**
 * @author .29.
 * @create 2023-10-17 21:09
 */
public class implementsCallable {
    //1.创建Callable接口实现类,泛型指定返回的线程执行结果的类型
    static class myCall implements Callable<Integer>{
        //2.重写实现类方法
        @Override
        public Integer call() throws Exception {
            return 29;
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //3.实例化Callable实现类
        myCall call = new myCall();
        //4.创建FutureTask对象,管理多线程执行结果
        FutureTask<Integer> ft = new FutureTask<>(call);
        //5.创建线程,并启动
        Thread t1 = new Thread(ft);
        t1.start();

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


④ 比较优缺点

在这里插入图片描述




3. Thread类 常用方法

常用的成员方法

  • String getName():返回此线程名称

  • void setName(String name):修改此线程名称

  • static Thread currentThread():获取当前线程实例对象

  • static void sleep(long time):让线程休眠指定时间(单位:毫秒)

  • setPriority(int newPriority):设置线程的优先级

  • final int getPriority():获取此线程的优先级,线程默认的优先级为5,优先级范围1-10,数字越大,优先级越高。

  • final void setDaemon(boolean on):设置为守护线程,当其他的非守护线程执行完毕后,守护线程就没有存在的必要了,会陆续结束,不一定会执行完毕。

  • public static void yield():出让线程/礼让线程

    •     //1.自定义类,继承Thread类
          static class myThread extends Thread{
              //2.重写run方法,编写执行逻辑
              @Override
              public void run() {
                  for(int i = 0;i < 100; ++i){
                      System.out.println("执行:" + getName());
                      //出让CPU的执行权,即出让线程
                      Thread.yield();
                  }
              }
          }
      
  • public static void join()插入线程/插队线程,将此线程插入到当前线程之前,只有插入的线程执行完毕,当前线程才会执行。




4. 线程安全

1) 同步代码块

  • 格式:

    • synchronized(锁对象){
          //操作共享数据的代码
      }
      
    • 锁对象:可以是任意一个对象,但需要对象是唯一的,使用的锁不一样,没意义。建议使用当前类的字节码文件对象:Xxx.class

  • 特点:

    • ①锁默认是打开状态,有一个线程进去了,锁自动关闭。
    • ②里面的代码全部执行完毕,线程出来,锁自动打开。

2) 同步方法

  • 同步方法 —— 将synchronized关键字加到方法上。

  • 格式:

    • 修饰符 synchronized 返回值类型 方法名(方法参数){...}
      
  • 特点:

    • ①同步方法是锁住方法里面所有的代码。
    • ②锁对象不能自己指定
      • 非静态方法的锁对象:this(当前方法的调用者)
      • 静态方法的锁对象:当前类的字节码文件对象(Xxx.class)

3) Lock锁

  • 为了清晰表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
  • Lock中提供了获取锁释放锁的方法:
    • void lock()——获得锁
    • void unlock()——释放锁
  • Lock是接口,获取Lock对象需要实例化Lock的实现类ReentrantLock
    • ReentrantLock()——构造方法、创建一个ReentrantLock实例

注意: 为了保证锁的释放,当调用lock()获得锁后,后面执行的代码放入到try{}catch{}块中,之后在finally{}块中调用unLock(),因为finally块中代码一定会执行,也就保证了锁一定会被释放。




5. 等待唤醒机制(生产者和消费者)

常用方法

  • void wait():当前线程等待,直至被其他线程唤醒
  • void notify():随机唤醒单个线程
  • void notifyAll():唤醒所有线程

1) 生产者

生产者 代码

/**
 * @author .29.
 * @create 2023-10-18 21:18
 */
//继承Thread
public class Producer extends Thread{
    //重写run方法
    @Override
    public void run() {
        //1.循环
        while(true){
            //2. 同步代码块
            synchronized(Product.lock){
                if(Product.count == 0){
                    break;
                }else{
                    if(Product.flag == 0){ //货架没有商品
                        System.out.println("生产者生产商品"); //生产
                        Product.flag = 1;                   //上架(修改状态)
                        Product.lock.notifyAll();           //唤醒与锁绑定的所有线程

                    }else{
                        try {
                            Product.lock.wait(); //货架有商品,等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                    }
                }
            }
        }
    }
}

商品货架:

/**
 * @author .29.
 * @create 2023-10-18 21:20
 */
public class Product {
    //总个数
    public static int count = 10;
    //消费者可否消费 0无产品,不可消费 1有产品,可消费
    public static int flag = 0;
    //锁对象
    public static Object lock = new Object();
}

2) 消费者

消费者 代码

/**
 * @author .29.
 * @create 2023-10-18 21:18
 */
//继承Thread
public class Consumer extends Thread{
    //重写run方法
    @Override
    public void run() {
        //1.循环
        while(true){
            //2.同步代码块
            synchronized(Product.lock){
                //3.判断货架是否上架了商品
                if(Product.flag == 0){
                    //没有商品线程进入等待
                    try {
                        Product.lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    //可以消费,消费者进行消费,总数-1
                    Product.count--;
                    System.out.println("消费者消费商品,还可消费"+Product.count+"件商品");
                    //4. 通过锁,唤醒与锁绑定的所有线程
                    Product.lock.notifyAll();
                    //5. 修改货架状态
                    Product.flag = 0;
                }
            }
        }

    }
}

测试:

/**
 * @author .29.
 * @create 2023-10-18 21:39
 */
public class Test {
    public static void main(String[] args){
        Producer producer = new Producer();
        Consumer consumer = new Consumer();

        producer.start();
        consumer.start();
    }
}

运行

生产者生产商品
消费者消费商品,还可消费9件商品
生产者生产商品
消费者消费商品,还可消费8件商品
生产者生产商品
消费者消费商品,还可消费7件商品
生产者生产商品
消费者消费商品,还可消费6件商品
生产者生产商品
消费者消费商品,还可消费5件商品
生产者生产商品
消费者消费商品,还可消费4件商品
生产者生产商品
消费者消费商品,还可消费3件商品
生产者生产商品
消费者消费商品,还可消费2件商品
生产者生产商品
消费者消费商品,还可消费1件商品
生产者生产商品
消费者消费商品,还可消费0件商品

3) 等待唤醒 —— 阻塞队列方式

阻塞队列继承结构

  • Iterable 接口
  • Collection 接口
  • Queue 接口
  • BlockingQueue 接口
    • ArrayBlockingQueue 实现类
      • 底层是数组,有界
    • LinkedBlockingQueue实现类
      • 底层是链表,可看作无界,上限是int类型最大值

生产者 代码

/**
 * @author .29.
 * @create 2023-10-19 10:19
 */
public class Producer extends Thread{
    private ArrayBlockingQueue<String> blockingQueue;

    public Producer(ArrayBlockingQueue<String> bq){
        blockingQueue = bq;
    }

    @Override
    public void run() {
        while(true){//不断生产商品
            try {
                blockingQueue.put("商品");
                System.out.println("生产者生产了商品"); //输出语句不在线程同步范围内
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

消费者 代码

/**
 * @author .29.
 * @create 2023-10-19 10:19
 */
public class Consumer extends Thread{
    private ArrayBlockingQueue<String> blockingQueue;

    public Consumer(ArrayBlockingQueue<String> bq){
        blockingQueue = bq;
    }

    @Override
    public void run() {
        while(true){//不断消费商品
            String take = null;
            try {
                //take(),获取阻塞队列元素,方法底层已经实现线程同步
                take = blockingQueue.take();
                System.out.println("消费者消费了 :" + take); //输出语句不在线程同步范围内
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

测试

public class test {
    public static void main(String[] args){
        //实例化BlockingQueue对象,传入参数1,表示阻塞队列长度为1
        ArrayBlockingQueue<String> bq = new ArrayBlockingQueue<>(1);

        Consumer consumer = new Consumer(bq);
        Producer producer = new Producer(bq);

        consumer.start();
        producer.start();
    }
}

输出:

生产者生产了商品
生产者生产了商品
消费者消费了 :商品
消费者消费了 :商品
生产者生产了商品
生产者生产了商品
消费者消费了 :商品
消费者消费了 :商品
生产者生产了商品
生产者生产了商品
消费者消费了 :商品
消费者消费了 :商品
生产者生产了商品
生产者生产了商品
消费者消费了 :商品
消费者消费了 :商品
  • 输出存在重复现象,是因为输出语句不在线程同步范围内导致的,实际上put和take是线程安全的。



6. 线程状态

  • 1)新建(NEW),至今尚未启动的线程处于这种状态。
    • 创建线程对象后
  • 2)就绪(RUNNABLE),正在Java虚拟机中执行的线程处于这种状态。
    • 调用start()后
  • 3)阻塞(BLOCKING),受阻塞并等待某个监视器锁的线程处于这种状态。
    • 无法获得锁对象时
  • 4)等待(WAITING),无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
    • 调用wait()后
  • 5)计时等待(TIMED WAITING),等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
    • 调用sleep()后
  • 6)死亡(TERMINATED),已退出的线程处于这种状态。
    • 全部代码运行完毕后

在这里插入图片描述




7. 线程池

核心原理

  • ①创建一个空的线程池
  • ②提交任务时,线程池创建新的线程对象,执行完毕,线程归还线程池,等待下次任务
  • ③提交任务时若线程池没有空闲线程,也无法创建新的线程(线程池满了),任务就会排队等待。
  • 不断提交的任务,存在以下临界点:
      1. 当核心线程满时,再提交任务就会排队。
      2. 当核心线程满,队伍也满时,会创建临时线程。
      3. 核心线程满,队伍满,临时线程也满时,会触发任务拒绝策略。
    • 任务拒绝策略:
      • ThreadPoolExecutor.AbortPolicy:默认策略、丢弃任务并抛出RejectedExecutionException异常
      • ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常,(不推荐)
      • ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入等待队列。
      • ThreadPoolExecutor.CallerRunsPolicy:调用任务的run()方法绕过线程池直接执行。

实现线程池

  • Executors:线程池的工具类,通过调用方法返回不同类型的线程池对象。
    • public static ExecutorService newCachedThreadPool():创建一个没有上限的线程池。
    • public static ExcutorService newFixedThreadPool(int nThreads) 创建一个有上限的线程池,参数表示线程池的上限。
      • submit(Runnable/Callable<> in)方法:提交任务
      • shutdown()方法:销毁线程池

自定义线程池

  • 参数① 核心线程数量(不能小于0)
  • 参数② 最大线程数量(不能小于0,最大数量 >= 核心线程数)
  • 参数③ 空闲线程最大存活时间(不能小于0)
  • 参数④ 时间单位(用TimeUnit指定)
  • 参数⑤ 任务队列(不能为null)
  • 参数⑥ 创建线程工厂(不能为null)
  • 参数⑦ 任务的拒绝策略(不能为null)
  • 任务拒绝策略:
    • ThreadPoolExecutor.AbortPolicy:默认策略、丢弃任务并抛出RejectedExecutionException异常
    • ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常,(不推荐)
    • ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入等待队列。
    • ThreadPoolExecutor.CallerRunsPolicy:调用任务的run()方法绕过线程池直接执行。
/**
 * @author .29.
 * @create 2023-10-19 12:08
 */
public class ThreadPoolDemo {
    public static void main(String[] args){

        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,         //核心线程数
                6,                     //最大线程数,不能小于0,需要大于等于核心线程数
                60,                    //空闲线程最大存活时间
                TimeUnit.SECONDS,      //时间单位,用TimeUnit设置
                new ArrayBlockingQueue<>(3), //任务队列
                Executors.defaultThreadFactory(),     //创建线程工厂
                new ThreadPoolExecutor.AbortPolicy() //任务的拒绝策略(线程池内部类)
        );

        pool.submit(new myCall());//提交任务
        
        pool.shutdown();//销毁线程池

    }
}




在这里插入图片描述

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

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

相关文章

脏牛提权 liunx

使用方法 Liunx 普通用户 内核版本 在版本里 我直接脏牛提权 有脚本查看内核版本 上传c脚本 编译 直接执行 获取高权限 提权 Liunx https://github.com/InteliSecureLabs/Linux Exploit Suggester 运行这个脚本 上传到客户端 https://github…

小插曲 -- 使用Visual Studio Code远程连接香橙派

在之前的学习中&#xff0c;代码的修改和保存都依赖于“vi”指令&#xff0c;而不得不承认vi指令的编辑界面非常原始&#xff0c;所以&#xff0c;如果可以将代码编辑放到更友好的环境里进行无疑是一件大快人心的事情。 本节介绍如何通过Visual Studio Code来进行远程连接: Vi…

二进制搭建 Kubernetes+部署网络组件+部署CornDNS+负载均衡部署+部署Dashboard

二进制搭建 Kubernetes v1.20 k8s集群master01&#xff1a;20.0.0.50 kube-apiserver kube-controller-manager kube-scheduler etcd k8s集群master02&#xff1a;20.0.0.100k8s集群node01&#xff1a;20.0.0.110 kubelet kube-proxy docker etcd k8s集群node02&#xff1a;20.…

SysTick—系统定时器

SysTick 简介 SysTick—系统定时器是属于CM3内核中的一个外设&#xff0c;内嵌在NVIC中。系统定时器是一个24bit 的向下递减的计数器&#xff0c;计数器每计数一次的时间为1/SYSCLK&#xff0c;一般我们设置系统时钟SYSCLK 等于72M。当重装载数值寄存器的值递减到0的时候&#…

研发效能(DevOps)职业技术认证-第六期开班啦丨IDCF

本证书是由国家工业和信息化部教育与考试中心颁发的职业技术证书&#xff0c;也是国内首个《研发效能&#xff08;DevOps&#xff09;工程师职业技术认证》。该《认证》对研发效能&#xff08;DevOps&#xff09;工程师的职业技术分为初级、中级、高级三个专业等级。 IDCF社区…

商场巨变!拓世法宝AI智能商业一体机引爆智慧购物新浪潮

在如今信息爆炸的时代&#xff0c;大型商场的规模与复杂程度也呈现出愈发庞大的趋势。它的背后不仅是商场规模的扩大&#xff0c;更是商业模式的转型升级。消费者对于购物体验和服务质量的要求也日益提高。传统商场单一提供商品销售的职能已无法满足消费者多元化的需求&#xf…

wf-docker集群搭建(未完结)

系列文章目录 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 系列文章目录前言一、redis集群二、mysql集群三、nacos集群1. 环境要求2. 拉取镜像2.1. 拉取镜像方式配置集群2.2. 自定义nacos镜像配置集群 3 自定义…

基于windows10的pytorch环境部署及yolov8的安装及测试

第一章 pytorch环境部署留念 第一步&#xff1a;下载安装anaconda 官网地址 &#xff08;也可以到清华大学开源软件镜像站下载&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/&#xff09; 我安装的是下面这个&#xff0c;一通下一步就完事儿。 第二步…

EtherCAT从站转CclinkIE协议网关应用案例

远创智控的YC-ECT-CCLKIE网关&#xff0c;一款具有强大功能的ETHERCAT通讯网关。 它可以将ETHERCAT网络和CCLINK IE FIELD BASIC网络无缝连接起来。作为ETHERCAT总线中的从站&#xff0c;本网关可以接收来自ETHERCAT主站的数据&#xff0c;并将其传输到CCLINK IE FIELD BASIC网…

高效MMdetection(3.1.0)环境安装和训练自己数据集教程(实现于Linux(ubuntu),可在windows尝试)

一、前言 很久没用mmdetection了&#xff0c;作为目标检测常见的几个深度学习框架&#xff0c;mmdetection用的人还是很多的&#xff0c;其中比较吸引人的一点就是mmdetection集成了非常多的算法&#xff0c;对于想做实验对比和算法学习的人来说&#xff0c;基于这个框架可以事…

cola架构:cola源码中访问者模式应用浅析

目录 1.访问者模式简介 2.cola访问者模式应用 2.1 cola被访问者类图 2.2 cola访问者类图 我们知道&#xff0c;如果一个对象结构包含很多类型的对象&#xff0c;希望对这些对象实施一些依赖其具体类型的操作&#xff0c;但又避免让这些操作“污染”这些对象的类&#xff0c…

day50 --动态规划9

198.打家劫舍 213.打家劫舍II 337.打家劫舍III 第一题&#xff1a;打家劫舍 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一…

Stable Diffusion AI绘图

提示词&#xff1a; masterpiece, best quality, 1girl, (anime), (manga), (2D), half body, perfect eyes, both eyes are the same, Global illumination, soft light, dream light, digital painting, extremely detailed CGI anime, hd, 2k, 4k background 反向提示词&…

WebSocket 入门案例

目录 WebSocket入门案例WebSocket-server新增项目:添加依赖:yml:启动类&#xff1a; frontend-server前端项目&#xff1a;添加依赖&#xff1a;添加yml&#xff1a;启动类&#xff1a;前端引入JS:前端页面&#xff1a;后端代码&#xff1a;测试&#xff1a; WebSocket 入门案…

css-边框流水线

效果图&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8"><meta name"viewport" content"initial-scale1.0, user-scalableno" /><title></title><style type&…

物联网知识复习

物联网的内涵和体系结构 物联网的基本内涵 物联网的基本内涵在于物联&#xff0c;物物相连或者物和人相连的互联网。 也就是说&#xff0c;它是要由物主动发起的&#xff0c;物物互联的互联网。 它的第一层意思是说物和物相连&#xff1b;第二层意思是说物和人相连。 物联网的…

Redis数据类型——set类型数据交并差操作

1.业务场景 2.求两个set集合中交并补的操作

Easyx趣味编程7,鼠标消息读取及音频播放

hello大家好&#xff0c;这里是dark flame master&#xff0c;今天给大家带来Easyx图形库最后一节功能实现的介绍&#xff0c;前边介绍了绘制各种图形及键盘交互&#xff0c;文字&#xff0c;图片等操作&#xff0c;今天就可以使写出的程序更加生动且容易操控。一起学习吧&…

Leetcode周赛365补题(3 / 3)

目录 1、2、有序三元组的最大值 - 预处理前后最大值 遍历 &#xff08;1&#xff09;预处理前后值遍历&#xff08;枚举j&#xff09; &#xff08;2&#xff09;枚举k 2、无限数组的最短子数组 - 前缀和 滑动窗口 1、2、有序三元组的最大值 - 预处理前后最大值 遍历 …

【STM32】标准库的引入

一、为什么要会有标志外设库 1、传统单片机软件开发方式 (1)芯片厂商提供数据手册、示例代码、开发环境 (2)单片机软件工程师面向产品功能&#xff0c;查阅数据手册&#xff0c;参考官方示例代码进行开发 (3)硬件操作的方式是用C语言对寄存器进行读写以操作硬件 (4)主要工作量…