每天学一点之多线程

news2025/8/3 9:55:16

多线程

一、相关概念

并发与并行

并行(parallel):指多个事件任务在同一时刻发生(同时发生)。
并发(concurrency):指两个或多个事件在同一个微小的时间段内发生。程序并发执行可以在有限条件下,充分利用CPU资源。
在这里插入图片描述
单核CPU:只能并发
多核CPU:并行+并发

线程与进程

  • 程序:为了完成某个任务和功能,选择一种编程语言编写的一组指令的集合。

  • 软件:1个或多个应用程序+相关的素材和资源文件等构成一个软件系统。

  • 进程是对一个程序运行过程(创建-运行-消亡)的描述,系统会为每个运行的程序建立一个进程,并为进程分配独立的系统资源,比如内存空间等资源。

  • 线程:线程是进程中的一个执行单元,负责完成执行当前程序的任务,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这时这个应用程序也可以称之为多线程程序。多线程使得程序可以并发执行,充分利用CPU资源。

    面试题:进程是操作系统调度和分配资源的最小单位,线程是CPU调度的最小单位。不同的进程之间是不共享内存的。进程之间的数据交换和通信的成本是很高。不同的线程是共享同一个进程的内存的。当然不同的线程也有自己独立的内存空间。对于方法区,堆中中的同一个对象的内存,线程之间是可以共享的,但是栈的局部变量永远是独立的。

多线程的优点与应用场景

  • 主要优点:
    • 充分利用CPU空闲时间片,用尽可能短的时间完成用户的请求。也就是使程序的响应速度更快 。
  • 应用场景:
    • 多任务处理。多个用户请求服务器,服务端程序可以开启多个线程分别处理每个用户的请求,互不影响。
    • 单个大任务处理。下载一个大文件,可以开启多个线程一起下载,减少整体下载时间。

线程调度

指CPU资源如何分配给不同的线程。常见的两种线程调度方式:

  • 分时调度

    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

  • 抢占式调度

    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java采用的是抢占式调度方式

二、线程的创建与启动

继承Thread类

通过继承Thread类来创建并启动多线程的步骤:

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程

多线程执行情况分析
在这里插入图片描述
注意事项:

  • 手动调用run方法不是启动线程的方式,只是普通方法调用。

  • start方法启动线程后,run方法会由JVM调用执行。

  • 不要重复启动同一个线程,否则抛出异常IllegalThreadStateException

  • 不要使用Junit单元测试多线程,不支持,主线程结束后会调用System.exit()直接退出JVM;

实现Runnable接口

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
  3. 调用线程对象的start()方法来启动线程。

两种创建线程方式比较

  • Thread类本身也是实现了Runnable接口的,run方法都来自Runnable接口,run方法也是真正要执行的线程任务。

    public class Thread implements Runnable {}
    
  • 因为Java类是单继承的,所以继承Thread的方式有单继承的局限性,但是使用上更简单一些。

  • 实现Runnable接口的方式,避免了单继承的局限性,并且可以使多个线程对象共享一个Runnable实现类(线程任务类)对象,从而方便在多线程任务执行时共享数据。

匿名内部类对象创建线程

匿名内部类对象的方式创建线程,并不是一种新的创建线程的方式,只是在线程任务只需执行一次的情况下,我们无需单独创建线程类,可以采用匿名对象的方式:

new Thread("新的线程!"){
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(getName()+":正在执行!"+i);
		}
	}
}.start();

new Thread(new Runnable(){
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+":" + i);
        }
    }
}).start();

三、Thread类

构造方法

  • public Thread() :分配一个新的线程对象。
  • public Thread(String name) :分配一个指定名字的新的线程对象。
  • public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

线程使用基础方法

  • public void run() :此线程要执行的任务在此处定义代码。

  • public String getName() :获取当前线程名称。

  • public static Thread currentThread() :返回对当前正在执行的线程对象的引用。

  • public final boolean isAlive():测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。

  • public final int getPriority() :返回线程优先级

  • public final void setPriority(int newPriority) :改变线程的优先级

    • 每个线程都有一定的优先级,优先级高的线程将获得较多的执行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。Thread类提供了setPriority(int newPriority)和getPriority()方法类设置和获取线程的优先级,其中setPriority方法需要一个整数,并且范围在[1,10]之间,通常推荐设置Thread类的三个优先级常量:
    • MAX_PRIORITY(10):最高优先级
    • MIN _PRIORITY (1):最低优先级
    • NORM_PRIORITY (5):普通优先级,默认情况下main线程具有普通优先级。

线程控制常见方法

  • public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。

  • public static void sleep(long millis) :线程睡眠,使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。

  • public static void yield():线程礼让,yield只是让当前线程暂时失去执行权,让系统的线程调度器重新调度一次,希望优先级与当前线程相同或更高的其他线程能够获得执行机会,但是这个不能保证,完全有可能的情况是,当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。

  • void join() :加入线程,当前线程中加入一个新线程,等待加入的线程终止后再继续执行当前线程。

    void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待。

    void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。

  • public final void stop():强迫线程停止执行。 该方法具有不安全性,已被弃用,最好不要使用。

    • 调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 语句中的,并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。
    • 调用 stop() 方法会立即释放该线程所持有的所有的锁,导致数据得不到同步,出现数据不一致的问题。
  • public void interrupt():中断线程,实际上是给线程打上一个中断的标记,并不会真正使线程停止执行。

  • public static boolean interrupted():检查线程的中断状态,调用此方法会清除中断状态(标记)。

  • public boolean isInterrupted():检查线程中断状态,不会清除中断状态(标记)

  • public void setDaemon(boolean on):将线程设置为守护线程。必须在线程启动之前设置,否则会报IllegalThreadStateException异常。

    • 守护线程,主要为其他线程服务,当程序中没有非守护线程执行时,守护线程也将终止执行。JVM垃圾回收器也是守护线程。
  • public boolean isDaemon():检查当前线程是否为守护线程。

  • volatile的作用是确保不会因编译器的优化而省略某些指令,volatile的变量是说这变量可能会被意想不到地改变,每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份,这样,编译器就不会去假设这个变量的值了。

线程生命周期

传统线程模型的五种线程状态
在这里插入图片描述

JDK定义的六种线程状态
java.lang.Thread类内部定义了一个枚举类用来描述线程的六种状态:

    public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }

在这里插入图片描述
说明:当从WAITINGTIMED_WAITING恢复到Runnable状态时,如果发现当前线程没有得到监视器锁,那么会立刻转入BLOCKED状态。

线程安全

当我们使用多个线程访问同一资源(可以是同一个变量、同一个文件、同一条记录等)的时候,但是如果多个线程中对资源有读和写的操作,就会出现前后数据不一致问题,这就是线程安全问题。

线程安全问题引出

  • 局部变量不能共享:局部变量是每次调用方法都是独立的,那么每个线程的run()的数据是独立的,不是共享数据。
  • 不同对象的实例变量不共享 :不同的实例对象的实例变量是独立的。
  • 静态变量是共享的
  • 同一个对象的实例变量共享

总结:线程安全问题的出现因为具备了以下条件

  1. 多线程执行
  2. 共享数据
  3. 多条语句操作共享数据

线程安全问题解决方式

在这里插入图片描述
同步方法:synchronized 关键字直接修饰方法,表示同一时刻只有一个线程能进入这个方法,其他线程在外面等着。

public synchronized void method(){
    可能会产生线程安全问题的代码
}

同步代码块:synchronized 关键字可以用于某个区块前面,表示只对这个区块的资源实行互斥访问。

synchronized(同步锁){
     需要同步操作的代码
}

锁对象选择

首选this其次是类.class也可以是" "空字符串对象

同步锁对象:

  • 锁对象可以是任意类型。
  • 多个线程对象 要使用同一把锁。

同步代码块的锁对象

  • 静态代码块中:使用当前类的Class对象
  • 非静代码块中:习惯上先考虑this,但是要注意是否同一个this

锁的范围太小:不能解决安全问题,要同步所有操作共享资源的语句。

锁的范围太大:因为一旦某个线程抢到锁,其他线程就只能等待,所以范围太大,效率会降低,不能合理利用CPU资源。

单例设计模式的线程安全问题

1、饿汉式没有线程安全问题

饿汉式:上来就创建对象

2、懒汉式线程安全问题

public class Singleton {
    private static Singleton ourInstance;

    public static Singleton getInstance() {
        //一旦创建了对象,之后再次获取对象,都不会再进入同步代码块,提升效率
        if (ourInstance == null) {
            //同步锁,锁住判断语句与创建对象并赋值的语句
            synchronized (Singleton.class) {
                if (ourInstance == null) {
                    ourInstance = new Singleton();
                }
            }
        }
        return ourInstance;
    }

    private Singleton() {
    }
}

等待唤醒机制

在一个线程满足某个条件时,就进入等待状态(wait()/wait(time)), 等待其他线程执行完他们的指定代码过后再将其唤醒(notify());或可以指定wait的时间,等时间到了自动唤醒;在有多个线程进行等待时,如果需要,可以使用 notifyAll()来唤醒所有的等待线程。wait/notify 就是线程间的一种协作机制。

  1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING或TIMED_WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”或者等待时间到,在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
  2. notify:则选取所通知对象的 wait set 中的一个线程释放;
  3. notifyAll:则释放所通知对象的 wait set 上的全部线程。

注意:
被通知线程被唤醒后也不一定能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。

总结如下:

  • 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE(可运行) 状态;
  • 否则,线程就从 WAITING 状态又变成 BLOCKED(等待锁) 状态

调用wait和notify方法需要注意的细节

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用,并且必须要通过锁对象调用这2个方法。

释放锁操作与死锁

1、释放锁的操作

  • 当前线程的同步方法、同步代码块执行结束。

  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致当前线程异常结束。

  • 当前线程在同步代码块、同步方法中执行了锁对象的wait()方法,当前线程被挂起,并释放锁。

2、死锁

不同的线程分别锁住对方需要的同步监视器对象不释放,都在等待对方先放弃时就形成了线程的死锁。一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

3、面试题:sleep()和wait()方法的区别

(1)sleep()不释放锁,wait()释放锁

(2)sleep()指定休眠的时间,wait()可以指定时间也可以无限等待直到notify或notifyAll

(3)sleep()在Thread类中声明的静态方法,wait方法在Object类中声明

因为我们调用wait()方法是由锁对象调用,而锁对象的类型是任意类型的对象。那么希望任意类型的对象都要有的方法,只能声明在Object类中。

练习

要求两个线程,同时打印字母,每个线程都能连续打印3个字母。两个线程交替打印,一个线程打印字母的小写形式,一个线程打印字母的大写形式,但是字母是连续的。当字母循环到z之后,回到a。

在这里插入图片描述

public class PrintLetterDemo {
	public static void main(String[] args) {
		// 2、创建资源对象
		PrintLetter p = new PrintLetter();

		// 3、创建两个线程打印
		new Thread("小写字母") {
			public void run() {
				while (true) {
					p.printLower();
					try {
						Thread.sleep(1000);// 控制节奏
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}.start();

		new Thread("大写字母") {
			public void run() {
				while (true) {
					p.printUpper();
					try {
						Thread.sleep(1000);// 控制节奏
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}.start();
	}
}

// 1、定义资源类
class PrintLetter {
	private char letter = 'a';

	public synchronized void printLower() {
		for (int i = 1; i <= 3; i++) {
			System.out.println(Thread.currentThread().getName() + "->" + letter);
			letter++;
			if (letter > 'z') {
				letter = 'a';
			}
		}
		this.notify();
		try {
			this.wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public synchronized void printUpper() {
		for (int i = 1; i <= 3; i++) {
			System.out.println(Thread.currentThread().getName() + "->" + (char) (letter - 32));
			letter++;
			if (letter > 'z') {
				letter = 'a';
			}
		}
		this.notify();
		try {
			this.wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
} 

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

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

相关文章

steam/csgo搬砖项目到底真的假的?

搬砖是从国外steam市场置办游戏装备回来&#xff0c;在国内网易buff售卖&#xff0c;低买高卖&#xff0c;产生利润的一个项目。 但我真正上手后&#xff0c;才知道steam是面向全球的游戏平台&#xff0c;用户真的大的夸张&#xff01;&#xff01;市场非常巨大&#xff0c;一…

物联网毕设 -- 智能厨房监测系统(改)

前言 在家庭生活中&#xff0c;厨房是必不可少的&#xff0c;所以厨房的安全问题关乎着我们大家的生命&#xff0c;所以提出智能厨房监测系统&#xff0c;目的就是为我们减少不必要的安全问题 ⚠️⚠️&#xff08;本文章仅提供思路和实现方法&#xff0c;并不包含代码&#x…

雷电模拟器安卓7以上+Charles抓包APP最新教程

一、工具准备&#xff1a; 证书安装工具全局代理工具下载&#xff1a; https://download.csdn.net/download/weixin_51111267/87536481 二、Charles设置 &#xff08;一&#xff09;电脑上证书安装 &#xff08;二&#xff09;安卓模拟器上系统证书安装&#xff08;RooT权限打…

wireshark 抓包使用记录

文章目录前言wireshark 抓包使用记录一、wireshark的基础使用二、wireshark的常用功能1、开始混杂模式2、过滤器操作2.1、抓包过滤器2.2、显示过滤器3、时间格式显示4、统计流量图5、标记显示6、导出数据包7、增加、隐藏、删除显示列前言 如果您觉得有用的话&#xff0c;记得给…

体验Linux USB 驱动

目录 一、USB OTG 二、I.MX6ULL USB 接口简介 硬件原理图 1、USB HUB 原理图 2 、USB OTG 原理图 三、使能驱动 1、打开 HID 驱动 2、 使能 USB 键盘和鼠标驱动 3 、使能 Linux 内核中的 SCSI 协议 4、使能 U 盘驱动 四、测试u盘 五、 Linux 内核自带 USB OTG USB 是…

vue3常用的API

目录 1.ref函数 2.reactive函数 3.reactive对比ref 4.computed函数 5.watch函数 6.toRef 7..provide && inject 1.ref函数 作用: 定义一个响应式的数据 语法: const xxx ref(initValue) 创建一个包含响应式数据的引用对象&#xff08;reference对象&#xff…

MySQL数据库基本操作

DDL 1、DDL解释 DDL(Data Definition Language)&#xff0c;数据定义语言&#xff0c;该语言部分包括以下内容&#xff1a; 对数据库的常用操作 对表结构的常用操作 修改表结构1、对数据库的常用操作 2、对表结构的常用操作-创建表 创建表格式 3、对表结构的常用操作-创建表…

Springboot——@valid 做字段校验和自定义注解

文章目录前言注意实现测试环境验证自带的注解自定义valid注解自定义注解和处理类创建参数接收类&#xff0c;并增加字段注解接口中使用自测环节正常测试异常测试自定义全局异常监听扩展递归参数下valid不识别的坑前言 再项目开发中&#xff0c;针对前端传递的参数信息&#xf…

机器学习中的数学——精确率与召回率

在Yolov5训练完之后会有很多图片&#xff0c;它们的具体含义是什么呢&#xff1f; 通过这篇博客&#xff0c;你将清晰的明白什么是精确率、召回率。这个专栏名为白话机器学习中数学学习笔记&#xff0c;主要是用来分享一下我在 机器学习中的学习笔记及一些感悟&#xff0c;也希…

Java开发 - Redis初体验

前言 es我们已经在前文中有所了解&#xff0c;和es有相似功能的是Redis&#xff0c;他们都不是纯粹的数据库。两者使用场景也是存在一定的差异的&#xff0c;本文目的并不重点说明他们之间的差异&#xff0c;但会简要说明&#xff0c;重点还是在对Redis的了解和学习上。学完本…

Lab2_Simple Shell_2020

Lab2: 实验目的&#xff1a;给xv6添加新的系统调用 并理解系统调用是如何工作的&#xff0c;并理解xv6内核的一些内部特征 实验准备&#xff1a; 阅读xv6的第2章以及第4章的4.3,4.3小节熟悉下面的源码 用户态相关的代码&#xff1a;user/user.h和user/usys.pl内核态相关的代…

第八章:枚举类与注解

第八章&#xff1a;枚举类与注解 8.1&#xff1a;枚举类的使用 ​ 类的对象只有有限个&#xff0c;确定的。我们称此类为枚举类。当需要定义一组常量是&#xff0c;强烈建议使用枚举类。如果枚举类中只有一个对象&#xff0c;则可以作为单例模式的实现方式。 如何定义枚举类 …

一图来看你需要拥有那些知识储备

技术实践 数据 关系型数据 MySQLSQLServerOraclePostgrSQLDB2 大数据存储 RedisMemcacheMongoDBHBaseHive 大数据处理 Hadoop 数据报表看板 DataGearGrafanaKibanaMetaBase 消息对列 Rabbit MQRock MQActive MQKafka 大数据搜索 SolrElasticSearchLucenHive 服务提…

HBase负载均衡的实现机制

数据库集群负载均衡的实现依赖于数据库的数据分片设计&#xff0c;可以在一定程度上认为数据分片就是数据读写负载&#xff0c;那么负载均衡功能就是数据分片在集群中均衡的实现。 一、Region迁移 作为一个分布式系统&#xff0c;分片迁移是最基础的核心功能。集群负载均衡、…

【实现“下一题”按钮的功能 Objective-C语言】

一、刚才我们把上半部分这个界面,给大家搭好了,懒加载也加载好了,接下来,我们要实现的就是点击“下一题”,是不是实现这个效果, 1.类似于图片浏览器这个效果呀, 来看一下,我们这里的这个数据懒加载已经加载起来了, 那么同时,我们界面上这些控件也都摆好了, 并且,…

商务会议租车价格受哪些因素影响!

有重要客户来访&#xff1f;要主办一次重要的会议&#xff1f;或者只是一次普通的机场接送。会议商务租车将能够根据您的需求为您提供全方位服务。 会议商务租车的概念 是指为满足企业商务活动的需要而提供的一种汽车服务。它主要包括&#xff1a;根据客户要求提供各种车型&…

黑马程序员提高变成

这里写目录标题函数模板1.2.2 函数模板注意事项1.2.3 函数模板案例调用规则类模板与函数模板区别类模板与继承类模板成员函数类外实现#pragma once类模板与友元案例重新定义【】stl2.2 STL基本概念STL六大组件容器算法迭代器初识vectorvector容器嵌套容器string容器string赋值操…

作为 React 开发者你应该知道的 7 个库

在成为一名全面的React开发人员的过程中&#xff0c;您会遇到无数的库&#xff0c;让您感到茫然和困惑。因此&#xff0c;这里列出了作为React开发人员学习不会出错的 7 个库。1.反应兜风React Joyride是一个库&#xff0c;可帮助您为React应用程序创建演练和导览。它是向现有用…

C++从头再来:知识点速通

1. 关于scanf 1.1 读入数字 scanf 的返回值表示成功输入的变量个数&#xff0c;当输入结束时&#xff0c;scanf将无法再次读取数据&#xff0c;返回0 # include <stdio.h> # include <math.h> # include <time.h># define M 1000000; // compute the max,…

99.【Git】

Git(一)、什么是版本控制1.什么是版本控制2、常见的版本控制工具(二)、版本控制分类1、本地版本控制2、集中版本控制 SVN3、分布式版本控制 Git(三)、Git与SVN的主要区别1、Git历史(四)、Git下载与环境配置1.git下载2、启动Git(五)、常用的Linux命令1.Linux常用命令(六)、Git必…