Java多线程编程技术总结

2023-05-16

目录

1、退出线程的3种方式:

1.1、判断线程是否中断?

1.2、interrupt()

1.3、stop()

1.4、StackTraceElement[] getStackTrace()方法

2、suspend()和resume() 

3、yield()

4、线程优先级

5、守护线程

6、Synchronized实现原理

7、脏读(dirty read)

8、可重入锁

9、同步方法的弊端,同步代码块的优势

10、对象锁

11、类Class的单例性

12、String常量池

13、Volatile

14、线程间通信(wait/notify)

15、线程状态的切换

16、通过管道进行线程通信---字节流

17、Join()

18、ThreadLocal

19、ReentrantLock + Condition

20、公平锁和非公平锁

21、ReentrantReadWriteLock---读写锁

22、Timer + TimerTask

23、单例模式

23.1、饿汉式

23.2、懒汉式

23.3、静态内置类实现单例模式

23.4、序列化与反序列化单例模式

23.5、静态代码块+枚举


1、退出线程的3种方式:

  1. 使用退出标志,使线程正常退出。
  2. 使用stop()强制退出线程, 不推荐使用,它和suspend()、resume()都是被废弃的方法,使用它们可能发生不可预料的结果。
  3. 使用interrupt()终端线程。

1.1、判断线程是否中断?

  • this.interrupted() 获取当前线程是否已经是中断状态,如果是中断状态,会清除中断状态标志,线程会继续执行。
  • this.isInterrupted() 获取当前线程是否已经是中断状态,不清楚中断状态标志。

1.2、interrupt()

使用interrupt()方法并不能立即终止我们的线程,它只会将该线程标记位中断状态,当线程内的任务执行完之后,才会真正地终止。如果我们想要立即终止该线程的话,需要在run()方法中去判断线程是否被终止,来主动终止该线程。

抛异常是彻底终止该线程的一种方法。相对于break,return等终止线程的方法,抛异常的方式,让我们可以按照统一的方式来处理被终止的线程,比如获取最后数据,或者清理一些内存啥的。

当线程处于sleep状态的话(当前线程处于TIME WAITING状态),调用interrupt()方法的话,线程会抛出InterruptedException异常。

反之,如果先调用了interrupt()然后在执行sleep的话,还是会抛出相同的异常。

1.3、stop()

stop()----暴力终止线程,不管线程在执行什么任务,会被立即终止,该方法已经被废弃,因为它会造成业务上的不确定性。而且一些清理性工作无法完成。

当我们调用stop()方法是,会抛出ThreadDeath异常,一般我们不需要显示地去捕捉。

1.4、StackTraceElement[] getStackTrace()方法

获取当前线程堆栈跟踪元素数组,也就是获取当前线程执行的方法栈中有多少个方法。

2、suspend()和resume() 

suspend()可以暂停当前的线程,并且可以通过resume()来重新唤醒该线程,让该线程继续执行下去,不过这俩方法已经处于被废弃的状态。

对于线程的暂停和重启,可以用wait/notify机制来代替被废弃的suspend/resume。

缺点:在同步方法中,我们要谨慎使用suspend()。当线程A执行了同步方法methodA之后,如果线程A执行了suspend()方法,那么除非调用resume()方法,恢复线程A,并让线程A执行完成,否则其他线程都无法获取到methodA的锁。

3、yield()

yield()方法的作用是放弃当前的CPU资源,让其他任务去占用CPU的执行时间,放弃的时间不确定,有可能刚刚放弃,马上又获得了CPU的时间片。

4、线程优先级

线程可以划分等级,优先级高的线程获取的CPU资源更多,也就是让优先级高的线程获取更多的CPU时间片。线程设置优先级的源码如下:setPriority()。

线程优先级具有继承性,例如:在线程A的run方法中,启动了B线程,并且没有单独给线程B设置优先级的话, 那么线程B的priority的值与线程A的值保持一直。

5、守护线程

Java中分两类线程:一种是用户线程,另一种是守护线程,最典型的守护线程就是GC线程。

6、Synchronized实现原理

在字节码指令中,我们的同步方法使用了ACC_SYNCHRONIZED标记flag来标记该方法为同步方法,当我们线程访问该方法的时候,就需要获取该方法所在对象的锁,才能进入。

在字节码指令中,我们的同步代码块,使用了monitorenter和  moniterexit指令来进行同步处理。

7、脏读(dirty read)

脏读,当我们在线程A中读取一个对象的属性 name 时, 被线程B提前修改了name的值,这样获取的结果和预想的不一样的现象叫做脏读。

8、可重入锁

可重入锁就是指:自己可以再次获取到自己的内部锁。

可重入锁具有继承的特性:当子类继承了父类中的synchronized方法 methodA,重写并且添加上synchronized关键字之后,线程A是可以同时同一个子类对象中的methodA方法和methodB方法的。

当线程执行的代码出现异常时,会自动释放锁。

9、同步方法的弊端,同步代码块的优势

在某些情况下,同步方法是存在弊端的, 如下图:getMsg()方法,存在耗时操作,当我们多个线程访问该方法的时候,线程B必须等待线程A执行完3秒的耗时操作,才会获得锁,然后自己再执行3秒的耗时操作。这种情况是效率非常低的同步方法。

这个时候我们就能使用同步代码快的方式来提高getMsg()方法的执行效率。

通过如下的方式,线程A和线程B就能同时去执行耗时操作,然后在输出数据的时候,按照同步的方式输出,这样就能节省下大约一倍的时间。

对于同步代码块synchronized(this),当我们有多个同步method的时候,它们之间也是同步,这样当两个不相关的方法,被多个线程执行的时候,是存在效率低下的问题,这个时候我们就需要使用synchronized(非this对象),使用多个对象锁,来实现异步执行的效果来提升效率。

10、对象锁

同步方法中,获取的锁都是对象的锁,不是方法的锁。

对于同一个对象objectA,当线程A调用objectA的同步方法methodA时,获取了objectA的锁之后,objectA的其他同步方法,也会处于被锁的状态,其他线程是无法访问的。

println()方法也是同步的。

11、类Class的单例性

每一个*.java文件对应的Class类的实例都是一个,在内存中是单例的。

也就是 同一个Class 类,new出多个不同的对象,它们getClass()的对象是一样的。

静态的synchronized方法,锁的对象是当前类对应的Class的单例, 也就是说不管你new出来多少个对象,它们的锁的对象只有一个。

synchronized(Test.class)和上面的静态同步方法的锁的对象是一样。

12、String常量池

在JVM中,是存在String常量池的,所以当我们使用String对象作为 synchronized(String)同步代码块时,如果两个String对象的值是相等的话,那它们的锁对象也是同一个。

13、Volatile

作用:

  1. 可见性:B线程能立马看到A线程修改的数据。
  2. 禁止代码重排序。(可以防止volatile修饰变量 前面的指令不会重排序到后面,但不会阻止volatile前面指令进行重排序。)synchronized也具有相同的功能。
  3. 原子性:在32位系统中,long double类型的赋值行为未实现原子性操作,需要使用volatile。 在64位系统中是实现的。
  4. 使用volatile 修饰的int变量  a  ,进行a++,操作也是非原子性操作。
  5. 对于原子性,我们可以使用Atomic 相关类来实现原子性操作。

14、线程间通信(wait/notify)

对于线程的暂停和重启我们可以通过wait/notify方法来实现,同时这样两个方法也是实现线程间通信的方法。

wait/notify机制也可以实现生产者/消费者模式。

wait()方法是Object类的方法,它的作用就是使当前线程进入等待的状态(WAITING),并且释放锁,等待其他线程的通知或者被中断为止。该方法必须在同步方法,或者同步代码块中。

执行了wait()方法的线程会按照队列的方式按顺序存储起来,被重新唤醒也是按照这样的顺序。

notify()也是需要在同步代码块和同步方法中执行,执行notity之后,会唤醒wait队列中的第一个WAITING线程,但是不会立即唤醒,需要当前线程执行完成,从synchronized中退出,释放锁之后,被通知的第一个线程才会重新唤醒,继续执行wait语句后面的语句。

一个notify()只能唤醒一个WAITING状态的线程,除非使用notifyAll()方法。notifyAll()唤醒wait()线程的顺序和notify()唤醒的顺序相反,是以倒序的当时唤醒所有wait的线程。当然唤醒的顺序还取决于JVM。

  • 执行wait()方法后,会立即释放锁;
  • 执行sleep()方法,不会释放锁;
  • 执行notify()方法,不会立即释放锁,需要执行完成后放锁。

wait(long)方法作用:会在long时间内进入TIMED WAITING状态,如果在long时间内被唤醒,则该线程被唤醒,如果时间到了还没被唤醒,则该线程会自动唤醒自己,进行后续的执行。

当然这个过程还需要获取锁,如果没有锁的话,还是会一直处于等待之中。

15、线程状态的切换

进入blocked 阻塞状态的情况:

  1. 线程调用了sleep()方法,主动放弃占用的CPU资源;
  2. 线程调用了阻塞式IO方法,在该方法返回前,该线程会被阻塞,这时,CPU时间片会被分给其他线程。
  3. 线程试图获取一个同步监视器,但是该监视器正被其他线程所持有。
  4. 线程执行wait()后等待通知notify。
  5. 调用了suspend()方法被挂起。

16、通过管道进行线程通信---字节流

Java中提供了各种各样的输入流/输出流,使我们可以很方便地对数据进行操作。

管道流(pipe stream)是一种特殊的流,用于在不同线程间直接传送数据。一个线程发送数据到输出管道,另一个线程从输入管道中读取数据。

Java中管道流:PipedInputStream PipedOutStream PipedReader PipedWriter.

需要注意的是:我们输入和输出流在创建之后,需要建立管道的连接之后,才能进行数据的传送,如下图3中所示。

17、Join()

Join()方法的作用就在于 当我们在线程A中启动了线程B,如果我们想要等待线程B执行完成之后,再继续线程A中的后续操作的话,就需要使用线程B.join()方法。

join()方法具有使线程排队运行的效果,有些类似同步的运行效果,但是Join()方法与synchronized方法区别:join()方法内部使用了wait()方法进行等待,synchronized使用了锁作为同步。

join()方法、wait()方法与interrupt方法相遇都会抛出interruptException异常。

join(long)方法作用:当线程A在等待特定时间之后,如果线程B还没有执行完成,那么线程A会被唤醒,并在获得锁之后,继续执行后续的操作,不管线程B是否执行完成。

join()方法内部使用了wait方法,所以join方法具有释放锁的特性,而sleep没有该特性。

18、ThreadLocal

ThreadLocal可以通过重写initialValue()方法来实现默认值。

InheritableThreadLocal 可以实现值的继承性,就比如在Main线程中通过同一个ThreadLocal set了一个值,然后在线程main中启动了线程A,然后在A通过这个相同的ThreadLocal去取值的时候,是可以取到main线程中设置的值的,但是使用ThreadLocal的话,取到的是null。实现原理:通过Thread中的inheritableThreadLocals 来实现了,线程A也直接复制了main线程中的inheritableThreadLocals的值。

ThreadLocal是每个线程独有的存储数据的数据类,它在Thread中的存在方式如下:

而ThreadLocalMap其实就是利用数组table和Entry对象实现了可以存储键值对的map类。

19、ReentrantLock + Condition

1、ReentrantLock 是另一个可重入锁,它相对于synchronized 具有更多的灵活性,比如:嗅探锁定,多路分支通知 等功能。

2、相对于Synchronized 中的wait/notify 在ReentrantLock中可以通过Condition对象中的await()/signal()实现相同的效果。

3、当Condition对象A执行await之后,必须用A通过signal唤醒,不能用另一个Condition对象B来唤醒。

ReentrantLock的一些自由方法:

  • getHoldCount()----获取当前线程获取该锁的次数,可重入锁的特性。
  • getQueueLength()---返回正在等待该Lock的线程的数量。
  • getWaitQueueLength(Condition c)---获取正在被该Condition对象所await()的线程的数量。

20、公平锁和非公平锁

公平锁:采用先到先得的策略,每次获得锁之前都会检查队列中有没有排队等待的线程,没有才会尝试获得锁,如果有的话就将当前线程追加到队列中。

非公平锁:采用“有机会插队策略”,一个线程在获取锁之前,会先尝试获取,而不是让队列中的线程先获取,只有获取不到的时候才会吧自己追加到队列中。

公平锁与非公平锁可以通过构造函数的参数fair来决定,true---公平锁; false---非公平锁。

21、ReentrantReadWriteLock---读写锁

ReentrantLock可以实现任何代码块的同步,但是它有一个缺点,对于读写操作来说具有效率低下的可能性。

ReentrantReadWriteLock锁可以实现read 和 write 的分开来锁定,它的锁的规则如下:

  1. 读与读之间是异步的;
  2. 读和写之间是同步的;
  3. 写与写操作也是同步的。

22、Timer + TimerTask

Timer定时器的作用可以帮助我们在制定的时间内执行任务,但是执行该任务需要注意:

如下图:当我们在3秒后执行了输出“Hello”的操作之后,我们的程序并没有停止。而是一直处于运行状态。原因继续往下看。

这是因为当我们在new Timer()对象的时候,Timer类中会创建一个新的线程:

这个线程会开启一个while (true)死循环,而退出该死循环的条件就是执行任务的队列queue必须为null,但是光queue为null还不行,还必须newTasksMayBeScheduled字段等于false,才能从第二个while死循环中退出来,进入到break语句,而newTasksMayBeScheduled字段的变为false,需要执行 Timer 的cancel操作才能完成,所以我们在执行完所需要的任务之后,需要cancel掉该Timer,否则会造成内存泄漏。

需要值得注意的是:

  1. 当我们在任务执行时间未到的就执行了cancel()操作的话,那么我们的任务也将会被舍弃,不再会被执行。
  2. Timer的cancel()会取消Timer队列中的所有任务,TimerTask的cancel()只会取消自己本身。
  3. cancel有时候取消任务会失败,这是因为它没争抢到queue 的锁。

23、单例模式

23.1、饿汉式

饿汉式采用了静态加载的方式,让我们在使用该类的时候就初始化了该对象,并且能实现快速的调用。

缺点:没实现同步,该类中存在其他变量的话,会出现同步问题。

23.2、懒汉式

懒汉式的实现思想就是在我们去获取该实例的时候再去new出来。

这里需要特别注意两点:

  1. 红色框内利用TestObject1.class 的锁来实现同步,这是因为一个类不管有几个对象,它的class只有一个。
  2. 绿色框内使用volatile关键字可以让其他线程对objct1实现可见性,同时防止代码的重排序。这是因为在new TestObject1()的时候经历了:

        1、memory=allocate()        分配对象的内存空间

        2、ctorInstance(memory)   初始化对象

        3、object1=memory;           设置object1指向刚分配的内存地址。

     这三个过程很有可能被重排序,如果不实用volatile的话。

23.3、静态内置类实现单例模式

23.4、序列化与反序列化单例模式

将单例对象进行序列化,然后使用默认的反序列化行为的话,得到对象是多例的,不是单例。这是因为在反序列化过程中,会创建新的对象。

要想解决该问题,就需要使用如下的readResolve方法,在我们反序列化之后,返回原来的静态对象。

23.5、静态代码块+枚举

除了上面的方法实现单例模式以外,我们还能通过 静态代码块和 枚举的方式来实现单例模式。

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java多线程编程技术总结 的相关文章

随机推荐

  • mac使用虚拟机(VirtualBox+centos7)搭建kubernetes(K8S)集群

    文章目录 说明一 环境准备1 配置主机网络2 配置磁盘空间3 安装虚拟机配置网络4 设置Linux环境 三台均需要设置 二 安装docker kubeadm kubelet kubectl 三台均需要设置 1 安装docker环境2 kub
  • .NET6入门:1.Windows开发环境搭建

    作为 NET的最新版本 NET6长期支持版已经发布 xff0c NET6宣称是迄今为止最快的 NET 那当然不能落下时代的潮流 xff0c 就让我们跟着文章进入 NET6的世界吧 1 NET6SDK下载 Download NET Linux
  • 音视频编解码原理(一) 封装格式和编码方式简介

    一 封装格式 要了解音视频编解码原理 xff0c 首先需要知道什么是封装格式 xff1f 所谓封装格式 xff0c 就是将已经编码压缩好的视频轨和音频轨按照一定的格式封装到一个文件中 xff0c 一般情况下 xff0c 不同的封装格式对应不
  • VS调试方法总结(二)

    通过结构化异常定位崩溃程序 程序崩溃时 xff0c 生成文本文件 xff0c 记录崩溃得堆栈信息 直接上代码 已经编译通过 xff0c 拷贝直接可用 h include lt Windows h gt include lt stdarg h
  • QT(C++) + OpenCV + Python库打包发布可执行EXE

    QT xff08 C 43 43 xff09 43 OpenCV 43 Python库打包发布可执行EXE 背景 最近写了一个操作界面 xff0c 不仅用到了OpenCV的函数 xff0c 还调用了一个python脚本 xff0c 所以这里
  • Linux 内存管理 页回收和swap机制

    页高速缓存和页写回机制 页是物理内存或虚拟内存中一组连续的线性地址 xff0c Linux内核以页为单位处理内存 xff0c 页的大小通常是4KB 当一个进程请求一定量的页面时 xff0c 如果有可用的页面 xff0c 内核会直接把这些页面
  • docker 创建 network,出现异常问题及解决

    最近在使用 docker 创建 network 时 xff0c 出现错误 xff1a Error response from daemon could not find plugin bridge in v1 plugin registry
  • 工作进度所占总进度的比例

    如果实现的功能模块全部完成为 或者说 工作进度 xff1a 100 那么 xff1a 1 产品原型全部完成 包括文档的整理 xff1a 15 2 UI设计全部完成 xff1a 10 3 后台全部完成 xff1a 25 4 前台全部完成 xf
  • Docx4J替换内容时,内容换行失败问题解决

    WordprocessingMLPackage wordMLPackage 61 WordprocessingMLPackage load new java io File templatePath MainDocumentPart doc
  • C语言经典例题-用4×4矩阵显示从1到16的所有整数,并计算每行、每列和每条对角线上的和

    编写一个程序 xff0c 要求用户 按任意次序 xff09 输入从1到16的所有整数 xff0c 然后用4 4矩阵的形式将它们显示出来 再计算出每行 每列和每条对角线上的和 include lt stdio h gt int main in
  • java中Map.hashCode()函数说明

    在java中 xff0c Map hashCode 函数是在具有一定工作积累后 xff0c 为了更好的成长不可避免需要研究的内容 首先 xff0c 我们先看下原始代码 xff1a static final int hash Object k
  • 不支持的特性: getMetaData,问题解决

    最近使用springboot 43 mybatis 时遇到 xff1a 不支持的特性 getMetaData 的异常 使用 xff1a 64 Options useGeneratedKeys 61 false 解决的该问题 xff1b 官方
  • jsp四种范围

    page代表是与一个页面相关的对象和属性 request代表是与 Web 客户机发出一个请求相关的对象和属性 session代表是与用于某个 Web 客户机的一个用户体验相关的对象和属性 application代表是与整个 Web 应用程序
  • Kotlin-17-等号比较(== 、===)

    目录 1 Java中的 61 61 2 Java中的 equals 3 两者的区别 3 对于基本数据类型的 61 61 比较 4 Kotlin中的 61 61 与 61 61 61 1 Java中的 61 61 Java中的 61 61 直
  • Kotlin-30-继承多个父类

    目录 1 Java中的继承 2 Kotlin中的继承 1 Java中的继承 Java中的类只能继承一个父类 xff0c 是无法实现继承多个父类 xff0c 但是一个类可以实现多个接口 Java中的接口是无法给函数添加函数体的 abstrac
  • 算法-9-快速排序

    目录 1 描述 2 特点 3 代码实现 3 1 切分函数partition 图示 4 性能 5 快速排序优化 xff08 小数组插入排序 xff09 6 快排优化 xff08 三向切分 xff09 1 描述 快速排序也是基于递归实现的 和归
  • Kotlin进阶-6-重入锁+synchronized+volatile

    目录 1 介绍 2 线程的状态 3 创建线程 4 线程同步 4 1 可重入锁 4 2 不可重入锁的实现 4 3 可重入锁的实现 4 4 Java中的可重入锁 ReentrantLock 4 5 同步方法 synchronized 5 vol
  • Kotlin进阶-7-阻塞队列+线程池

    目录 1 阻塞队列 1 1 常见阻塞场景 2 Java中的阻塞队列 2 1 ArrayBlockingQueue 2 2 LinkedBlockingQueue 2 3 PriorityBlockingQueue 2 4 DelayQueu
  • Kotlin进阶-11-Activity启动后的视图加载分析

    1 介绍 Kotlin进阶 9 setContentView源析 43 Window Activity DecorView关系 Kotlin进阶 10 Activity的启动流程 前面两节分别介绍了Activity的启动流程 xff0c 还
  • Java多线程编程技术总结

    目录 1 退出线程的3种方式 xff1a 1 1 判断线程是否中断 xff1f 1 2 interrupt 1 3 stop 1 4 StackTraceElement getStackTrace 方法 2 suspend 和resume