day6-day7多线程和并发

这篇具有很好参考价值的文章主要介绍了day6-day7多线程和并发。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

进程

线程与进程的区别
线程是操作系统能够进行运算调度最小的单元,是进程的执行单元。线程是进程内部创建和调度的,共享进程的资源;进程具有独立的地址空间,而线程共享进程的地址空间。进程之间通信需要特殊的机制,如管道、消息队列,而线程之间通信可以通过共享内存直接访问。进程切换开销较大,涉及到地址空间的切换,而线程切换开销较小,因为线程共享地址空间和其他资源。

线程

线程(Thread)是计算机程序中的执行单元,是进程中的一个实体。

线程的创建

创建线程通过
1.继承 Thread 类:创建一个类并继承 Thread 类,重写 run() 方法作为线程的执行逻辑,并通过调用 start() 方法来启动线程。
2.实现 Runnable 接口:创建一个类实现 Runnable 接口,实现 run() 方法,并通过创建 Thread 对象,将 Runnable 对象作为参数传递给 Thread 构造函数,并调用 start() 方法启动线程。
3.实现Callable接口:创建一个类实现Callable接口,重写call()方法

线程的状态和生命周期

  1. 新建状态(New):

    • 当一个线程对象被创建时,它处于新建状态。
    • 在新建状态下,线程对象被分配了内存,但尚未开始执行。
  2. 就绪状态(Runnable):

    • 在就绪状态下,线程已经准备好执行,等待获取 CPU 资源。
    • 处于就绪状态的线程处于线程调度器的控制之下,可能会被选择执行。
  3. 运行状态(Running):

    • 当线程获得 CPU 资源并开始执行时,它进入运行状态。
    • 处于运行状态的线程正在执行其任务逻辑。
  4. 阻塞状态(Blocked):

    • 当线程等待某个条件,例如等待锁的释放,或者等待某个 I/O 操作完成时,它进入阻塞状态。
    • 处于阻塞状态的线程不会占用 CPU 资源,直到条件满足后才能进入就绪状态。
  5. 终止状态(Terminated):

    • 线程进入终止状态表示线程执行结束或者发生了异常。
    • 处于终止状态的线程不会再执行,并且释放其占用的资源。

线程的状态之间存在转换:

  • 线程从新建状态转换到就绪状态,可以通过调用线程对象的 start() 方法启动线程。
  • 线程从就绪状态转换到运行状态,由线程调度器选择执行。
  • 线程从运行状态转换到阻塞状态,可能是因为等待获取锁资源、等待 I/O 完成等。
  • 线程从阻塞状态转换到就绪状态,可能是因为获取到了锁资源、I/O 操作完成等。
  • 线程从运行状态转换到终止状态,可以是线程执行结束或者抛出了未捕获的异常。

线程的常用方法:

sleep():使当前线程休眠指定时间。
join():等待该线程执行完毕。
yield():暂停当前线程的执行,让出 CPU 资源。
interrupt():中断线程的执行。
isAlive():判断线程是否存活。

Java并发编程的基本原理和常用的并发工具类

线程池

线程池可以降低线程生命周期的系统开销问题,加快响应速度;统筹内存和CPU的使用,避免资源使用不当;可以统一管理资源,创建线程的方式有很多种,我了解的有:1、固定大小的线程池,可控制并发的线程数,超出的线程会在工作队列中等待。2、带有缓存的线程池。3、可以执行延迟任务的线程池。

线程池的创建有七个参数分别是:核心线程数,最大线程数,工作队列,线程工厂,存活时间,存活时间单位,拒绝策略
线程池的工作原理:
接收到任务,首先判断一下核心线程是否已满,如果未满则创建一个新的线程执行任务,如果核心线程已满,工作队列未满,将线程存储到工作队列当中,等待核心线程获取执行;如果工作队列已满且线程数小于最大线程数,则创建一个新的线程去处理任务;如果线程数超过了最大线程数,按照四种拒绝策略处理任务,四种拒绝策略分别是:1.提交最早的线程自己去执行该任务,2.默认拒绝策略,会抛出异常,3.直接丢弃任务,没有任何异常抛出,4,丢弃最老任务,其实就是把最早进入的工作丢掉,然后把新任务加入到工作队列当中。

线程安全

如何保证线程安全的
在多线程环境下,确保数据的一致性和避免竞态条件是非常重要的。以下是几种常用的方法和技术来实现这一目标:

  1. 使用同步机制:

    • 使用 synchronized 关键字或锁机制来保证多个线程对共享资源的互斥访问。
    • 同一时间只允许一个线程访问被 synchronized 保护的代码块或方法,从而避免竞态条件。
  2. 使用原子操作类:

    • Java 提供了一些原子操作类(如 AtomicBoolean、AtomicInteger、AtomicReference 等)来实现原子性的读取和修改操作。
    • 原子操作类提供了线程安全的操作方法,可以避免竞态条件。
  3. 使用锁机制:

    • 使用显式锁(如 ReentrantLock)来实现线程之间的互斥访问。
    • 通过显式地加锁和解锁来控制对共享资源的访问,确保同一时间只有一个线程可以访问共享资源。
  4. 使用并发容器:

    • Java 提供了一些线程安全的并发容器(如 ConcurrentHashMap、ConcurrentLinkedQueue 等),可以避免竞态条件。
    • 这些并发容器提供了原子性的操作,并采用了内部的线程安全机制,可以安全地在多线程环境下使用。
  5. 使用线程安全的类:

    • Java 提供了一些线程安全的类(如 StringBuffer、Vector 等),可以避免在多线程环境下的竞态条件问题。
    • 这些类在设计上考虑了线程安全性,并提供了相应的同步机制。
  6. 使用线程间的通信:

    • 使用等待/通知机制(如 wait()、notify()、notifyAll())来实现线程之间的协调和同步。
    • 通过等待和通知的方式,可以确保线程在特定条件下等待或唤醒,避免竞态条件的发生。

1. 锁的类型
锁分为乐观锁、悲观锁、synchronized
乐观锁是每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。
悲观锁每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻止,直到这个锁被释放
2.lock和synchronize的区别synchronized
都是解决线程安全的工具,synchronize是java中的同步关键字;而lock是J.U.C包中提供的接口
synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。

2.lock和synchronize的区别

这个问题我从四个方面来回答
第一个,从功能角度来看,lock和synchronize都是java中用来解决线程安全问题的一个工具,
第二个,从特性来看,首先synchronize是java中的同步关键字;而lock是J.U.C包中提供的接口而这个接口它有很多的实现类其中就包括reentrantLock这样一个重入锁的实现,其次synchronize可以通过两种方式去控制锁的力度,一种是把synchronize关键字修饰在方法层面,另一张种是修饰在代码块上,并且我们可以通过synchronize加锁对象的生命周期来控制锁的作用范围,比如锁对象是静态对象或者类对象那么这个锁就属于全局锁;如果锁对象是普通实例对象,那么这个锁的范围取决于这个实例的生命周期。lock中锁的力度是通过它里面提供的lock( )方法和unlock( )方法来决定的,包裹在两个方法之间的代码是可以保证线程安全的,而锁的作用域取决于lock实例的生命周期。
lock比synchronize的灵活性更高,lock可以自主的去决定什么时候加锁,什么时候释放锁,只需要调用lock()和unlock()方法就可以了。同时lock还提供了非阻塞的竞争锁的方法,叫trylock(),这个方法可以通过返回true/false来告诉当前线程是否已经有其他线程正在使用锁,而synchronize由于是关键字所以他无法去实现非阻塞竞争锁的方法,另外synchronize锁的释放是被动的,就是当synchronize同步代码块执行结束以后或者代码出现异常的时候才会被释放。
最后lock提供了公平锁和非公平锁的机制,公平锁是指线程竞争锁资源时候如果已经有其他线程正在排队或者等待锁释放那么当前竞争锁的线程是无法插队的;而非公平锁就是不管是否有线程在排队等待锁他都会去尝试竞争一次锁,synchronize只提供了一种非公平锁的实现。
第三个,从性能方面来看,synchronize和lock在性能方面相差不大。在实现上会有一定的区别,synchronize引入了偏向锁,轻量级锁,重量级锁,以及锁升级的机制去实现锁的优化,而lock中用到了自旋锁的方式,去实现性能优化,以上就是我对这个问题的理解。

3.说一下死锁
当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。
产生死锁的原因(1) 因为系统资源不足。(2) 进程运行推进的顺序不合适。(3) 资源分配不当等。
怎样防止死锁
1、尽量使用try lock( )防范,设置超时时间,超时则关闭,防止死锁
2、使用安全类concurrent 代替自己手写锁
3、减少锁的使用粒度,避免几个功能共用一把锁
4、减少同步代码块

如何证明Hashmap不是线程安全的?

要证明 HashMap 不是线程安全的,可以通过以下步骤进行测试:

  1. 创建多个线程:创建多个并发线程,每个线程都尝试在同一个 HashMap 上执行并发读写操作。

  2. 并发写入操作:在每个线程中,进行并发写入操作,即向 HashMap 中添加新的键值对。

  3. 并发读取操作:同时在其他线程中进行并发读取操作,即从 HashMap 中获取键对应的值。

  4. 观察结果:观察在多线程环境下,是否出现以下情况:

    • 线程安全性问题:是否出现数据不一致、丢失、重复等问题,即写入的数据没有被正确地读取。
    • 非原子性操作:是否出现读取到不完整或不一致的数据,即读取操作没有在写入操作完成后进行。

如果在测试中出现了以上情况,即可得出结论:HashMap 不是线程安全的。

示例代码如下所示:

import java.util.HashMap;

public class HashMapThreadSafetyTest {
    private static final int NUM_THREADS = 10;
    private static final int NUM_OPERATIONS = 10000;

    private static HashMap<Integer, Integer> hashMap = new HashMap<>();

    public static void main(String[] args) throws InterruptedException {
        // 创建多个并发线程
        Thread[] threads = new Thread[NUM_THREADS];
        for (int i = 0; i < NUM_THREADS; i++) {
            threads[i] = new Thread(new HashMapWriter());
            threads[i].start();
        }

        // 等待所有线程执行完毕
        for (int i = 0; i < NUM_THREADS; i++) {
            threads[i].join();
        }

        // 输出 HashMap 的大小
        System.out.println("HashMap size: " + hashMap.size());
    }

    static class HashMapWriter implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < NUM_OPERATIONS; i++) {
                // 并发写入操作
                hashMap.put(i, i);

                // 并发读取操作
                for (int j = 0; j < NUM_OPERATIONS; j++) {
                    Integer value = hashMap.get(j);
                    if (value == null || !value.equals(j)) {
                        System.out.println("HashMap is not thread-safe");
                        return;
                    }
                }
            }
        }
    }
}

在上述示例中,我们创建了多个并发线程,在每个线程中进行并发写入和读取操作。如果出现了数据不一致的情况,即可得出结论:HashMap 不是线程安全的。

如果创建线程池指定了核心数和最大线程数,这时来了并发请求,线程池具体是如何运作的呢?

当线程池指定了核心线程数和最大线程数,并且有并发请求到达时,线程池的运作如下:

  1. 核心线程处理请求:如果当前线程池中的线程数量小于核心线程数,线程池会创建新的线程来处理请求,直到达到核心线程数。

  2. 任务队列存储请求:如果当前线程池中的线程数量已达到核心线程数,而且任务队列还有剩余容量,线程池会将新的请求任务放入任务队列中等待执行。

  3. 创建新的线程处理请求:如果当前线程池中的线程数量已达到核心线程数,并且任务队列已满,而且当前线程池中的线程数量还未达到最大线程数,线程池会创建新的线程来处理请求。

  4. 拒绝策略处理请求:如果当前线程池中的线程数量已达到最大线程数,并且任务队列已满,而且没有空闲线程可用,线程池会根据指定的拒绝策略来处理新的请求。常见的拒绝策略包括抛出异常、直接丢弃任务、丢弃队列中最早的任务、或者在调用者线程中执行任务等。

  5. 空闲线程回收:如果线程池中的线程数量超过核心线程数,并且有空闲线程,那么超过空闲线程存活时间的空闲线程将被终止,以减少资源消耗。

总结起来,线程池在接收到并发请求时,首先会创建核心线程来处理请求,然后将多余的请求放入任务队列中。如果任务队列已满,线程池会创建新的线程来处理请求,直到达到最大线程数。如果线程池中的线程数量已达到最大线程数并且没有空闲线程可用,根据指定的拒绝策略来处理新的请求。同时,空闲线程会根据存活时间进行回收,以减少资源消耗。这样可以在并发请求的情况下,合理利用线程池中的线程资源,并根据负载情况自动调整线程数量,以提高系统的并发处理能力。

线程池创建的参数?.这些参数在线程创建的时候是如何生效的?

线程池的创建参数可以控制线程池的大小、线程的存活时间、任务队列的容量等。常见的线程池创建参数包括以下几个:

  1. 核心线程数(corePoolSize):线程池中保留的核心线程数,即初始创建的线程数量。这些线程会一直存活,即使没有任务需要执行。

  2. 最大线程数(maximumPoolSize):线程池中允许创建的最大线程数。当任务数量超过核心线程数且任务队列已满时,线程池会创建新的线程,直到达到最大线程数。超过最大线程数的任务将按照线程池的拒绝策略进行处理。

  3. 空闲线程存活时间(keepAliveTime):当线程池中的线程数量超过核心线程数,并且这些线程处于空闲状态时,空闲线程的存活时间。超过存活时间的空闲线程将被终止,以减少资源消耗。

  4. 任务队列(workQueue):用于存放等待执行的任务的队列。线程池中的线程会从任务队列中取出任务并执行。

  5. 线程工厂(threadFactory):用于创建线程的工厂类。可以自定义线程工厂来创建具有自定义属性的线程。

这些参数在线程池创建时通过构造函数或者对应的设置方法进行配置。在线程创建时,这些参数会影响线程池的行为:

  • 核心线程数决定了初始创建的线程数量,这些线程会一直存活,即使没有任务需要执行。
  • 最大线程数限制了线程池能够创建的最大线程数量,避免无限制地创建线程。
  • 空闲线程存活时间决定了空闲线程的存活时间,超过存活时间的空闲线程会被终止以减少资源消耗。
  • 任务队列用于存放等待执行的任务,当线程池中的线程空闲时,它们会从任务队列中获取任务并执行。
  • 线程工厂用于创建线程,可以自定义线程工厂来创建具有自定义属性的线程。

这些参数的设置会影响线程池的行为,例如线程的数量、任务的排队等,以满足应用程序的需求。在运行时,线程池会根据这些参数动态调整线程的创建和销毁,以及任务的执行。文章来源地址https://www.toymoban.com/news/detail-466283.html

到了这里,关于day6-day7多线程和并发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux操作系统】Linux中的进程和线程的概念、区别和实现的细节

    在Linux中,进程和线程是操作系统中两个重要的执行单位。它们是对程序运行时的抽象概念,用于管理和调度计算机资源。 进程是正在运行的程序的实例。它是操作系统分配资源和调度执行的基本单位。每个进程都有自己独立的内存空间,包含代码、数据、堆栈等。进程还包

    2024年02月14日
    浏览(53)
  • 【操作系统——进程与线程(一)】

    2.1.1 进程的概念和特征 进程是指正在执行中的程序的实例。它是计算机系统进行资源分配和调度的基本单位。每个进程都有自己的地址空间、堆栈和数据区域,以及与其他进程通信和同步所需要的操作系统资源。 进程具有以下特点: 独立性:进程是独立的执行实体,拥有自

    2024年02月11日
    浏览(56)
  • 操作系统进程线程(三)—进程状态、同步互斥、锁、死锁

    原子操作的概念 原子操作就是不可中断的一个或者一系列操作。 原子操作如何实现 总线锁定 使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出此信号的时候,其他处理器的请求将被阻塞住,那么该处理器可以独占内存。 缓存锁 总线锁开销比较大,因为把CPU和内

    2024年02月04日
    浏览(52)
  • 操作系统-进程和线程-同步、互斥、死锁

    目录 一、同步互斥  二、互斥的实现方法 2.1软件实现 2.1.1单标志法 2.1.2双标志先检查 2.1.3双标志后检查 2.1.4Petersons算法 2.2硬件实现 2.2.1 TestAndSet指令 2.2.2 Swap指令   三、信号量机制 3.1整形变量  3.2 记录型变量  3.3用信号量实现进程互斥、同步、前驱关系 3.3.1互斥  3.3.2同步

    2024年02月08日
    浏览(52)
  • Java——Java选择题复习(1)(Java基础,进程,多线程,操作系统)

    1. 下面关于程序编译说法正确的是() A. java语言是编译型语言,会把java程序编译成二进制机器指令直接运行 B. java编译出来的目标文件与具体操作系统有关 C. java在运行时才进行翻译指令 D. java编译出来的目标文件,可以运行在任意jvm上 答案:C 题目解析: A. Java是半编译半

    2024年02月03日
    浏览(56)
  • 【操作系统OS】学习笔记:第二章 进程与线程 (上)【哈工大李治军老师】

    基于本人观看学习 哈工大李治军老师主讲的操作系统课程 所做的笔记,仅进行交流分享 特此鸣谢李治军老师,操作系统的神作! 如果本篇笔记帮助到了你,还请点赞 关注 支持一下 ♡𖥦)!! 主页专栏有更多,如有疑问欢迎大家指正讨论,共同进步! 给大家跳段街舞感谢支持

    2024年02月02日
    浏览(59)
  • 【Java基础教程】(四十二)多线程篇 · 上:多进程与多线程、并发与并行的关系,多线程的实现方式、线程流转状态、常用操作方法解析~

    理解进程与线程的区别; 掌握Java 中多线程的两种实现方式及区别; 掌握线程的基本操作方法; 进程是程序的一次动态执行过程,它经历了从代码加载、执行到执行完毕的一个完整过程,这个过程也是进程本身从产生、发展到最终消亡的过程 。多进程操作系统能同时运行多

    2024年02月16日
    浏览(48)
  • 网络编程day6——基于C/S架构封装的线程池

        竞争与同步         同一个进程中的线程共享进程中的绝大多数资源,当它们随意竞争时可能会导致资源被破坏、脏数据、不完整问题         通过一些手段让线程在竞争资源时相互协调、避免出现以上问题,这就称为线程同步     原子操作:         操作过程中不能

    2024年02月09日
    浏览(34)
  • Java并发(一)----进程、线程、并行、并发

    进程 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了

    2023年04月10日
    浏览(65)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包