线程的基本使用

news2025/1/11 7:48:38

线程

使用线程方法

  • 继承Thread类,重写run方法
  • 实现Runnable接口,重写run方法

继承Thread vs 实现Runnable的区别

  1. 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档可以看到Thread类本身就实现了Runnable接口
  2. 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable

线程常用方法

  1. setName:设置线程名称,使之与参数name相同
  2. getName:返回该线程的名称
  3. start:使该线程开始执行;Java虚拟机底层调用该线程的start0方法
  4. run:调用线程对象run方法
  5. setPriority:更改线程的优先级
  6. getPriority:获取线程的优先级
  7. sleep:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
  8. interrupt:中断线程(中断当前操作)
  9. yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
  10. join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务

启动线程

  • 继承Thread类:让重写run方法的类的对象调用start方法,即可启动另一个线程并自动调用重写的run方法
  • 实现Runnable接口:创建Thread类对象,并把实现Runnable接口的类的对象作为Thread类构造参数,然后让Thread对象调用start方法,即可启动另一个线程并自动调用重写的run方法

把实现Runnable接口的类的对象作为Thread类构造参数:Thread底层使用了设计模式(代理模式)

start( ) 方法

start方法调用start0方法,start0方法是本地方法,是JVM调用,底层是C/C++实现
真正实现多线程效果的是start0方法,而不是run
start() 方法调用 start0() 方法后,该线程并不一定会立马执行,只是将线程变成了可运行状态,具体什么时候执行,取决于CPU,由CPU统一调度。

用户线程和守护线程

用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
常见的守护线程:垃圾回收机制

线程状态

https://blog.csdn.net/pange1991/article/details/53860651

image.png

  • NEW:初始状态 尚未启动的线程处于此状态。
  • RUNNABLE:运行时状态 在Java虚拟机中执行的线程处于此状态。
    • Ready
    • Running
  • BLOCKED:阻塞状态 被阻塞等待监视器锁定的线程处于此状态
  • WAITING:等待状态 正在等待另一个线程执行特定动作的线程处于此状态,
  • TIMED_WAITING:超时等待 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
  • TERMINATED:终止状态 已退出的线程处于此状态

线程同步机制

同步和互斥是多线程编程中常用的两种机制,用于解决多个线程之间的协调和资源共享的问题。

  1. 同步(Synchronization): 同步是指控制多个线程之间的执行顺序,以避免彼此之间的冲突和混乱。在多线程环境下,当多个线程同时访问共享资源时,可能会导致数据不一致或错误的结果。通过同步机制,可以确保在某个线程访问共享资源时,其他线程无法同时修改该资源,从而保证数据的一致性。
  2. 互斥(Mutual Exclusion): 互斥是同步的一种具体实现,它通过一种锁的机制来保证在任意时刻只有一个线程可以访问共享资源,其他线程必须等待当前线程释放锁之后才能进入临界区。这样可以避免竞争条件和数据不一致的问题。

简单来说,同步是一种更广泛的概念,用于描述协调多个线程之间的操作顺序和数据访问方式,而互斥是同步的一种实现方式,通过锁机制来确保在同一时刻只有一个线程能够访问共享资源。
在实际的多线程编程中,同步和互斥通常是密切相关的,开发者需要根据具体的需求选择合适的同步和互斥机制,以确保多线程程序的正确性和稳定性。

synchronized 关键字(加互斥锁)

实现线程的同步,在多线程并发执行时保证线程安全。

  1. 同步方法:在方法声明中使用 synchronized 关键字,表示该方法是同步方法。当一个线程进入同步方法时,会自动获取该方法所属对象的锁(也称为内置锁或监视器锁),其他线程必须等待锁被释放后才能进入该方法。
public synchronized void synchronizedMethod() {
    // 同步方法的代码块
}

静态方法的同步

public class abc Runnable {
    public synchronized static void someMethod(){
            //同步代码块   
    }
}
// 锁加在了abc.class上
  1. 同步代码块:在代码块中使用 synchronized 关键字,对指定的对象进行加锁。只有一个线程能够获得该对象的锁,其他线程必须等待锁被释放后才能执行该代码块。
public void someMethod() {
    // 非同步代码块

    synchronized (obj) {
        // 同步代码块
    }

    // 非同步代码块
}

在静态方法的同步代码块中

public class abc Runnable {
    public static void someMethod(){
        // 非同步代码块
        synchronized(abc.class){// 锁加在了abc.class上
            //同步代码块   
        }
        // 非同步代码块
    }
}

注意事项和细节

  1. 同步方法如果没有使用static修饰:默认锁对象为this
  2. 如果方法使用static修饰,默认锁对象:当前类.class

使用 synchronized 可以有效避免多线程并发访问共享资源时出现的数据不一致或冲突的问题。通过对共享资源进行同步,确保每次只有一个线程能够修改该资源,其他线程需要等待。然而,过多地使用 synchronized 可能会导致性能问题,因此在设计多线程程序时需要慎重使用。

死锁

线程死锁是指两个或多个线程在互相等待对方释放资源的情况下,导致它们都无法继续执行的状态。当发生死锁时,每个线程都在等待另一个线程释放资源,从而导致它们之间形成僵局,无法继续执行下去。
典型的死锁情况通常涉及多个线程和多个共享资源,同时涉及到以下四个条件的出现:

  1. 互斥条件:资源只能被一个线程持有,不能同时被多个线程持有。
  2. 请求与保持条件:一个线程持有一个资源的同时,又请求另一个资源。
  3. 不剥夺条件:线程不能强行从另一个线程手中夺取资源,只能通过自愿释放来释放资源。
  4. 循环等待条件:一组线程形成循环等待其他线程持有的资源,即每个线程都在等待下一个线程所持有的资源。

避免死锁的方法包括但不限于以下几点:

  1. 避免使用多个锁:尽量减少使用多个锁,或者确保对多个锁的获取顺序是一致的,从而降低发生死锁的可能性。
  2. 避免持有锁的同时等待其他资源:尽可能减少持有一个锁的同时等待其他资源的情况,可以通过设计更合理的资源分配策略来避免这种情况。
  3. 使用超时机制:在获取锁时设定一个超时时间,超过该时间则放弃当前锁,释放资源,避免长时间等待而导致死锁。
  4. 使用专门的工具进行死锁检测:一些工具可以帮助检测和诊断死锁情况,及时发现并解决潜在的死锁问题。

在编写多线程程序时,需要谨慎设计线程之间的资源竞争关系,避免出现死锁情况,以确保程序的稳定性和可靠性。

锁的释放

锁的释放是通过退出 synchronized 块或方法来实现的。当线程执行完 synchronized 块或方法内的代码,或者通过调用 wait() 方法使线程进入等待状态时,锁会自动释放。
在使用 synchronized 关键字时,锁的获取和释放是由 Java 虚拟机来管理的,开发者无需手动释放锁。当一个线程获取到锁后,在退出 synchronized 块或方法时,锁会被自动释放,其他线程才有机会获取该锁继续执行。
除了自动释放锁外,Java 中还提供了 ReentrantLock 类来进行锁的控制,使用 ReentrantLock 类可以更灵活地控制锁的获取和释放。在使用 ReentrantLock 类时,需要手动调用 lock() 方法获取锁,调用 unlock() 方法释放锁,确保锁的正确释放。
以下是一个简单的示例代码,演示了如何使用 ReentrantLock 类手动释放锁:

public class LockExample {
    private final Lock lock = new ReentrantLock();

    public void performTask() {
        lock.lock();
        try {
            // 执行需要同步的代码块
            System.out.println("Performing task...");
        } finally {
            lock.unlock(); // 明确释放锁
        }
    }

    public static void main(String[] args) {
        LockExample example = new LockExample();
        example.performTask();
    }
}

在上述示例中,通过调用 ReentrantLock 对象的 lock() 方法获取锁,在执行完同步代码块后,通过调用 unlock() 方法明确释放锁,确保资源得到正确释放。这种手动释放锁的方式可以帮助避免死锁等问题,但也需要开发者谨慎处理锁的获取和释放逻辑。

下面操作不会释放锁

  • 线程执行同步代码块或同步方法时,程序调用Thread.sheep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起该线程不会释放锁。

注意:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用

线程的等待和唤醒

https://zhuanlan.zhihu.com/p/453278516

  • wait()方法

首先我们需要了解wait()方法的继承体系,他是在Object对象的基类方法,也就是说所有的对象都拥有wait()方法。一个线程调用了java的wait()方法后,当前线程会被阻塞挂起,这里的调用指的是线程里面调用了加锁对象的wait()方法。
线程被阻塞挂起后是需要唤醒的,下面会讲到唤醒方法,但是也可以调用重载方法wait(long timeout),让线程被阻塞后超过一定时间还没被唤醒而自动唤醒。

  • notify()方法

notify()方法也是继承于Object对象。当某个线程调用了加锁对象的notify方法后,会唤醒之前在该对象进行获取监视器锁时失败而被阻塞的线程,如果有多个线程同时被阻塞,notify()方法只会有一个线程被唤醒,如果需要唤醒全部,则可以调用notifyAll()方法。
所以面试中会被问到wait和notify的作用,可以侧重的知识点是:

  1. 调用wait之前一定是获取到锁的,所以要保证在synchronized块中。
  2. 调用wait后会释放该对象的锁。
  3. 调用notify()方法也要是获取锁, 也要保证在synchronized块中。
  4. 调用notify()方法唤醒一个线程,调用notifyAll()方法唤醒全部被阻塞线程。
  5. 调用notify()或者notifyAll()方法只是唤醒了其他被阻塞的线程,他们有了重新竞争锁的条件,但是当前线程还没有释放锁的,只有调用了wait()方法才会释放锁。

用一个生产者消费者模型来看看wait和notify的用法。生产者消费者模型可以简单理解为有一个容器,当里面没有数据时生产者会往里面添加数据,满了则暂停当前的工作等待消费者消费数据后通知他继续添加。消费者会往里面拿数据,没有了数据则暂停工作等待生产者生产了数据并通知他继续消费。

public static void main(String[] args) {
        Object lock = new Object();
        AtomicInteger counter = new AtomicInteger(0);
        Queue<Integer> queue = new LinkedList<>();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    while (true) {
                        //如果队列没有数据,调用wait()方法,阻塞自己
                        if (queue.isEmpty()) {
                            try {
                                System.out.println("消费者线程阻塞");
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }

                        //如果队列不为空,消费数据;如果线程被生产者通过notifyAll()方法唤醒后,线程重新获取到锁时是从这里执行的
                        System.out.println("消费者线程消费数据: " + queue.poll());
                        //消费者消费后,唤醒可能由于之前队列满了而主动阻塞自己的生产者
                        lock.notifyAll();

                    }

                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    while (true) {
                        //如果队列数据满了,调用wait()方法,阻塞自己
                        if (queue.size() > 10) {
                            System.out.println("生产者线程阻塞");
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }

                        //如果队列没有满,生产数据; 如果被其他线程唤醒,在下次获取到锁的时候生产数据
                        System.out.println("生产者线程生产数据");
                        queue.add(counter.incrementAndGet());

                        //队列有数据了,唤醒之前可能没有数据而主动祖寺啊自己的消费者
                        lock.notifyAll();
                    }

                }
            }
        }).start();

    }

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

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

相关文章

如何在不带备份的情况下恢复 Android 手机照片?

你的 Android 手机出了什么问题&#xff1f; Android 手机上的垃圾文件过多。 您的 Android 手机屏幕黑屏。 您的手机蓝屏死机。 您的设备卡在启动屏幕上。 电话停止工作。 手机一直关机。 ... 您是否遇到过以上情况并将您的 Android 手机恢复出厂设置&#xff1f;如果是这样&…

最后的挣扎 - Qt For Android on HuaWei Mate 60Pro (v4.0.0)

简介 为什么叫最后的挣扎, 其实都知道即将到来的 HarmonyOS NEXT 将抛弃Android支持&#xff0c;纯血HarmonyOS 将上线&#xff0c; 此时再说Qt for android支持Huawei HarmonyOS的设备其实并没有多少意思&#xff0c; 但恐怕在大多数基础软件完成兼容前&#xff0c; 很多人还是…

【Godot 4.2】常见几何图形、网格、刻度线点求取函数及原理总结

概述 本篇为ShapePoints静态函数库的补充和辅助文档。ShapePoints函数库是一个用于生成常见几何图形顶点数据&#xff08;PackedVector2Array&#xff09;的静态函数库。生成的数据可用于_draw和Line2D、Polygon2D等进行绘制和显示。因为不断地持续扩展&#xff0c;ShapePoint…

定位及解决OOM

一、定义 内存溢出&#xff1a;OutOfMemoryError&#xff0c;是指因内存不够&#xff0c;导致操作新对象没有剩余空间。会导致频繁fullgc出现STW从而导致性能下降。 内存泄漏&#xff1a;指用malloc或new申请了一块内存&#xff0c;但是没有通过free或delete将内存释放&#…

cmd常用指令

cmd全称Command Prompt&#xff0c;中文译为命令提示符。 命令提示符是在操作系统中&#xff0c;提示进行命令输入的一种工作提示符。 在不同的操作系统环境下&#xff0c;命令提示符各不相同。 在windows环境下&#xff0c;命令行程序为cmd.exe&#xff0c;是一个32位的命令…

全域电商数据API接口采集突破堆人头的方式的数据采集实现高效稳定大批量的电商商品数据采集

全域电商&#xff0c;是近几年的新趋势&#xff0c;几乎所有商家都在布局全域&#xff0c;追求全域增长。但商家发现&#xff0c;随着投入成本的上涨&#xff0c;利润却没有增加。 其中最为突出的是——商家为保证全域数据的及时更新&#xff0c;通过堆人头的方式完成每日取数…

vulhub中GitLab 任意文件读取漏洞复现(CVE-2016-9086)

GitLab是一款Ruby开发的Git项目管理平台。在8.9版本后添加的“导出、导入项目”功能&#xff0c;因为没有处理好压缩包中的软连接&#xff0c;已登录用户可以利用这个功能读取服务器上的任意文件。 环境运行后&#xff0c;访问http://your-ip:8080即可查看GitLab主页&#xff0…

cartographer学习与使用

记录一下在配置和使用cartographer建图时遇到的各种问题吧。 我的数据 配置文件&#xff1a; my_rslidar.launch <launch> <param name"/use_sim_time" value"false" /> <!--启动建图节点--> <node name"cartographer_n…

设计模式学习笔记 - 设计原则与思想总结:1.总结回顾面向对象、设计原则、编程规范、重构技巧等知识点

概述 对前面的内容的回顾&#xff0c;温故而知新&#xff0c;包括&#xff1a;面向对象、设计原则、规范与重构三个模块的内容。 1.代码质量评判标准 如何评价代码质量的高低&#xff1f; 代码质量的评价具有很强的主观性&#xff0c;描述代码质量的词汇也有很多&#xff0c…

【人工智能】英文学习材料01(每日一句)

&#x1f33b;个人主页&#xff1a;相洋同学 &#x1f947;学习在于行动、总结和坚持&#xff0c;共勉&#xff01; 目录 1.Natural Language Processing&#xff0c;NLP&#xff08;自然语言处理&#xff09; 2.Machine Learing&#xff0c;ML&#xff08;机器学习&#xf…

R语言:microeco:一个用于微生物群落生态学数据挖掘的R包,第四:trans_beta class

trans_beta class&#xff1a;利用trans_beta类可以变换和绘制beta分集的距离矩阵。该类中涉及到beta多样性的分析主要包括排序、群距、聚类和方差分析。我们首先使用PCoA显示排序。 > dataset$cal_betadiv() The result is stored in object$beta_diversity ... > t1 &…

MySQL-- B+ 树

一、InnoDB 是如何存储数据的&#xff1f; InnoDB 的数据是按「数据页」为单位来读写的 数据库的 I/O 操作的最小单位是页&#xff0c;InnoDB 数据页的默认大小是 16KB 单个数据页的结构及作用 多个数据页之间的逻辑连接&#xff08;双向链表&#xff09;&#xff0c;不需要物…

每周一算法:双向深搜

题目描述 达达帮翰翰给女生送礼物&#xff0c;翰翰一共准备了 N N N 个礼物&#xff0c;其中第 i i i 个礼物的重量是 G [ i ] G[i] G[i]。 达达的力气很大&#xff0c;他一次可以搬动重量之和不超过 W W W的任意多个物品。 达达希望一次搬掉尽量重的一些物品&#xff0c;请…

微信小程序的页面制作---常用组件及其属性

微信小程序里的组件就是html里的标签&#xff0c;但其组件都自带UI风格和特定的功能效果 一、常用组件 view&#xff08;视图容器&#xff09;、text&#xff08;文本&#xff09;、button&#xff08;按钮&#xff09;、image&#xff08;图片&#xff09;、form&#xff08…

记录-gitlab-安装在k8s中的一些注意点

一、已有cert-manager的时候如何配置&#xff1f; 1、首先需要创建一个ClusterIssuer apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata:name: letsencrypt-staging spec:acme:# You must replace this email address with your own.# Lets Encrypt will use thi…

npm包、全局数据共享、分包

使用 npm 包 小程序对 npm 的支持与限制 目前&#xff0c;小程序中已经支持使用 npm 安装第三方包&#xff0c;从而来提高小程序的开发效率。但是&#xff0c;在小程序中使用npm 包有如下 3 个限制&#xff1a; ① 不支持依赖于 Node.js 内置库的包 ② 不支持依赖于浏览器内置…

launchctl及其配置、使用、示例

文章目录 launchctl 是什么Unix / Linux类似的工具有什么哪个更常用配置使用常用子命令示例加载一个 launch agent:卸载一个 launch daemon:列出所有已加载的服务:启动一个服务:停止一个服务:禁用一个服务:启用一个服务: 附com.example.myagent.plist内容有趣的例子参考 launch…

ISIS接口MD5 算法认证实验简述

默认情况下&#xff0c;ISIS接口认证通过在ISIS协议数据单元&#xff08;PDU&#xff09;中添加认证字段&#xff0c;例如&#xff1a;MD5 算法&#xff0c;用于验证发送方的身份。 ISIS接口认证防止未经授权的设备加入到网络中&#xff0c;并确保邻居之间的通信是可信的。它可…

数据结构之顺序存储-顺序表的基本操作c/c++(创建、初始化、赋值、插入、删除、查询、替换、输出)

学习参考博文&#xff1a;http://t.csdnimg.cn/Qi8DD 学习总结&#xff0c;同时更正原博主在顺序表中插入元素的错误。 数据结构顺序表——基本代码实现&#xff08;使用工具&#xff1a;VS2022&#xff09;&#xff1a; #define _CRT_SECURE_NO_WARNINGS #include <stdi…

深度强化学习01

Random variable Probability Density Function 期望 Random Sampling 学习视频 这绝对是我看过最好的深度强化学习&#xff01;从入门到实战&#xff0c;7小时内干货不断&#xff01;_哔哩哔哩_bilibili