Java(九)(多线程,线程安全,实现线程的方法,线程同步,线程池,并发和并行,线程的六种状态)

news2024/11/20 8:46:19

目录

多线程

线程

实现线程的方法

方法一:继承Thread父类

方法二:实现Runnable接口

方法三:Callable接口和FutureTask类来实现

Thread方法

线程安全

线程同步

同步代码块

同步方法

Lock锁

线程池

线程池对象的创建方式一:

线程池处理Runnable任务

线程池处理Callable任务

并发和并行

并发的含义

并行的理解

线程的六种状态


多线程

线程

线程(Thread是一个程序内部单独一条执行流程),程序中如果只有一条执行流程,那这个程序就是单线程的程序

实现线程的方法

方法一:继承Thread父类

我们先写一个MyThread类,表示我们创建的子线程类

public class MyThread extends Thread{
    @Override
    public void run(){
        for (int i = 0; i <= 5; i++) {
            System.out.println("子线程MyThread输出"+i);
        }
    }
}

我们实现main方法

public class main_Thread {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
        for (int i = 0; i <= 5; i++) {
            System.out.println("主线程main输出:"+i);
        }
    }
}

输出结果为

说明我们现在有两个线程,每一个线程都先运行

需要我们注意的是:

第一点: 我们在子类中重写了run方法,但是我们在调用创建的线程对象t的方法是start(),如果是run()方法会变成单线程,会先执行完run方法里的才会执行main函数中的

第二点: 我们的子线程要放到主线程的前面

缺点:一个子类只能继承一个父类,不能再继承其他类,所以继承了Thread之后,不能继承其他类,会导致功能减少

方法二:实现Runnable接口

(1)定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法

public class MyRunnable implements Runnable{
    @Override
    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程1---->"+i);
        }
    }
}

(2)创建MyRunnable任务对象

Runnable r = new MyRunnable();

(3)把MyRunnable任务对象交给Thread处理并且调用对象的start()方法启动线程

new Thread(r).start();

下面是mian方法的完整代码

public class main_Thread {
    public static void main(String[] args) {
        // 创建MyRunnable任务对象
        Runnable r = new MyRunnable();
        new Thread(r).start();
        for (int i = 0; i < 5; i++) {
            System.out.println("mian线程--->"+i);
        }
    }
}

代码优化

不创建子类,用匿名内部类

public class main_Thread {
    public static void main(String[] args) {
        // 创建MyRunnable任务对象

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("子线程--->"+i);
                }
            }
        }).start();
        for (int i = 0; i < 5; i++) {
            System.out.println("mian线程--->"+i);
        }
    }
}

优点: 任务类只能实现接口,可以继续继承其他类,实现其他接口

缺点: 需要多一个Runable对象

方法三:Callable接口和FutureTask类来实现

使用原因:因为我们上面两个方法都是调用start方法,进而让调用run方法的,而run方法是void类型的,不会给我们返回值,这时候就要用到方法三

最大优点:可以返回线程执行完毕后的结果

创建线程的步骤:

1.创建任务对象:

(1)定义一个类实现Callable接口,重写call方法,封装要做的事情,和返回的数据

import java.util.concurrent.Callable;

public class MyCall implements Callable<String> {
    private int n ;

    public MyCall(int n) {
        this.n = n;
    }

    @Override
    public String call() throws Exception {
        // 描述线程的任务,返回线程执行返回后的结果
        // 需求:求1-n的和返回
        int sum = 0;
        for (int i=1;i<n;i++)
        {
            sum+=i;
        }

        return ""+sum;
    }
}

(2)把Callable类型的对象封装成FutureTask(线程任务对象)

//创建一个Callable对象
        Callable<String> call = new MyCall(100);

2.把线程任务对象交给Thread对象

// 把Callable的对象封装成一个FutureTask对象
        // 未来任务对象的作用?
        // 1.是一个任务对象,实现Runnable对象
        // 2.可以在线程执行完毕后,
        // 用未来线程对象调用get方法获取线程执行完毕后值
        FutureTask<String> f1 = new FutureTask<>(call);

3.调用Thread对象的start方法启动线程

// 把任务对象交给一个Thread对象
        new Thread(f1).start();

4.线程执行完毕后,通过FutureTask对象的get()方法去获取线程任务执行的结果

// 获取线程执行完毕后返回的结果
        // 注意: 如果执行到这,假如说线程还没有执行完毕
        // 这里的代码会暂停,等待上面线程执行完毕后才会获取结果
        String s = f1.get();
        System.out.println(s);

mian主线程中的完整代码


import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class main_Thread {
    public static void main(String[] args) throws Exception {
        //创建一个Callable对象
        Callable<String> call = new MyCall(100);
        // 把Callable的对象封装成一个FutureTask对象
        // 未来任务对象的作用?
        // 1.是一个任务对象,实现Runnable对象
        // 2.可以在线程执行完毕后,
        // 用未来线程对象调用get方法获取线程执行完毕后值
        FutureTask<String> f1 = new FutureTask<>(call);
        // 把任务对象交给一个Thread对象
        new Thread(f1).start();

        // 获取线程执行完毕后返回的结果
        // 注意: 如果执行到这,假如说线程还没有执行完毕
        // 这里的代码会暂停,等待上面线程执行完毕后才会获取结果
        String s = f1.get();
        System.out.println(s);

    }
}

Thread方法

public class main_Thread {
    public static void main(String[] args) throws Exception {
        Thread t1 = new MyThread();
        t1.setName("1号线程");
        t1.start();
        System.out.println(t1.getName()); // 输出线程1的名字

        Thread t2 = new MyThread();
        t2.setName("2号线程");
        t2.start();
        System.out.println(t2.getName()); // 输出线程2的名字

        // 主线程对象的名字
        Thread m = Thread.currentThread(); // 拿到当前执行线程名字
        m.setName("nb线程");
        System.out.println(m.getName()); // main

        for (int i = 0; i <= 5; i++) {
            System.out.println("main主线程输出:"+i);
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i <=5 ; i++) {
            if(i== 3)
            {
                // 让当前的线程停止5秒
                Thread.sleep(5000);
            }
        }
        // join方法让调用方法的线程先执行完
        Thread t1 = new Thread();
        t1.start();
        t1.join();

    }
}

线程安全

线程安全问题的原因: 多个线程,同时访问同一个共享资源,且存在修改资源

我们来模拟线程安全,假如说现在取钱,小明和小红在同一个账户下取钱,现在账户有10万元,小明在他的手机上取10万元,小红在她的手机取10万元,想有没有一种可能小明的手机判断有钱但是还没有取钱的同时,小红的手机也判断有钱,也要完成取钱操作,这样的话小明取出来了10万元,小红手机也取出了10万元,而账户中只有10万元,这就是线程安全

我们用代码模拟一下这个情况

我们先创建账户类,用来创建账户对象

package surf_Thread;

public class Account {
    private String Id;
    private int money;

    public Account() {
    }

    public Account(String id, int money) {
        Id = id;
        this.money = money;
    }

    public String getId() {
        return Id;
    }

    public void setId(String id) {
        Id = id;
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    public void drawMoney(int i) {
        // 先搞清楚谁来取钱?
        String name = Thread.currentThread().getName();
        // 判断余额是否足够
        if(this.money>=i)
        {
            System.out.println(name+"来取钱"+money+"成功");
            this.money-=i;
            System.out.println(name+"来取钱后,余额剩余: "+money);
        }
        else{
            System.out.println(name + "来取钱,余额不足");
        }
    }
}

我们在上面创建了一个类的取钱方法,因为每个线程都要进入这个方法,我们可以拿到这个线程的名字

下面我们创建一个线程类,run里面要把握住,这个线程的行为,因为我们要完成取钱,所以必须有一个账户对象,我们要构建一个有参构造器,来接收对那个账户进行操作,还要接受线程的名字

package surf_Thread;

public class DrawMoney extends Thread{
    private Account acc;
    public DrawMoney(Account acc,String name)
    {
        super(name); // super 要放到上面
        this.acc = acc;
    }
    @Override
    public void run()
    {
        //run里面主要实现线程要完成什么事情
        acc.drawMoney(100000);
    }
}

主线程

package surf_Thread;

public class ThreadTest {
    public static void main(String[] args) {
        // 1. 创建一个账户,两个人共同的对象
        Account acc = new Account("ICBC-120",100000);
        // 2.创建取钱线程
        new DrawMoney(acc,"小明").start();
        new DrawMoney(acc,"小红").start();


    }
}

我们对这个案例运行时,发现:

这个就会造成线程安全问题

我们应该怎么解决呢?

看下面

线程同步

同步代码块

建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象

对于静态方法建议使用字节码(类名.class)对象作为锁对象

同步方法

有隐含this

Lock锁

线程池

JDK 5.0起提供了代表线程池的接口: ExecutorService

但是利用接口我们是不能创建线程池对象的,那我们如何得到线程池对象?

线程池对象的创建方式一:

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

需要我们注意的是:

1.临时线程什么时候创建?

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

2.什么时候会开始拒绝新任务?

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

下面是创建线程池

import java.util.concurrent.*;

public class Pool {
    public static void main(String[] args) {
        ExecutorService Pool = new ThreadPoolExecutor(3,5,8, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory()
                ,new ThreadPoolExecutor.AbortPolicy());
        
    }
}

线程池处理Runnable任务

下面是4中不同形式的任务拒绝策略

public class Pool {
    public static void main(String[] args) {
        ExecutorService Pool = new ThreadPoolExecutor(3,5,8, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory()
                ,new ThreadPoolExecutor.AbortPolicy());
        Runnable target = new MyRunnable();
        Pool.execute(target); // 线程池自动创建一个新线程,自动处理这个任务,自动执行
        Pool.execute(target); // 线程池自动创建一个新线程,自动处理这个任务,自动执行
        Pool.execute(target); // 线程池自动创建一个新线程,自动处理这个任务,自动执行
        Pool.execute(target); // 复用上面的线程
        Pool.execute(target); // 复用上面的线程 // 注意临时线程的创建条件:核心线程都占用了,而且队列中都占满了
        // 如果想要创建临时线程,让上面的线程都被占用,而且队列占满
        //假如说现在满足条件,那么下面添加任务就开始创建临时线程
        Pool.execute(target);
        Pool.execute(target);  // 上面创建了两个临时线程
        // 如果再有任务那么就直接拒绝
        
    }
}

因为线程池创建之后开始运行,他不能自动关闭,我们一般情况下也不希望它关闭

那么怎么让他关闭呢?

两种方法:

线程池处理Callable任务

package ThreadPoolTest;

import java.util.concurrent.*;

public class Pool {
    public static void main(String[] args) throws Exception {
        ExecutorService Pool = new ThreadPoolExecutor(3,5,8, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory()
                ,new ThreadPoolExecutor.AbortPolicy());
        Future<String> f1 = Pool.submit(new MyCall(100));
        Future<String> f2 = Pool.submit(new MyCall(200));
        Future<String> f3 = Pool.submit(new MyCall(300));

        String s1 = f1.get();
    }
}

并发和并行

进程: 正在运行的程序(软件)就是独立的进程

线程是属于进程的,一个进程中可以同时运行很多个线程

进程中的多个线程其实是并发和并行的

并发的含义

进程中的线程是由CPU负责执行的,但CPU能同时处理的线程数量有限,为保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发

并行的理解

在同一个时刻上,同时有多个线程在被CPU调度执行

线程的六种状态

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

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

相关文章

办公软件定制开发在企业发展中的优势|app小程序搭建

办公软件定制开发在企业发展中的优势|app小程序搭建 如今&#xff0c;办公软件已经成为企业日常工作的必需品。很多企业为了提高工作效率和满足自身业务需要&#xff0c;选择定制开发办公软件。下面将介绍定制开发办公软件在企业发展中的优势。 1定制开发办公软件可以满足企业…

uni-app 微信小程序 pdf预览

<div click"getPDF">查看体检报告</div>getPDF() {uni.downloadFile({url: ${this.$baseURL}/file/download?id${this.pdfFileId},//编写在线的pdf地址success: function(res) {const filePath res.tempFilePath;uni.openDocument({filePath: filePath…

每日一练【移动零】

一、题目描述 283. 移动零 - 力扣&#xff08;LeetCode&#xff09; 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 二、题目解析 可以…

Appium自动化如果出现报错怎么办?这么做确实解决问题

解决通过appium的inspector功能无法启动app的原因 在打开appium-desktop程序&#xff0c;点击inspector功能&#xff0c;填写app的配置信息&#xff0c;启动服务提示如下&#xff1a; 报错信息&#xff1a; An unknown server-side error occurred while processing the com…

数据结构(超详细讲解!!)第二十五节 树与森林

1.树的存储结构 和线性表一样&#xff0c;树可以用顺序和链式两种存储结构。 树的顺序存储结构适合树中结点比较“满”的情况。根据树的非线性结构特点&#xff0c;常用链式存储方式来表示树。树常用的存储方法有&#xff1a;双亲表示法、孩子表示法和孩子兄弟表…

Toast UI Editor上传图片到Flask

Toast UI Editor国内文档几乎搜不到&#xff0c;国外文档也写得不是特别项目&#xff0c;没有太多举例的demo。一开始选择使用这个就是因为UI好看。不过看看源码把思路滤清了。 他会给把图片转成Base64&#xff0c;到时候发表单直接丢过去就行了&#xff0c;blob这个参数能拿到…

05 Nacos实战:集成Nacos实现分布式配置中心实现配置动态刷新

上一节介绍了Nacos注册中心的功能,这节介绍下木谷博客中如何使用Nacos作为分布式配置中心。 在第二节搭建项目并运行中讲到创建mugu_nacos_config这个数据库,其中已经包含了木谷博客所需的全部配置,在nacos中也可以查看到,如下: 引入Nacos作为配置中心很简单,步骤如下:…

vue3中的customRef创建一个自定义的 ref对象

customRef 创建一个自定义的 ref&#xff0c;并对其依赖项跟踪和更新触发进行显式控制 小案例: 自定义 ref 实现 debounce <template><div style"font-size: 14px;"><input v-model"text" placeholder"搜索关键字"/><…

flask 上传文件

from flask import Flask, request, render_template,redirect, url_for from werkzeug.utils import secure_filename import os from flask import send_from_directory # send_from_directory可以从目录加载文件app Flask(__name__)#UPLOAD_FOLDER media # 注意&#xff…

【TiDB】TiDB离线方式部署

目录 1 下载TiDB离线组件包 2 安装TiUP 3 合并离线包 4 TIDB 软件和硬件环境建议配置 5 TiDB环境与系统配置检查 6 生成集群初始化配置文件模板 7 执行部署命令 1 检查就能存在的潜在风险 2 手动修复风险 3 部署 TiDB 集群 8 查看TIUP管理的集群情况 9 检查部署的…

[⑥ADRV902x]: 软件系统初始化流程学习

前言 本篇博客主要记录ADRV902x参考软件中对ADRV902x系统的初始化流程&#xff0c;使用API函数来实现transceiver的配置&#xff0c;校准和控制等。官方将整个系统初始化称之为multichip synchronization initialization (MCS) sequence&#xff0c;主要分成PreMcsInit&#x…

数字电源为什么一般用DSP控制,而不能用普通的单片机?

数字电源为什么一般用DSP控制&#xff0c;而不能用普通的单片机&#xff1f; 首先你要清楚&#xff0c;数字电源需要一个芯片具备什么功能&#xff1f; 1 能发PWM波 &#xff0c;并且具备保护关断功能&#xff1b; 电源对PWM发波 要求很高&#xff0c;精度要ns级甚至ps级的&…

易宝OA系统ExecuteSqlForSingle接口SQL注入漏洞复现 [附POC]

文章目录 易宝OA系统ExecuteSqlForSingle接口SQL注入漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 易宝OA系统ExecuteSqlForSingle接口SQL注入漏洞复现 [附POC] 0x01 前言 免责声明&#xff1a;请勿利用文章…

2023极客大挑战-AGRT战队wp

目录 RE Shiftjmp 点击就送的逆向题 幸运数字 ​编辑 砍树 小黄鸭 flower-or-tea mySelf 是男人就来扎针 听说cpp很难&#xff1f; Easymath 寻找初音未来 Rainbow 浪漫至死不渝 ezandroid Pwn nc_pwntools password ret2text write1 ret2libc ezpwn wr…

MicroPython STM32F4 RTC功能使用介绍

MicroPython STM32F4 RTC功能使用介绍 &#x1f516;STM32和ESP32 RTC功能差不多&#xff0c;相关篇《MicroPython ESP32 RTC功能使用介绍》&#x1f4cc;固件刷可参考前面一篇《STM32刷Micropython固件参考指南》&#x1f33f; 相关篇《Micropython STM32F4入门点灯》&#x1…

v-model(双向数据绑定)自动收集数据

v-model 是 Vue 中一个常用的指令&#xff0c;用于实现表单元素与数据的双向绑定。 它的实现原理主要基于以下两个方面&#xff1a; 语法实现&#xff1a; v-model 实际上是 Vue 对 :value 和 input 两个属性的语法糖。当我们在组件中使用 v-model 指令时&#xff0c;Vue 会根…

只需十分钟快速入门Python,快速了解基础内容学习。零基础小白入门适用。

文章目录 简介特点搭建开发环境版本hello world注释文件类型变量常量数据类型运算符和表达式控制语句数组相关函数相关字符串相关文件处理对象和类连接mysql关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源…

数据在内存中的存储练习题

数据在内存中的存储练习题 文章目录 数据在内存中的存储练习题1. 练习一2.练习二3. 练习三4. 练习四5. 练习五6. 练习六7. 总结 1. 练习一 #include <stdio.h>int main() {char a -1;signed b -1;unsigned char c -1;printf("a %d b %d c %d", a, b, c)…

WebSocket快速入门

WebSocket 借鉴&#xff1a; https://blog.csdn.net/weixin_45747080/article/details/117477006 https://cloud.tencent.com/developer/article/1887095 简介 WebSocket 是一种网络传输协议&#xff0c;可在单个 TCP 连接上进行全双工通信&#xff0c;位于 OSI 模型的应用…

第七节HarmonyOS UIAbility生命周期以及启动模式

一、UIAbility生命周期 为了实现多设备形态上的裁剪和多窗口的可扩展性&#xff0c;系统对组件管理和窗口管理进行了解耦。UIAbility的生命周期包括Create、Foreground、Background、Destroy四个状态&#xff0c;WindowStageCreate和WindowStageDestroy为窗口管理器&#xff08…