1. priority:优先级 Interrupted:中断、打断 yield:让步
  2. syncn 同步
  3. synchronizevt 同步
  4. synchronization:同步
  5. synchronizedadj 同步的
  6. async:异步,同步这个词加上s就是异步

1.ThreadLocal如何理解

Java使用哈希表实现的

1.1 定义

  1. 定义:提供线程局部变量,一个线程局部变量在多线程中,分别有独立的值(副本)

    1.2 场景

  2. 资源持有:太常见了,例如存储用户的信息,后面只要是这个线程操作都可以直接取出来

  3. 线程一致性:暂不清除
  4. 并发计算:将一个大的计算,分解成多个线程来执行,最后只需要同步数据即可
  5. 线程安全:它是多线程情况下的,保证资源安全
  6. https://www.imooc.com/video/21053

    1.3 模型

    image.png

  7. 左图:代表一个进行中含有多个线程

  8. 右图:线程独占数据是由进程分配的,它包含ThreadLocalMap(其实就是哈希表)

内存泄漏

线程拥有资源,只有计算资源,那么为什么每个线程都有自己的ThreadLocal,并且每个线程的ThreadLocal互不影响?
因为ThreadLocal整体代表的是进程的资源,整体像一个HashMap,用户使用的时候感觉是属于线程的单独资源

2. 同步异步、阻塞非阻塞

2.1 同步异步

  1. 同步异步这里指的是被调用者的行为(服务器的行为,微服务环境下也指被调用服务)而不是请求方的行为
  2. 被调用者是否主动告诉调用者结果;调用者是否等待调用结果
  3. 同步:在没有收到任何结果前,服务端就不返回任何结果,这时候请求可以等待结果那就是阻塞,也可以不等待结果继续执行那就是非阻塞
  4. 异步:调用发出之后,服务端会立刻返回,我收到了你的请求,我会处理的

    2.2 阻塞非阻塞

  5. 线程的角度

    1. 阻塞:是指当前线程不能执行了,需要等待一段时间,或者需要其它线程来唤醒它
    2. 非阻塞:当前线程继续执行
  6. 站在线程发出请求(通常是http请求)的角度
    1. 阻塞:当前线程什么都不执行了,等待返回结果
    2. 非阻塞:继续执行

      2.3 两两组合

  1. 一个类继承Thread类,重写run()方法,在run()方法中实现运行在线程上的代码
  2. Thread类中提供了一个start()方法用于启动新线程,线程启动后,系统会自动调用run()方法

    3.1 实现Runnable接口

  3. Thread类提供了一个构造方法Thread(Runnable target),其中Runnable是一个接口,它只有一个run()方法

  4. 只需要一个类实现Runnable,然后将这个类对象传递Thrad的构造方法中,这样创建的线程将会调用实现了Runnable接口中的run()方法作为运行代码

    3.3 Runnable的优势

  5. 适合多个相同程序代码的线程去处理同一个资源的情况,把线程和程序代码、数据有效分离,很好的体现了面向对象的设计思想

  6. 可以避免java单继承带来的局限性。因为java是单继承,一旦某个类已经继承了另外一个类,那么则无法通过这个方式实现多线程
  7. 事实上大部分程序都会采用实现Runnable接口来创建多线程

    1. // 将实现Runnable接口的对象实例化
    2. CreateThreadRunnable createThreadRunnable = new CreateThreadRunnable();
    3. // 使用Thread类的构造函数,传入一个实现了Runnable接口的对象
    4. Thread thread_one = new Thread(createThreadRunnable);
    5. Thread thread_two = new Thread(createThreadRunnable);
    6. Thread thread_three = new Thread(createThreadRunnable);
    7. // 开启线程,执行run方法中的代码
    8. thread_one.start();
    9. thread_two.start();
    10. thread_three.start();

    实现Callable<?>接口

    3.4 启动main之后Jvm自动创建了哪些线程

  8. signal dispatcher:把操作系统发过来的信号分发给适当的处理程序

  9. finalizer:负责对象的finalizer()方法(垃圾回收)
  10. reference handlerGC、引用相关的线程

    3.5 start方法

  11. 该方法是线程安全的

  12. 使当前线程开始执行, Java 虚拟机调用该线程的run()方法
  13. 结果是两个线程同时运行:当前线程(从调用start()方法返回)和另一个线程(执行其run()方法)
  14. 多次启动一个线程是不合法的。特别是,线程一旦完成执行就可能不会重新启动。

    3.6 后台线程(守护线程)

  15. 如果一个进行只有后台线程在运行,那么这个进程就会结束

  16. 新创建的线程默认都是前台线程
  17. 如果某个线程在启动之前调用setDaemon(true),这个线程就会变成后台线程
  18. thread.isDaemon():判断线程是否为后台线程
  19. 要将某个线程设置为后台线程,必须在该线程启动之前设置,也就是说setDaemon(true)必须要在start()之前,否则会引发IllegalThreadStateException异常

    4. 线程的生命周期及状态转换

    由下图可知,线程只能从阻塞状态进入到就绪状态,不能直接进入到运行状态,也就是说结束阻塞的线程需要重新进入可运行池中,等待系统调用

多线程入门 - 图2

  1. 新建状态:创建一个线程对象后,该线程对象就处于新建状态,此时它不能运行,和其它java对象一样,仅仅是由java虚拟机为其分配了内存,没有表现任何线程的动态特征
  2. 就绪状态
    1. 当线程调用了start()方法后,线程就处于就绪状态(也可以称为可运行状态),处于运行状态的线程可以位于运行池中,此时它只是具备了运行条件,能否获取cpu的使用权,还需要等待系统调用
    2. Tips:这个调用需要详细了解操作系统的原理,比如:如何排队、调度算法、权限之类的
  3. 运行状态
    1. 处于就绪状态的线程获取了cpu的使用权,开始执行run()方法中的线程执行体,则该线程处于运行状态
    2. 当一个线程处于运行状态的时候,它不可能一直处于运行状态,除非它的执行体足够短,瞬间就完成了
    3. 当系统分配的时间执行完成了,系统就会剥夺该线程的cpu使用权,让其它线程执行
  4. 阻塞状态
    1. 一个线程可能在某种情况下,例如进行耗时的输入/输出操作的时候,会放弃cpu的使用权进入阻塞状态,线程进入阻塞状态后,就不能进入队列排队了,当引起阻塞的原因被取消后,才能进入就绪状态
    2. 常见的引起线程由运行进入到阻塞,以及如何从阻塞进入到就绪
      1. 同步锁情况:当线程试图获取某个对象的同步锁,如果该锁被其它对象持有,则当前线程会进入阻塞状态。只有获取了锁,才会从阻塞状态进入到就绪状态
      2. 当线程调用了一个阻塞式的IO方法,就会进入阻塞状态,如果想进入就绪状态,就必须要等到这个阻塞的IO方法返回
      3. 当线程调用了某个对象的wait()方法时,就会进入到阻塞状态,想要进入到就绪状态就需要使用到notify()方法唤醒该线程
      4. 当调用线程的sheep(long millis)方法时,也会使线程进入阻塞状态,等到线程休眠的时间到了,就会自动进入就绪状态
      5. 线程中调用了另外一个线程的join()方法时,会使当前线程(调用者线程)进入阻塞状态,这时候需要等到新加入的线程执行完毕才会结束阻塞状态,进入就绪状态
  5. 死亡状态:现成的run()方法执行完毕,或者线程抛出一个未捕获的异常、错误,线程就进入死亡状态

    5. 线程调度

    5.1 简单的操作系统知识

  6. java虚拟机会按照特定的机制为程序中的每个线程分配cpu的使用权,这种机制就被称作线程的调度

  7. 线程调度的两种模型:
    1. 分时调度模型:每个线程轮流获得cpu的使用权,并且平均分配每个线程占用cpu的时间片段
    2. 抢占式调度模型:优先级高的线程先占用cpu,相同优先级的随机选择一个
    3. 上面的理算简单理解,但是实际更复杂,需要详细学习计算机操作系统
    4. java默认采用抢占式调度模型

      5.2 线程的优先级 setPriority()

      ```makefile 优先级越高,越优先获得cpu的使用权,线程的优先级用1~10之间的整数来表示,数字越大优先级越高

优先级常量

static int max_priority 最高优先级,=10

static int norm_priority 普通优先级,=5

static int min_priority 最低优先级,=1

main线程的优先级默认是”5”

可以通过setPriority(int newPriority)设置线程的优先级,参数可以接受1~10之间的整数,或者三个常量 ```

5.3 线程休眠 sleep()

  1. 如果人为的控制线程,使正在执行的线程暂停,将cpu让给别的线程,可以使用sleep(long millis)
  2. sleep(long millis)方法会声明抛出InterruptedException异常
  3. sleep是一个静态的并且也是一个native的方法,只能控制正在运行的线程休眠(休眠就是进入阻塞状态),而不能控制其它线程休眠,休眠结束后会返回到就绪状态,而不是立即开始运行

    5.4 线程让步 yield()

  4. 线程让步可以通过yield(),方法来实现,该方法和sleep()类似,都可以使线程暂停

  5. 区别是:yield()方法不会阻塞该线程,它只是让系统的调度器重新调度一次
  6. 使用之后,只有与当前线程优先级相同或者更高的线程才能获得机会

    5.5 线程插队 join()

  7. 某个线程调用其它线程的join()方法,调用的线程将被阻塞,

  8. 直到被join()的线程执行完成之后(是整体执行完成,不是单纯的获取一次cpu使用权限),才会回到就绪状态等待执行

    6. 多线程同步

    线程安全问题就是“并发编程-锁相关”的临界条件部分 多线程同步这一块,仅仅是入门了解,具体的还是需要看“并发编程-锁相关”

6.1 线程安全

  1. 多线程并发执行可以提高程序效率,多线程访问同一资源,会引发线程安全问题
  2. 存在线程安全的前提
    1. 存在共享数据
    2. 多个线程并发操作共享数据

      7. 多线程通信

      7.1 wait()方法

      线程安全,使当前线程放弃同步锁,直到其它线程进入次同步锁,并且调用notify()方法,或者notifyAll()方法唤醒该线程为止

      7.2 notify()

      线程安全,唤醒此同步锁上等待的第一个调用wait()方法的线程

      7.3 notifyAll()

      线程安全,唤醒此同步锁上所有调用wait()方法的线程

      8. 面试题

      进程和线程的区别?

      为什么要减少线程切换?

      java线程有哪些状态?如何转换?

      Java线程是内核级线程还是用户级线程