多线程学习和Thread类

news2025/1/21 22:06:15

多线程的创建使用和Thread类

  • 一、多线程相关概念
    • 1. 并行与并发
    • 2. 进程与线程
    • 3. 多线程的作用
    • 4. 线程调度
  • 二、多线程创建使用
    • 1.经典的两种方式
    • 2. 匿名内部类实现
    • 3 Thread类
      • 3.1 构造器
      • 3.2 基本方法
      • 3.3 线程控制方法
      • 3.4 守护线程
  • 三、 线程的生命周期
  • 四、线程安全
    • 方式1:继承Thread类
    • 方式2:实现Runnable接口
    • 出现线程安全问题的前提条件
    • 解决方法:同步代码块synchronized
    • 锁对象
    • 编写多线程程序的一般原则
    • 单例模式之懒汉式的同步代码块

一、多线程相关概念

1. 并行与并发

  • 并行:同一个时间点,多个事件同时执行.
  • 并发:同一个微小的时间段,多个事件正在执行.

2. 进程与线程

  • 进程:是程序运行过程的描述,系统以进程为单位为每个程序分配独立的系统资源,比如内存。
  • 线程:线程是进程中的执行单元,主要负责执行程序的任务,每个进程至少有一个线程。

3. 多线程的作用

主要优点:充分利用CPU空闲时间片,用尽可能短的时间完成用户的请求,即使程序的响应速度更快。

4. 线程调度

表示CPU计算资源在不同线程中切换。有不同的调度方式:

  • 分时调度:所有线程轮流使用CPU使用权,平均分配。
  • 抢占式调度:按照优先级抢占,同级的话随机分配。

二、多线程创建使用

1.经典的两种方式

实现多线程有两种方式:

  • 继承Thread类
  • 实现Runnable接口

方式一:

  1. 创建类(线程任务类)继承Thread,并重写run方法
  2. 创建线程类对象
  3. 调用线程对象的start方法启动线程
public class Thread1Demo {
    public static void main(String[] args) {
        //2.创建线程对象
        MyThread myThread = new MyThread();
        //3.启动线程
        myThread.start();

        //主线程代码
        for (int i = 0; i < 30; i++) {
            System.out.println("======主线程=======" + i);
        }
    }
}

//1.编写线程类,继承Thread类
class MyThread extends Thread{
    //重写run方法,即线程的任务

    @Override
    public void run() {
        System.out.println("线程任务启动");
        for (int i = 0; i < 30; i++) {
            System.out.println("分支线程" + i);
        }
    }
}

注意事项

  • 不要直接调用run方法来启动线程,run方法是由jvm来调用的
  • 不要重复启动同一个线程

方式二:

  1. 创建类(线程任务类)实现Runnable接口,并重写run方法
  2. 创建线程类对象
  3. 创建Thread对象,通过构造器传入线程任务对象
  4. 调用线程对象的start方法启动线程
public class Thread2Demo {
    public static void main(String[] args) {
        A a = new A();
        Thread thread = new Thread(a);
        thread.start();

        //主线程代码
        //Thread thread = Thread.currentThread();
        for (int i = 0; i < 30; i++) {
            System.out.println("======主线程=======" + i);
        }
    }
}

class A implements Runnable{

    @Override
    public void run() {
        //System.out.println("线程任务启动");
        for (int i = 0; i < 30; i++) {
            System.out.println("分支线程" + i);
        }
    }
}

注:使用接口创建多个线程,方便共享数据,多个线程可以都使用同一个线程任务类启动线程,在后期线程的同步机制中也更为便于锁的实现。

2. 匿名内部类实现

当创建一个线程,只会使用一次后就不会再使用了,可以使用匿名内部类方式实现。

public class Demo3 {
    public static void main(String[] args) {
        //方式1匿名写法:
        new Thread(){
            @Override
            public void run() {
                System.out.println("线程任务A");
            }
        }.start();
        
        //方式2匿名写法:
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程任务B");
            }
        }).start();
    }
}

3 Thread类

3.1 构造器

  1. 空参构造器:public Thread()
  2. 指定名字:public Thread(String name)
  3. 指定线程任务对象: public Thread(Runnable target);

3.2 基本方法

  1. run(): 定义线程执行任务
  2. isAlive(): 判断当前线程是否还在活动状态
  3. currentThread(): 返回当前线程对象的引用
  4. setName():设置线程名称
  5. getName():获取线程名称
  6. setPriority(): 设置线程优先级
  7. getPriority(): 获取线程优先级

线程优先级最高10,最低1,主线程默认优先级为5,主线程创建的子线程默认优先级也为5.

3.3 线程控制方法

  1. start(): 启动线程
  2. sleep(): 线程休眠
  3. yield(): 线程礼让,抢到CPU资源后让出给其他线程。
  4. join(): 线程插队,插入线程执行结束后,被插队线程再执行。
  5. stop (): 强迫线程终止,不安全,已废弃。
  6. interrupt(): 给线程标记为中断状态
  7. interrupted(): 判断线程的中断状态,判断后会清除中断标记。
  8. isInterrupted(): 判断线程的中断状态,不会清除中断标记。

注: 礼让有可能并没有礼让成功,如果没有其他线程使用CPU资源,原线程还会继续执行。

3.4 守护线程

线程可以分为两类:用户线程和守护线程。用户线程都结束时,守护线程也会随着用户线程的结束而终止。

设置守护线程的方法:

  1. 必须在启动线程前设置
  2. setDaemon(true)

JVM中的垃圾回收器就是一个守护线程,守护的就是程序就是用户线程,程序结束时,守护线程就好自动结束。

三、 线程的生命周期

线程生命周期的五种状态:新建、就绪、运行、阻塞、死亡

在这里插入图片描述

JDK将线程状态分为6种。其中就绪和运行合并为可运行状态,阻塞状态分为了三种:定时等待状态、无限等待状态、等待监视器锁。
在这里插入图片描述

四、线程安全

使用多个线程模拟多个窗口的卖票过程,保证卖票过程的正常运行。

方式1:继承Thread类

public class Demo {
    public static void main(String[] args) {
        new TicketSell("窗口1").start();
        new TicketSell("窗口2").start();
        new TicketSell("窗口3").start();
    }
}


class TicketSell extends Thread{
    static int num = 100;
    public TicketSell(String name) {
        super(name);
    }

    @Override
    public void run() {
        //int num = 100;//局部变量
        while(num > 0) {
            System.out.println(Thread.currentThread().getName()+"卖出了第"+num+"张票");
            num -- ;
        }
        System.out.println("=====买完了=====");
    }
}

运行结果:出现了重票问题,第100张卖了多次。
在这里插入图片描述

方式2:实现Runnable接口

public class Demo2 {
    public static void main(String[] args) {
        TicketSell2 sell2 = new TicketSell2();
        new Thread(sell2, "窗口1").start();
        new Thread(sell2, "窗口2").start();
        new Thread(sell2, "窗口3").start();
    }
}

class TicketSell2 implements Runnable{
    static int num = 100;

    @Override
    public void run() {
        //int num = 100;//局部变量
        while(num > 0) {
            System.out.println(Thread.currentThread().getName()+"卖出了第"+num+"张票");
            num -- ;
        }
        System.out.println("=====买完了=====");
    }
}

运行结果:同样出现线程安全问题,这里就不再贴图了。

出现线程安全问题的前提条件

  1. 多个线程访问
  2. 访问的是共享数据
  3. 操作共享数据的过程不是原子操作

解决方法:同步代码块synchronized

出现线程安全问题的前两个条件无法改变,我们就是想让多线程去访问共享数据。故只能对第三个条件进行处理,其中sychronized关键字来将访问共享数据的操作变成原子操作。同步代码块要越小越好,同步代码块越大,效率越低。代码块最小时,应该包含所有操作共享数据的代码,其中本问题中的num就是共享数据。必须将判断num大小和对num--的代码都包含在代码块中才能解决线程安全问题。

public class Demo2 {
    public static void main(String[] args) {
        TicketSell2 sell2 = new TicketSell2();
        new Thread(sell2, "窗口1").start();
        new Thread(sell2, "窗口2").start();
        new Thread(sell2, "窗口3").start();
    }
}

class TicketSell2 implements Runnable{
    static int num = 100;


    @Override
    public void run() {
        //int num = 100;//局部变量
        while(true) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (this) {//this就是锁对象
                if(num > 0)//共享数据
                    System.out.println(Thread.currentThread().getName()+"卖出了第"+num--+"张票");//共享数据
            }
            if(num <= 0) {
            	break;
            }
        }
        System.out.println("=====买完了=====");
    }
}

锁对象

当线程获取到锁对象才能执行同步代码,在执行完同步代码时会持有锁对象,执行完同步代码后才会释放锁对象。

因此,在使用同步代码块解决线程安全问题时,必须保证锁对象是同一个实例。如果上面代码中的synchronized (this) 更换为new Object()时就仍然会出现线程安全问题。

  • 非静态静态方法,默认的锁对象就是this
  • 静态同步代码块建议使用当前类的Class实例作为锁对象

编写多线程程序的一般原则

把共享数据和操作共享数据的方法封装到一个类中,作为共享资源类,与线程类解耦合。还是以买票问题为例子,将该问题中的买票和操作共享数据分成两个类来处理。

共享资源类:

public class Ticket{
	private int num = 100;//共享数据
	//操作共享数据的方法
	public synchronized void sale(){
		if(num > 0)
			System.out.println("卖出了第" +(num--)+"张票");
	}
	//get-set方法
}

线程类:

public class TicketThread extend Thread{
	private Ticket ticket;//共享资源
	public TicketThread(String name, Ticket ticket){
		super(name);
		this.ticket = ticket;
	}
	@Override
	public void run(){
		while(true){
			ticket.sale();
			if (ticket.getNum() <= 0){
				return ;
			}
		}
	}
}

单例模式之懒汉式的同步代码块

在实现单例模式时,懒汉式是先判断当前对象实例是否已经创建了,如果为空就创建一个新的对象,否则就new一个唯一的实例。如果在多线程的情况下, 由于判断是否为空和创建对象实例不是原子操作,在CPU资源切换时有可能导致单例模式失效的问题,也需要用到同步代码块。

public class Singleton {
    //私有的静态变量
    private static Singleton instance = null;

    private Singleton(){

    }

    public static Singleton getInstance(){
        if(instance == null){//如果目前对象还未创建,就进入同步代码块,否则直接返回实例对象即可
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

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

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

相关文章

hive-无法启动hiveserver2

启动hiveserver2没有反应&#xff0c;客户端也无法连接( beeline -u jdbc:hive2://node01:10000 -n root) 报错如下 查看hive的Log日志&#xff0c;发现如下报错 如何解决 在hive的hive_site.xml中添加如下代码 <property><name>hive.server2.active.passive…

8年测试经验之谈 —— JMeter生成HTML性能测试报告

1.修改JMeter的配置文件 2.配置环境变量 3.控制台到测试脚本目录并执行命令 4.查看测试报告 1.修改JMeter的配置文件 在JMeter本地安装目录下&#xff0c;把bin目录下jmeter.properties文件中的jmeter.save.saveservice.output_formatcsv禁⽤取消 # legitimate values: xml…

涨薪加薪利器:聊聊Synchronized和Volatile的差异究竟何在?

大家好&#xff0c;我是小米&#xff01;今天&#xff0c;我们要聊一个在Java多线程编程中非常重要的话题&#xff1a;Synchronized和Volatile的区别。这两个关键字常常令人迷惑&#xff0c;但却是我们编写高效、稳定多线程程序不可或缺的工具。废话不多说&#xff0c;让我们一…

Redis专题-队列

Redis专题-队列 首先&#xff0c;想一想 Redis 适合做消息队列吗&#xff1f; 1、消息队列的消息存取需求是什么&#xff1f;redis中的解决方案是什么&#xff1f; 无非就是下面这几点&#xff1a; 0、数据可以顺序读取 1、支持阻塞等待拉取消息 2、支持发布/订阅模式 3、重…

Vue 批量注册组件

全局组件 在components文件夹下新建一个Gloabl文件夹&#xff08;可以自行命名&#xff09; 在目录下新建index.js import Vue from vue// require.context(路径, 是否遍历子目录, 匹配规则) const requireComponents require.context(./, true, /\.vue/)requireComponents.k…

常见的前端对数据的操作方法

[{"name": "蒸汽锅炉 A 年度检验报告","sort": 5,"analysisComponent": "组件类型","trHeadVOList": [],"trContentVOList": [{"jieguo": "√","jianyanxiangmu": "…

神经网络分类算法的应用及其实现

目录 神经网络分类算法的应用及其实现 神经网络算法特点 1) 黑盒算法 2) 数据量 3) 算力和开发成本高 神经网络算法应用 神经网络分类算法的应用及其实现 神经网络算法特点 我们知道&#xff0c;深度学习的本质就是神经网络算法&#xff08;深度学习是神经网络算法的一个…

嵌入式编译FFmpeg6.0版本并且组合x264

下载直通车:我用的是6.0版本的 1.准备编译: 2.进入ffmpeg源码目录&#xff0c;修改Makefile&#xff0c;添加编译选项&#xff1a; CFLAGS -fPIC 不加会报错 3.使用命令直接编译 ./configure --cross-prefix/home/xxx/bin/arm-linux-gnueabihf- --enable-cross-compile --targ…

生信豆芽菜-多种算法计算免疫浸润

网址&#xff1a;http://www.sxdyc.com/immuneInfiltration 一、使用方法 1、数据准备 一个全编码蛋白的表达谱基因&#xff0c;其中行为基因&#xff0c;列为样本 第一列为基因为行名&#xff0c;不能重复 2、选择计算的方法&#xff08;这里提供了5种免疫计算的方法&#x…

php错误类型与处理

1 语法编译错误&#xff0c;少了分号&#xff0c;这是系统触发的错误&#xff0c;不需要我们去管。 2 错误类型有四种&#xff1a;error致命错误&#xff0c;代码不会往下运行&#xff1b;warning&#xff1a;提醒错误&#xff0c;会往下运行&#xff0c;但是会有意想不到的结果…

【BEV】3D视觉 PRELIMINARY

这里的知识来自于论文 Delving into the Devils of Bird’s-eye-view Perception: A Review, Evaluation and Recipe 的 Appendix B.1 部分来自 这篇文章 从透视图转向鸟瞰图。&#xff08;Xw、Yw、Zw&#xff09;、&#xff08;Xc、Yc、Zc&#xff09;表示世界World坐标和相…

Unity TreeView 树形菜单

文章目录 1. 参考文章2. 工程地址3. 项目结构4. 主要代码 1. 参考文章 https://blog.csdn.net/qq992817263/article/details/54925472 2. 工程地址 将文件夹放入 unity 中即可查看 作者 github 地址&#xff1a;https://github.com/ccUnity3d/TreeView 本人 gitee 地址(不用…

IronPDF for .NET Crack

IronPDF for .NET Crack ronPDF现在将等待HTML元素加载后再进行渲染。 IronPDF现在将等待字体加载后再进行渲染。 添加了在绘制文本时指定旋转的功能。 添加了在保存为PDFA时指定自定义颜色配置文件的功能。 IronPDF for.NET允许开发人员在C#、F#和VB.NET for.NET Core和.NET F…

【数据库系统】-- 【1】DBMS概述

1.DBMS概述 01数据库系统概述02数据库技术发展概述03关系数据库概述04数据库基准测试 01数据库系统概述 几个基本概念 为什么使用数据库系统 数据库发展的辉煌历程 02数据库技术发展概述 数据模型 应用领域 ● OLTP ● OLAP ● HTAP ● GIS OLTP与OLAP 与其他技术相…

基于C#的消息处理的应用程序 - 开源研究系列文章

今天讲讲基于C#里的基于消息处理的应用程序的一个例子。 我们知道&#xff0c;Windows操作系统的程序是基于消息处理的。也就是说&#xff0c;程序接收到消息代码定义&#xff0c;然后根据消息代码定义去处理对应的操作。前面有一个博文例子( C#程序的启动显示方案(无窗口进程发…

actuator/prometheus使用pushgateway上传jvm监控数据

场景 准备 prometheus已经部署pushgateway服务&#xff0c;访问{pushgateway.server:9091}可以看到面板 实现 基于springboot引入支持组件&#xff0c;版本可以 <!--监控检查--><dependency><groupId>org.springframework.boot</groupId><artifa…

【刷题笔记8.15】【链表相关】LeetCode:合并两个有序链表、反转链表

LeetCode&#xff1a;【链表相关】合并两个有序链表 题目1&#xff1a;合并两个有序链表 题目描述 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3…

第14集丨Vue2 基础教程 —— 生命周期

目录 一、引子1.1 实现一1.2 一个死循环的写法1.3 mounted实现 二、生命周期2.1 概念2.2 常用的生命周期钩子2.3 关于销毁Vue实例注意点2.4 vm的一生(vm的生命周期)2.5 生命周期图示 每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如&#xff0c;需要设置数据监听、…

如何能够写出带货的爆文?

网络推广这个领域&#xff0c;公司众多价格差别很大&#xff0c;就拿软文文案这块来讲&#xff0c;有人报价几十块&#xff0c;也有人报价几千块。作为企业的营销负责人往往会被价格吸引&#xff0c;比价择优选用&#xff0c;结果写出来的文案不满意&#xff0c;修改也无从入手…

vs2019 vs2022默认以管理员身份运行

找到快捷方式属性&#xff0c;点高级&#xff0c;把“用管理员身份运行”打勾再确定&#xff0c;之前是有个兼容性选项卡的&#xff0c;在没有选项卡的情况下就用这种方法