Java核心语法——多线程2(几种创建方式对比、多线程通信、互斥锁、信号量、等待机制)简述
创始人
2025-05-30 18:33:58

文章目录

  • 一、几种多线程的创建方式
    • 1.继承Thread类创建线程类
    • 2.通过Runnable接口创建线程类
    • 3.通过Callable和Future创建线程
    • 4.Excutor线程池
      • 4.1.不推荐,Executors自动创建
      • 4.2.推荐,手动创建ThreadPoolExcutor
        • 4.2.1.核心线程数如何设置?
    • 5.创建线程的三种方式的对比
      • 5.1.采用实现Runnable、Callable接口的方式创见多线程时
      • 5.2.使用继承Thread类的方式创建多线程时
  • 二、多线程之间如何通信?
    • 1.LockSupprot阻塞与唤醒顺序没有要求吗?为什么连续唤醒两次,阻塞两次,最后仍是阻塞?
  • 三、互斥锁、信号量、多线程等待机制

在这里插入图片描述对上篇文章【Java核心语法——多线程】做个补充扩展,👉 https://blog.csdn.net/Today_He/article/details/108621582

一、几种多线程的创建方式

题外话:线程生命周期(新建、就绪、运行、阻塞和死亡)
官方承认的只有两种,Thread和Runnable,其他都是设计模式。

1.继承Thread类创建线程类

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

2.通过Runnable接口创建线程类

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

3.通过Callable和Future创建线程

(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。注释:FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

4.Excutor线程池

线程池提供了一个线程队列,队列中保存所有等待状态的线程,避免创建与销毁额外开销,提高了响应速度。

4.1.不推荐,Executors自动创建

1) FixedThreadPool和SingleThreadPool
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM(内存不足OutOfMemoryError)
2) CachedThreadPool
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM

4.2.推荐,手动创建ThreadPoolExcutor

不允许使用Executors自动创建,而是通过ThreadPoolExecutor的方式,这样处理可以让程序员更加明确线程池的运行规则,规避资源耗尽的风险。

举例:
在这里插入图片描述

4.2.1.核心线程数如何设置?

  • CPU密集型:CPU核数 + 1
  • IO密集型:
     等待时间多:(线程等待时间/CPU时间 + 1) * CPU核数
     等待时间少:CPU核数 * 2
  • 混合型:划分为CPU和IO类型,用不同的线程池

5.创建线程的三种方式的对比

5.1.采用实现Runnable、Callable接口的方式创见多线程时

优势是:
(1)线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
(2)在这种方式下,多个线程可以共享同一个target(无特殊含义,就是Runnable、Callable的变量名,一般叫做target)对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
(1)编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

5.2.使用继承Thread类的方式创建多线程时

优势是:
(1)编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:
(1) 线程类已经继承了Thread类,所以不能再继承其他父类。

二、多线程之间如何通信?

两种模式:对象共享、消息传递
 对象共享:volatile,多个线程监听同一个对象
 消息传递:Object的wait与notify;LockSupprot的park与unpark

注意:
 wait会释放锁;notify不会释放锁,当前逻辑执行完成才会唤醒
 wait最好放在while循环中,阻塞前后都会做条件判断
 unpark、park忽略顺序,都可以唤醒,原理获取凭证(只有一个)

代码实现,参考地址https://blog.csdn.net/zhaomengxia123/article/details/126598141

1.LockSupprot阻塞与唤醒顺序没有要求吗?为什么连续唤醒两次,阻塞两次,最后仍是阻塞?

 因为unpark获取了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。
 因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会得到一个凭证;而调用两次park却需要消费两个凭证,证不够,不能放行。

三、互斥锁、信号量、多线程等待机制

1) 互斥锁:一个资源只能被一个线程访问,ReentranLock使用tryLock()获取锁,失败返回false;unlock()释放锁
2) 信号量:指定个线程同时访问某个资源。相当于计数器,获取这个资源-1. 创建new Semaphore(n),semaphore.acquire()获取许可,semaphore.release()释放许可
3) 等待机制:等待指定个线程达到某些条件后,进行下一步,类似开会。创建new CountDownLatch(n),countDown()计数器减一,await()主线程唤醒。

代码实现,参考地址https://www.php1.cn/detail/XiangJie_java_Zh_07a2d934.html

相关内容

热门资讯

重磅.揭秘“千月互娱辅助脚本”... 您好.千月互娱这款游戏是可以开挂的,确实是有挂的,很多玩家在这款游戏中怀疑是不是有挂,实际上这款游戏...
今日重大通报“微乐陕西挖坑到底... 您好:微乐陕西挖坑这款游戏可以开挂,确实是有挂的,需要软件加微信【8700483】,很多玩家在微乐陕...
今日通报"家乡互动上... 您好:这款游戏可以开挂,确实是有挂的,需要了解加客服微信【34169262】很多玩家在这款游戏中打牌...
分享“妙趣杭州有挂吗果然有挂”... 你好:妙趣杭州这个游戏其实有挂的,确实是有挂的,需要了解十客服微【7676943】1.了解加微【76...
今日重大通报“中至二人麻将有没... 您好:中至二人麻将这款游戏可以开挂,确实是有挂的,需要了解加客服微信【8700483】很多玩家在中至...