• 多线程的知识补充


    目录

    一·、线程的等待与唤醒机制

     1、等待方法

     2、唤醒方法

    3、代码演示

    4、阻塞和等待队列

     5、wait和sleep方法的区别

    二、单例模式

    1、单例模式的使用

     2、饿汉式单例模式

     3、懒汉单例模式

    3、懒加载的简单介绍

    (1)懒加载的概念

    (2)懒加载的应用案列

    (3)懒加载和急加载的理解

    (4)单列模式的懒加载 

     三、阻塞队列

    1、阻塞队列的介绍

    2、java标准库中的阻塞队列

    3、阻塞队列的简单实现

    四、定时器

    1、定时器的简单介绍

    2、java中的定时器 

    3、定时器的简单实现

    ​ 五、线程池

    1、线程池的简单介绍 

     2、java中的线程池

    3、线程的常用方法 

    4、ThreadPoolExecutor子类的核心构造方法参数

     六、相关代码


    一·、线程的等待与唤醒机制

    线程得物等待与唤醒,wait与notify方法(都是Object类提供的方法),用于线程的等待和唤醒操作,必须搭配synchronized锁来使用,在多线程并发的场景下,有时候需要某些线程先执行,这些线程执行结束后其他线程接着继续执行。

     1、等待方法

    痴汉方法,死等,线程进入阻塞态(WAITING),直到有其他线程调用notiofy方法唤醒

    等待一段时间,若该时间内线程被唤醒,则继续执行,若超过对应的时间还没有被其他线程唤醒此线程,此线程就不在等待,恢复执行

     2、唤醒方法

    随机唤醒一个正在等待状态的线程

     唤醒所有处在等待状态的线程

    3、代码演示

    1. package Thread.wait_notiofy;
    2. /**
    3. * 线程的等待方法
    4. */
    5. public class WaitDemo1 {
    6. private static class WaitTesk implements Runnable{
    7. private Object lock;
    8. public WaitTesk(Object lock){
    9. this.lock=lock;
    10. }
    11. @Override
    12. public void run() {
    13. synchronized (lock){
    14. System.out.println(Thread.currentThread().getName()+"准备进入等待状态~~");
    15. //此线程在等到lock对象的notify方法唤醒
    16. try {
    17. lock.wait();
    18. } catch (InterruptedException e) {
    19. throw new RuntimeException(e);
    20. }
    21. System.out.println(Thread.currentThread().getName()+"等待结束,线程机组执行~~");
    22. }
    23. }
    24. }
    25. private static class NotifyTask implements Runnable{
    26. private Object lock;
    27. public NotifyTask(Object lock){
    28. this.lock=lock;
    29. }
    30. @Override
    31. public void run() {
    32. synchronized (lock){
    33. System.out.println(Thread.currentThread().getName()+"准备唤醒~~");
    34. //随机唤醒一个处在lock对象上等待的线程
    35. lock.notify();
    36. System.out.println(Thread.currentThread().getName()+"唤醒成功~~");
    37. }
    38. }
    39. }
    40. public static void main(String[] args) throws InterruptedException {
    41. Object lock=new Object();
    42. //创建3个等待线程
    43. Thread t1=new Thread(new WaitTesk(lock),"t1");
    44. Thread t2=new Thread(new WaitTesk(lock),"t2");
    45. Thread t3=new Thread(new WaitTesk(lock),"t3");
    46. //创建一个唤醒线程
    47. Thread notify=new Thread(new NotifyTask(lock),"notify线程");
    48. t1.start();
    49. t2.start();
    50. t3.start();
    51. Thread.sleep(1000);
    52. notify.start();
    53. }
    54. }

    (1)调用wait和notify方法必须要在代码同步块中使用,不然就会直接报错

     (2)调用wait方法的情况 

    3、调用notify方法的情况

    4、阻塞和等待队列

    对于wait和notify方法来说,其实有一个阻塞队列和等待队列

    阻塞队列表示同一时间只有一个线程能获取到锁,其他线程进入阻塞队列

    等待队列表示调用wait(首先此线程要获取到锁,进入等待队列,释放锁)

    调用wait方法的前提是获得锁(synchronized锁),调用wait方法会释放锁,本线程进入等待队列等待被唤醒,当多个线程被唤醒之后不是立即恢复执行,而是再次进入阻塞队列去竞争锁

     5、wait和sleep方法的区别

    比如说:

    ①wait方法是Object类提供的方法,需要搭配synchronized锁来使用,调用wait方法会释放锁,线程进入WAITING状态,等待被其他线程唤醒或者自动超时唤醒,当多个线程被唤醒后需要再次进入阻塞队列再次竞争synchronized锁才能执行

    ② sleep方法是Thread方法提供的方法,调用sleep方法的线程就会进入TIMED_WAITING状态,不释放锁,时间到了就会自动醒来

    二、单例模式

    1、单例模式的使用

     所谓的单例模式就是保证每个类在程序中有且只有一个对象,那么如何控制某个类只有一个对象呢?

    ①如何控制某个类的对象,要创建类的对象,可以通过构造方法来产生对象,当构造方法是public权限时,对于类的外部而言,就可以随意的创建对象,就无法控制对象的个数,此时就应该将构造方法私有化,这样类的外部就彻底没法产生对象了

    ②当构造方法私有化之后,对于类的外部而言就一个对象也没有了,所以要想创建唯一的一个对象,就需要在当前类中只调用一次构造方法

     2、饿汉式单例模式

    这个类一加载(加载到JVM中),这个唯一的对象就产生了

    1. public class singletest {
    2. //唯一的一个对象,必须是静态对象
    3. private static singletest single=new singletest();
    4. private singletest(){}
    5. public static singletest getSingletest() {
    6. return single;
    7. }
    8. }
    9. class singletest1Main{
    10. public static void main(String[] args) {
    11. singletest s1= singletest.getSingletest();
    12. singletest s2= singletest.getSingletest();
    13. singletest s3= singletest.getSingletest();
    14. System.out.println(s1==s2);
    15. System.out.println(s1==s3);
    16. }
    17. }

     3、懒汉单例模式

     系统初始化时,外部不需要这个对象,就先不产生,只有当外部需要时才产生实例化对象,可以加快初始化时间

    1. public class Singletest2 {
    2. private static Singletest2 singletest2;
    3. private Singletest2(){};
    4. public static Singletest2 getSingle() {
    5. if (singletest2==null){
    6. singletest2=new Singletest2();
    7. }
    8. return singletest2;
    9. }
    10. }
    11. class Singletest2Main{
    12. public static void main(String[] args) {
    13. Singletest2 s1=Singletest2.getSingle();
    14. Singletest2 s2=Singletest2.getSingle();
    15. Singletest2 s3=Singletest2.getSingle();
    16. System.out.println(s1==s2);
    17. System.out.println(s1==s3);
    18. }
    19. }

    3、懒加载的简单介绍

    (1)懒加载的概念

    懒加载(Load On Demand)是一种独特而又强大的数据获取方法,它能够在用户滚动页面的时候自动获取更多的数据,而新得到的数据不会影响原有数据的显示,同时最大程度上减少服务器端的资源耗用。

    (2)懒加载的应用案列

        考虑这样一个例子:海尔电器是一个非常大的组织机构,它下有1万多个组织单元。由于组织单元的复杂性,组织单元间也存在着上下级关系。现在的问题是:用户想加入海尔电器的某个组织单元,他该怎么选择到这个组织单元呢?
        很容易想到的一个解决方法是:查询数据库,把海尔电器的所有组织单元放到一个下拉列表中,让用户选择即可。这样的确是解决问题了,但是,测试发现,浏览器在显示组织单元数据时就直接假死了。看来,这样做性能太差,可以不采纳。

        另一个解决方法就是利用懒加载技术。由于组织单元间存在着上下级关系,那么组织单元的排列就可以当作一棵树来处理。在显示数据时只显示父节点,点击父节点时,就能显示父节点下的子节点。用户要选择某个组织单元,只需点击该组织单元的父亲节点就能找到该组织单元。

        可以看出,懒加载提高了系统响应时间,提升了系统性能,非常具有利用价值。

    (3)懒加载和急加载的理解

    FetchType.LAZY:懒加载,加载一个实体时,定义懒加载的属性不会马上从数据库中加载。

    FetchType.EAGER:急加载,加载一个实体时,定义急加载的属性会立即从数据库中加载。

         比方User类有两个属性,name跟address,就像百度知道,登录后用户名是需要显示出来的,此属性用到的几率极大,要马上到数据库查,用急加载;而用户地址大多数情况下不需要显示出来,只有在查看用户资料是才需要显示,需要用了才查数据库,用懒加载就好了。所以,并不是一登录就把用户的所有资料都加载到对象中,于是有了这两种加载模式。

    (4)单列模式的懒加载 

        在系统初始化时,外部不需要这个单列对象,就先不产生该对象,只有当外部需要时才会实例化对象,这里就体现了懒加载的思想。

         对于饿汉单列模式来说,它天生就是线程安全的,因为在系统初始化JVM加载类时就创建了这个唯一的对象,所以在多线程场景下是安全的。而对于懒汉式单例来说,是在第一次调用getSingle方法时才会实例化对象,但如果此时多个线程并发执行,同时调用getSingle方法就会产生问题。

    解决懒汉单例模式的线程安全问题

    ①直接在静态方法上加锁,此时锁的力度太大,不推荐这种方法

     ②

    此时如果只在同步代码块添加锁,分析一下

    当t1进入同步代码块之后,t2和t3就会卡在获取锁的位置,t1产生对象并且释放锁之后,t2和t3还是从获取锁的位置继续执行,此时t2和t3就会再次new对象,就怒视只有唯一的一个对象

     所以,需要在同步代码块中添加一个判断条件,当第一个线程进去时,此时还没有产生对象,就会在此时产生唯一的对象,当其他线程进入之后,由于已经产生了唯一的对象,就不会再产生对象了。在同步代码块中合格添加判断条件就是为了防止其他线程恢复执行后多次创建单例对象。

    ③双重加锁,使用volatile关键字保证单例对象不会被中断

     三、阻塞队列

    1、阻塞队列的介绍

    阻塞队列是一种特殊的队列,遵守“先进先出”的原则

    阻塞队列是 一种线程安全的数据结构,并且具有一下特性:

            ●  当队列满的时候,继续入队列就会阻塞,直到其他线程从队列中取走元素

            ●  当队列空的时候,继续出队列也会阻塞,直到其他线程往队列中插入元素

    阻塞队列得一个典型应用场景就是“生产者消费模型”,这是一种非常典型的开发模型

    2、java标准库中的阻塞队列

    在 Java 标准库中内置了阻塞队列. 如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可.

        ●  BlockingQueue 是一个接口. 真正实现的类是ArrayBlockingQueue 

             和 LinkedBlockingQueue.                      

        ●  put 方法用于阻塞式的入队列, take 用于阻塞式的出队列.

        ●  BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.

    1. import java.util.Random;
    2. import java.util.concurrent.BlockingDeque;
    3. import java.util.concurrent.LinkedBlockingDeque;
    4. /**
    5. * 使用阻塞队列实现一个简单的生产消费模型
    6. */
    7. public class test {
    8. public static void main(String[] args) throws InterruptedException {
    9. BlockingDeque<Integer> blockingDeque=new LinkedBlockingDeque<>();
    10. Thread customer=new Thread(()->{
    11. while (true){
    12. try{
    13. int val=blockingDeque.take();
    14. System.out.println("消费元素:"+val);
    15. Thread.sleep(1000);
    16. }catch (InterruptedException e){
    17. throw new RuntimeException(e);
    18. }
    19. }
    20. },"消费者");
    21. Random random=new Random();
    22. Thread producer=new Thread(()->{
    23. while (true){
    24. try {
    25. int val= random.nextInt(100);
    26. blockingDeque.put(val);
    27. System.out.println("生产者元素:"+val);
    28. Thread.sleep(1000);
    29. } catch (InterruptedException e) {
    30. throw new RuntimeException(e);
    31. }
    32. }
    33. },"生产者");
    34. customer.start();
    35. producer.start();
    36. }
    37. }

    3、阻塞队列的简单实现

    ●通过 "循环队列" 的方式来实现.

    ●使用 synchronized 进行加锁控制.

    ●put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一 定队列就不满了, 因为同时可能是唤醒了多个线程).

    ●take 取出元素的时候, 判定如果队列为空, 就进行 wait. (也是循环 wait)

    1. /**
    2. * 简单实现一个阻塞队列
    3. */
    4. public class BlockingQueue {
    5. private int[] items = new int[1000];
    6. private volatile int size = 0;
    7. private int head = 0;
    8. private int tail = 0;
    9. public void put(int value) throws InterruptedException {
    10. synchronized (this) {
    11. // 此处最好使用 while.
    12. // 否则 notifyAll 的时候, 该线程从 wait 中被唤醒,
    13. // 但是紧接着并未抢占到锁. 当锁被抢占的时候, 可能又已经队列满了
    14. // 就只能继续等待
    15. while (size == items.length) {
    16. wait();
    17. }
    18. items[tail] = value;
    19. tail = (tail + 1) % items.length;
    20. size++;
    21. notifyAll();
    22. }
    23. }
    24. public int take() throws InterruptedException {
    25. int ret = 0;
    26. synchronized (this) {
    27. while (size == 0) {
    28. wait();
    29. }
    30. ret = items[head];
    31. head = (head + 1) % items.length;
    32. size--;
    33. notifyAll();
    34. }
    35. return ret;
    36. }
    37. public synchronized int size() {
    38. return size;
    39. }

    四、定时器

    1、定时器的简单介绍

    定时器也是软件开发中的一个重要组件. 类似于一个 "闹钟". 达到一个设定的时间之后, 就执行某个指定 好的代码.

    2、java中的定时器 

    ●标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule .

    ●schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后 执行 (单位为毫秒).

    1. /**
    2. * java中定时器的简单认识
    3. */
    4. public class TimerTest {
    5. public static void main(String[] args) {
    6. Timer timer=new Timer();
    7. //表示3秒之后执行此任务
    8. timer.schedule(new TimerTask() {
    9. @Override
    10. public void run() {
    11. System.out.println("hello~~~");
    12. }
    13. },3000);
    14. }
    15. }

     第三个参数是在延迟3秒启动任务之后,每隔一秒就会再次执行该任务 

    3、定时器的简单实现

    定时器的构成: 一个带优先级的阻塞队列 为啥要带优先级呢? 因为阻塞队列中的任务都有各自的执行时刻 (delay).

    最先执行的任务一定是 delay 最小的. 使用带 优先级的队列就可以高效的把这个 delay 最小的任务找出来.

    队列中的每个元素是一个 Task 对象.

    Task 中带有一个时间属性, 队首元素就是即将 同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行 

     五、线程池

    1、线程池的简单介绍 

    虽然线程的创建和销毁的开销是比较小的(和进程相比),但是当系统中的线程数量比较多的时候,这个开销就不可忽视了。线程池最大的好处就是减少每次启动、销毁线程的损耗。

     2、java中的线程池

    ●使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.

    ●返回值类型为 ExecutorService

    ●通过 ExecutorService.submit 可以注册一个任务到线程池中.

    1. ExecutorService pool = Executors.newFixedThreadPool(10);
    2. pool.submit(new Runnable() {
    3. @Override
    4. public void run() {
    5. System.out.println("hello");
    6. }
    7. });

    Executors 本质上是 ThreadPoolExecutor 类的封装.  

    ThreadPoolExecutor 提供了更多的可选参数,是描述线程最常用的一个子类, 可以进一步细化线程池行为的设定。

    3、线程的常用方法 

    线程池的父接口是ExecutorService接口,ThreadPoolExecutor是描述线程最常用的一个子类

     Executors 创建线程池的几种方式:

        ●newFixedThreadPool: 创建固定线程数的线程池

        ●newCachedThreadPool: 创建线程数目动态增长的线程 .

        ●newSingleThreadExecutor: 创建只包含单个线程的线程池.

        ●newScheduledThreadPool: 设定延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.

    1. public static void main(String[] args) {
    2. //固定大小的线程池
    3. ExecutorService pool1= Executors.newFixedThreadPool(10);
    4. //数量动态变化的线程池
    5. ExecutorService pool2=Executors.newCachedThreadPool();
    6. //只包含一个线程的单线程池
    7. ExecutorService pool3=Executors.newSingleThreadExecutor();
    8. //定期线程池,可以设置任务的延时启动时间
    9. ExecutorService pool4=Executors.newScheduledThreadPool(10);
    10. }
    1. public class test {
    2. public static void main(String[] args) {
    3. ExecutorService pool1 = Executors.newFixedThreadPool(10);
    4. pool1.submit(new Runnable() {
    5. @Override
    6. public void run() {
    7. for (int i = 0; i < 5; i++) {
    8. System.out.println(Thread.currentThread().getName() + "hello~" + i);
    9. }
    10. }
    11. });
    12. ExecutorService pool2 = Executors.newCachedThreadPool();
    13. pool2.submit(new Runnable() {
    14. @Override
    15. public void run() {
    16. for (int i = 0; i < 5; i++) {
    17. System.out.println(Thread.currentThread().getName() + "hello~" + i);
    18. }
    19. }
    20. });
    21. ExecutorService pool3 = Executors.newSingleThreadExecutor();
    22. pool3.submit(new Runnable() {
    23. @Override
    24. public void run() {
    25. for (int i = 0; i < 5; i++) {
    26. System.out.println(Thread.currentThread().getName() + "hello~" + i);
    27. }
    28. }
    29. });
    30. ScheduledExecutorService pool4=Executors.newScheduledThreadPool(10);
    31. pool4.schedule(new Runnable() {
    32. @Override
    33. public void run() {
    34. System.out.println(Thread.currentThread().getName()+"3秒后执行次任务~~~");
    35. }
    36. },3, TimeUnit.SECONDS);
    37. }
    38. }

    4、ThreadPoolExecutor子类的核心构造方法参数

     六、相关代码

    rocket_class_Grammer: java的语法相关知识的学习笔记 - Gitee.comhttps://gitee.com/ren-xiaoxiong/rocket_class_-grammer/tree/master/src/Thread

  • 相关阅读:
    MySQL官方文档如何查看,MySQL中文文档
    【GPT4账号】ChatGPT/GPT4科研技术应用与AI绘图及论文高效写作
    Windows Update MiniTool 20.12.2016 控制Window更新下载及使用教程
    渗透测试常用书签
    java计算机毕业设计家教管理系统源码+mysql数据库+系统+lw文档+部署
    golang中一种不常见的switch语句写法
    runc hang 导致 Kubernetes 节点 NotReady
    2023年【公路水运工程施工企业安全生产管理人员】新版试题及公路水运工程施工企业安全生产管理人员模拟试题
    CilckHouse创建表
    ES6介绍
  • 原文地址:https://blog.csdn.net/m0_68989458/article/details/125468219