线程的生命周期,线程有哪些状态

image.png
image.png
image.png

sleep()、wait()、join()、yield()的区别

image.png
image.png
image.png
上述代码执行结果22222 1111

说说你对线程安全的理解

image.png

如何解决线程安全问题?有几种方法

  • synchronized的同步方法 和 同步代码块
  • lock锁

Thread、Runnable的区别

拓展创建多线程的方法
image.png
上面肯定卖出10张,因为new了两个 MyThread 而ticket是类的成员变量,所以一个线程卖5个咯
image.png
image.png

实现多线程的方法有哪些

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口(比Runnable强大,可以又返回值,可以抛异常,支持范型)
  • 使用线程池ExecutorService(常用参数 核心池大小、最大线程数、超时时间….)

synchronized的概念

image.png
JVM指令分析:
monitorenter 互斥入口
monitorexit 互斥正常出口
image.png

synchronized与Lock的对比

image.png
主要相同点:Lock能完成synchronized所实现的所有功能
主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。

说说你对守护线程的理解

image.png弟们 Daemon
image.png
使用java的线程池

ThreadLocal的原理和使用场景

ThreadLocal的主要作用是提供线程的局部变量,访问某个ThreadLocal变量的每个线程都有自己的局部变量,它独立于变量的初始化副本。实际上是Thread类里面维护一个ThreadLocalMap,ThreadLocalMap以该ThreadLocal对象变量为key,去取对应的value的实现过程,达到某个ThreadLocal变量的每个线程都有自己的局部值(每一线程维护变量的副本),线程间独立变化独立维护,下面会分析源码来详细介绍ThreadLocal的实现原理。
image.png
image.png
image.pngimage.png

ThreadLocal内存泄漏原因,如何避免

image.png
image.png
image.png
Thread Ref->Threead->ThreadLocalMap->Entry->value这样一条强引用,而这个value永远不会被访问到了,所以存在内存泄漏

另外一种说法
image.png

并行、并发、串行

image.png
image.png

并发的三大特性

  • 原子性

不能在中间暂停然后再调度,要么都执行完,要么都不执行,整个事件是一个最小单位。
image.png
image.png

  • 可见性

当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他的线程狗立刻看得到修改的值
可见性就是使用计算机的两个协议:总线Lock和MESI
image.png

  • 有序性

image.png
image.png

Java中的自增是线程安全的吗?如何实现线程安全的自增

  • 不安全 见上题 实际上是4个步骤
  • 考虑原子性,4个要么都执行,要么都不执行

为什么使用线程池?解释参数

image.png

简述线程池处理流程

image.png

线程池中阻塞队列的作用?为什么是先添加队列而不是先创建最大线程

image.png

简述volatile

image.png

volatile和synchronized的区别

volatile synchronized
使用上 只能修饰变量 方法和代码块
对原子性的保证 不能保证原子性 可以保证原子性
对可见性对保证 可以,对变量加了lock 可以,使用monitorEnter和monitorExit
对有序性对保证 可以(禁止指令重排) 可以但代价太大,重量级,并发退化串行
其他 不会引起阻塞 引起阻塞

CAS的缺点

image.png

线程池中线程复用原理

image.png

如何定义线程池参数

image.png

设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少

  1. package com.cheung.JUI;
  2. public class test1 {
  3. private int i;
  4. private synchronized void inc(){
  5. i++;
  6. System.out.println(Thread.currentThread().getName() + "--inc--" + i);
  7. }
  8. private synchronized void dec(){
  9. i--;
  10. System.out.println(Thread.currentThread().getName() + "--dec--" + i);
  11. }
  12. //内部类
  13. class Inc implements Runnable{
  14. @Override
  15. public void run() {
  16. inc();
  17. }
  18. }
  19. //内部类
  20. class Dec implements Runnable{
  21. @Override
  22. public void run() {
  23. dec();
  24. }
  25. }
  26. public static void main(String[] args) {
  27. test1 t1 = new test1();
  28. //内部类的实例化
  29. Inc inc = t1.new Inc();
  30. Dec dec = t1.new Dec();
  31. for(int i = 0; i < 2; i++){
  32. new Thread(inc).start();
  33. new Thread(dec).start();
  34. }
  35. }
  36. }

列举出一种出现死锁的情况?java是否解决了死锁问题?

银行两个账户转账问题。(同时锁两个,他们两个还不能死锁) 对id排序
image.png

补充:用户态和内核态

image.png
概念:

  • 内核态 CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序
  • 用户态 只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺, CPU资源可以被其他程序获取

为什么要有用户态和内核态:

  • 由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级

用户态切换到内核态三种方式:

  • 系统调用
  • 异常
  • 外部设备的中断

java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在户态与核心态之间切换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作。

synchronized会导致争用不到锁的线程进入阻塞状态,所以说它是java语言中一个重量级的同步操纵,被称为重量级锁,为了缓解上述性能问题,JVM从1.5开始,引入了轻量锁与偏向锁,默认启用了自旋锁,他们都属于乐观锁。所以明确java线程切换的代价,是理解java中各种锁的优缺点的基础之一。

补充:AQS的了解

https://blog.csdn.net/oldshaui/article/details/102692646?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161637387816780274131548%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=161637387816780274131548&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_click~default-1-102692646.first_rank_v2_pc_rank_v29&utm_term=aqs
AQS就是一个并发包的基础组件,用来实现各种锁,各种同步组件的。它包含了state变量、加锁线程、等待队列等并发中的核心组件。
image.pngimage.png
image.png

补充 多线程之间的通信

7种方式
https://blog.csdn.net/weixin_43755155/article/details/107224577?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161637901016780255265747%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=161637901016780255265747&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v29-1-107224577.first_rank_v2_pc_rank_v29&utm_term=%E5%A4%9A%E7%BA%BF%E7%A8%8B%E4%B9%8B%E9%97%B4%E7%9A%84%E9%80%9A%E4%BF%A1%E9%9D%A2%E8%AF%95%E9%A2%98

补充 Reentranklock和Synchronized异同

https://blog.csdn.net/qq_40551367/article/details/89414446?utm_source=app&app_version=4.5.3
同:

  • 都是加锁方式同步
  • 都是重入锁
  • 都是阻塞式的同步(用户态-内核态)

不同:

  • 这两种方式最大区别就是对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。(底层minitor enter/exit)
  • 而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。(底层CAS+CLH队列)实现的前提是AQS
  • 需要手动释放,不需要手动释放
  • Syn不可中断,而Reentranklock等待线程可以放弃等待。