面试题 —— Java线程


1、进程、线程、协程?

总的来说:Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。

进程:

  • 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在 指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的 。
  • 进程就可以视为程序的一个实例,当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。

线程:

  • 在一个进程中可以创建多个线程,一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行

两者对比:

  • 进程间是相互独立的,而线程存在于进程内,同一进程的线程间很可能会相互影响。
  • 从 JVM 内存结构的角度,多个线程共享进程的方法区(JDK1.8之后改为元空间)资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈。
  • 进程间通信比较复杂,而线程间通信相对简单。
    • 同一台计算机的进程通信称为 IPC(Inter-process communication)
    • 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
    • 线程通信相对简单,因为它们共享进程内的内存。例如:是多个线程可以访问同一个共享变量

参考:https://blog.csdn.net/mu_wind/article/details/124616643

协程:

  • 协程(Coroutines)是一种比线程更加轻量级的存在。一个操作系统中可以有多个进程;一个进程可以有多个线程;同理,一个线程可以有多个协程。
  • 产生背景:举例说明,10000个人同时查询账户余额,假设启动10000个线程同时处理每个人的查询操作,线程此时都在执行 IO 操作,会阻塞当前线程。这会产生两个问题:一是系统线程会占用非常多的内存空间,二是过多的线程切换会占用大量的系统时间。
  • 协程的作用:协程运行在线程之上,当一个协程执行完成后,可以选择主动让出,让另一个协程运行在当前线程之上。协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程
  • 最重要的一点:减少线程间的切换
  • 协程的使用:
    • 计算型的操作,利用协程来回切换执行,没有任何意义,来回切换并保存状态 反倒会降低性能。
    • IO型的操作,利用协程在IO等待时间就去切换执行其他任务,当IO操作结束后再自动回调,那么就会大大节省资源并提供性能,从而实现异步编程(不等待任务结束就可以去执行其他代码)。

参考文章:https://cloud.tencent.com/developer/article/1916705
https://zhuanlan.zhihu.com/p/172471249
https://blog.csdn.net/williamgavin/article/details/83062645(用户级线程和核心级线程)

1.1 那么线程有几种状态?

  • 在 Java 语言层面,有六种状态
    在这里插入图片描述

了解 Object.wait()、Object.notify()、Thread.sleep()等方法会使线程从哪个状态到哪个状态

在这里插入图片描述

1.2 yield() 方法有什么作用?和 sleep() 方法有什么区别?

  • 调用 yield 方法时,线程只是让出自己剩余的时间片,并没有被阻塞挂起,而是处于就绪状态(也就是上面所说的 RUNNABLE 状态),线程调度器下一次调度时就有可能调度到当前线程执行。

  • 当线程调用 sleep 方法时调用线程会被阻塞挂起指定的时间(即处于上面所说的 TIMED_WAITING 状态),在这期间线程调度器不会去调度该线程

  • CountdownLatch 和 join 的区别就是,CountdownLatch 能配合线程池使用。

2、死锁、死锁产生的条件、如何避免?

2.1死锁

  • 有多个线程,相互之间占有了对方需要的资源,同时又等待对方释放自己所需要的资源。造成线程无限期的等待下去。
占有
等待
等待
占有
线程1
线程2
资源1
资源2

死锁的示例代码:

public class DeadLockDemo {
    private static Object resource1 = new Object();//资源 1
    private static Object resource2 = new Object();//资源 2

    public static void main(String[] args) {
    	//线程1
        new Thread(() -> {
        	//先占有资源1
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                //等待线程2先占有资源2
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource2");
                }
            }
        }, "线程 1").start();
		
		//线程2
        new Thread(() -> {
        	//先占有资源2
            synchronized (resource2) {
                System.out.println(Thread.currentThread() + "get resource2");
                //等待线程1先占有资源1
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource1");
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + "get resource1");
                }
            }
        }, "线程 2").start();
    }
}

2.2 死锁产生的条件

  • 死锁的产生需要满足以下**四个条件:**结合上面的死锁例子来理解。
    1. 互斥条件:该资源任意一个时刻只由一个线程占用。如果此时还有其他线程请求获取该资源,则请求者只能等待,直至占有资源的线程释放该资源。
    2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
    3. 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
    4. 环路等待条件:指在发生死锁时,必然存在一个线程–资源的环形链,即有一个线程集合{T1,T2,T3…Tn},其中 T1 正在等待一个 T2 占用的资源,… ,Tn 正在等待一个已被 T1 占用的资源。

2.3 如何避免死锁?

  • 只需要破坏死锁产生条件中的任意一个条件即可,但是学过操作系统的应该知道,目前只有“请求与保持条件”、“环路等待条件”是可以被破坏的(参考《Java并发编程之美》)。
  • 那么如何破坏这两个条件呢?—— 资源的有序分配
    • 将 2.1 节中死锁例子中,线程1、线程2 获取资源的顺序都改为 资源1 --> 资源2 的顺序,即可避免死锁的产生。

3、线程间的通信

  1. 使用 synchronized/wait/notify关键字:是使用共享内存的思想,主要利用的是同步机制的原子性,大致意思就是多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行。再通过Object的wait()方法使当前线程进入阻塞状态,再调用Object的notify()方法时阻塞的线程进入就绪状态。
  2. 使用 volatile 关键字:是使用共享内存的思想,主要利用的是同步机制的可见性,大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,其他线程能够感知并执行相应的业务。
  3. 使用ReentrantLock 结合 Condition,原理类似synchronized/wait/notify。
  4. 使用 Thread.join()
  5. 使用 TheadLocal

3.1 哪种通信方式最高效?

3.1 ThreadLocal 什么场景下使用?原理?

3.2 ThreadLocalMap,

>参考文章:https://blog.csdn.net/qq_33807380/article/details/117047222
>《Java并发编程艺术》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值