浅谈Java并发编程中的线程

 更新时间:2023年08月11日 08:45:23   作者:刘婉晴  
这篇文章主要介绍了浅谈Java并发编程中的线程,操作系统运行一个程序,就会创建一个进程,在一个进程里可以创建多个线程,因此线程也叫做轻量级进程,需要的朋友可以参考下

一、线程

线程是操作系统调度的最小单元,多线程同时执行,可以提高程序性能 。

1. 什么是线程

操作系统运行一个程序,就会创建一个进程,在一个进程里可以创建多个线程,因此线程也叫做轻量级进程 。

2. 线程带来了什么好处

现代处理器都是多核的,程序运行过程中能够创建多个线程,而一个线程在一个时刻只能运行在一个处理器核心上,如果一个单线程程序在运行时只能使用一个处理器核心,那么再多的处理器核心加入也无法显著提升该程序的执行效率。

3. 线程基础

(1)优先级 —— setPriority(int)

线程优先级决定线程需要多或者少分配一些处理器资源的线程属性

设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。

程序正确性不能依赖于优先级高低 —— 不同操作系统有不同的处理方式

(2)状态

New 初始态\ Running 运行态 \ Blocked 阻塞态 \Waiting 等待态\ TimeWaiting 超时等待态\ Terminated 终止态

注: Java将操作系统中的运行和就绪两个状态合并称为运行状态

阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在java.concurrent包中Lock接口的线程状态却是等待状态,因为java.concurrent包中Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。

(3)Daemon 线程 —— setDaemon(true)

支持型线程,用作程序中台调度以及支持性工作 当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出

(4)线程的启动和终止

新构造的线程对象是由其parent线程来进行空间分配的,child线程继承了parent是否为Daemon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时还会分配一个唯一的ID来标识这个child线程,至此,一个能够运行的线程对象就初始化好了。

start()方法的含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start()方法的线程

(5)中断

Thread.currentThread().isInterrupted()

  • 中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作;
  • 中断状态是线程的一个标识位,中断操作是一种简便的线程间交互方式,这种交互方式适合用来取消或停止任务
  • 除了中断以外,还可以利用一个boolean变量来控制是否需要停止任务并终止该线程

4. volatile和synchronized关键字

volatile

告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。

synchronized

关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。

本质是对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由synchronized所保护对象的监视器

任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取到该对象的监视器才能进入同步块或者同步方法,而没有获取到监视器(执行该方法)的线程将会被阻塞在同步块和同步方法的入口处,进入BLOCKED状态。

5. 等待通知

等待/通知机制是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。

等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上。

  • 使用wait()、notify()和notifyAll()时需要先对调用对象加锁;
  • 调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列;
  • notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifAll()的线程释放锁之后,等待线程才有机会从wait()返回;
  • notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED;
  • 从wait()方法返回的前提是获得了调用对象的锁。
synchronized(对象) {
       while(条件不满足) {
            对象.wait();
       }
       对应的处理逻辑
}
synchronized(对象) {
       改变条件
       对象.notifyAll();
}

(1)Thread.join()

线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回

join() 当线程终止时,会调用线程自身的notifyAll()方法,会通知所有等待在该线程对象上的线程。

(2)ThreadLocal

以ThreadLocal对象为键、任意对象为值的存储结构,一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。

ThreadLocal 调用耗时统计的功能上,在方法的入口前执行begin()方法,在方法调用后执行end()方法,好处是两个方法的调用不用在一个方法或者类中,比如在AOP(面向方面编程)中,可以在方法调用前的切入点执行begin()方法,而在方法调用后的切入点执行end()方法,这样依旧可以获得方法的执行耗时。

6. 如何实现等待超时模式

场景:调用一个方法时等待一段时间0,如果该方法能够在给定的时间段之内得到结果,那么将结果立刻返回,反之,超时返回默认结果 。

解决:对象加锁 条件循环 处理逻辑 + 设置超时时间段

应用:超时等待模式可以用来实现 数据库连接池 中数据库连接操作超时返回

好处: 保证客户端线程不会一直挂在连接获取的操作上,而是“按时”返回,并告知客户端连接获取出现问题,是系统的一种自我保护机制,针对昂贵资源(比如数据库连接)的获取都应该加以超时限制。

7. 线程池

为什么需要考虑 C/S 架构的程序,如果没有线程池, 我们客户端向服务器发的请求,每个请求创建一个线程,假设我们有 1 万个请求, 那么就需要创建 1 万的线程进行处理。 众所周知,线程的创建和上下文切换是非常消耗资源的。因此, 需要线程池技术解决这个问题 。

线程池好处

  • 消除了频繁创建和消亡线程的系统资源开销
  • 面对过量任务的提交能够平缓的劣化

线程池的默认实现

使用一个线程安全的工作队列连接工作者线程和客户端线程。

线程池种类

newSingleThreadExecutor newFixedThreadPool newCachedThreadPool : 提高执行许多短暂的异步任务的程序的性能

线程池应用:

简单的 Web 服务器 常用的Java Web服务器,如Tomcat、Jetty,在其处理请求的过程中都使用到了线程池技术。

因为我们不能一个一个请求的顺序处理,这样的话,估计就没有用户会想用了 。现代的浏览器可以并发发送多个请求, 服务器可以并发处理多个请求 。

线程池中线程数量设置

线程池中线程数量并不是越多越好,具体的数量需要评估每个任务的处理时间,以及当前计算机的处理器能力和数量。使用的线程过少,无法发挥处理器的性能;使用的线程过多,将会增加系统的无故开销,起到相反的作用。

((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

线程池可能出现的问题

  • 使用默认的无界的阻塞队列,任务过多,导致阻塞队列炸了 OOM 使用newFixedThreadPool创建的线程池默认的是无界的阻塞队列,如果任务过多,会导致OOM问题。因此,建议自定义线程池,使用指定长度的阻塞队列
  • 线程池创建线程过多,导致JVM无法创建出更多线程,从而导致OOM 使用newCachedThreadPool创建的线程池时,其对线程数量没有规定,如果某个业务的请求特别多,也会导致OOM (因为 newCachedThreadPool 创建的线程没有规定数量,完全由 JVM 能创建的线程数量而定)
  • 不同业务共享线程池,导致次要逻辑拖主要逻辑
  • 线程池拒绝策略 使用不合理的拒绝策略,可能会导致阻塞问题 。比如使用 DiscardPolicy 或者 DiscardOldestPolicy 策略 并且 被拒绝的任务调用 get() 方法,那么调用线程就会一直被阻塞 (FutureTask的状态大于COMPLETING才会返回,要不然都会一直阻塞等待。),这也和 get() 方法本身的问题有关,其不带超时时间,可能会导致阻塞 。
  • Spring 内置线程池 (使用内置线程池一定要注意其时如何创建销毁线程的)
  • 自定义命名
  • 线程池参数 —— 最佳线程数量,每个都要好好配置,做到心中有数
  • 异常问题 (submit 不会直接抛出异常, execute 可以 ,最好是try…catch捕获 )
  • 忘记关闭

到此这篇关于浅谈Java并发编程中的线程的文章就介绍到这了,更多相关Java中的线程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot实现嵌入式 Servlet容器

    SpringBoot实现嵌入式 Servlet容器

    传统的Spring MVC工程部署时需要将WAR文件放置在servlet容器的文档目录内,而Spring Boot工程使用嵌入式servlet容器省去了这一步骤,本文就来设置一下相关配置,感兴趣的可以了解一下
    2023-12-12
  • Springboot异常日志输出方式

    Springboot异常日志输出方式

    这篇文章主要介绍了Springboot异常日志输出方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • java数据库连接、查询、更新等

    java数据库连接、查询、更新等

    这篇文章主要介绍了java数据库连接、查询、更新等,需要的朋友可以参考下
    2018-05-05
  • 简述JAVA中堆内存与栈内存的区别

    简述JAVA中堆内存与栈内存的区别

    这篇文章主要介绍了JAVA中堆内存与栈内存的区别,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-07-07
  • 完美解决Spring声明式事务不回滚的问题

    完美解决Spring声明式事务不回滚的问题

    下面小编就为大家带来一篇完美解决Spring声明式事务不回滚的问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • Spring Boot集成RabbitMQ以及队列模式操作

    Spring Boot集成RabbitMQ以及队列模式操作

    RabbitMQ是实现AMQP(高级消息队列协议)的消息中间件的一种,下面这篇文章主要给大家介绍了关于Spring Boot集成RabbitMQ以及队列模式操作的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-04-04
  • Spring Security如何使用URL地址进行权限控制

    Spring Security如何使用URL地址进行权限控制

    这篇文章主要介绍了Spring Security如何使用URL地址进行权限控制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • Spring Boot + Kotlin整合MyBatis的方法教程

    Spring Boot + Kotlin整合MyBatis的方法教程

    前几天由于工作需要,便开始学习了kotlin,java基础扎实学起来也还算比较快,对于kotlin这个编程语言自然是比java有趣一些,下面这篇文章主要给大家介绍了关于Spring Boot + Kotlin整合MyBatis的方法教程,需要的朋友可以参考下。
    2018-01-01
  • Java工程使用ffmpeg进行音视频格式转换的实现

    Java工程使用ffmpeg进行音视频格式转换的实现

    FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序,本文主要介绍了Java工程使用ffmpeg进行音视频格式转换的实现
    2024-02-02
  • Springboot JPA如何使用distinct返回对象

    Springboot JPA如何使用distinct返回对象

    这篇文章主要介绍了Springboot JPA如何使用distinct返回对象,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02

最新评论