Java 多线程面试题

(一)走进Java世界中的线程

1.1进程,线程,任务

进程(process)是程序的运行实例。例如,一个eclipse就是一个进程。

1.1.1进程与程序之间的关系

进程与程序之间的关系就好比播放中的视频和对应的mp4文件的关系。前者是从动态的角度刻画事务,后者是从静态的角度刻画事务。
进程是程序向操作系统申请的资源(内存空间和文件句柄)的基本单位。

1.1.2 线程

线程(threaad)是进程可独立执行的最小单位。例如:实现从服务器下载数据,为了提高下载效率可以使用多个线程,这些线程各自独立地从服务器上下载数据中的一段。

1.1.3

一个进程可以包含多个线程。同一个进程中的所有线程共享该进程中的资源(内存空间和文件句柄)。

1.1.4 进程与线程之间的关系

进程与线程之间的关系,就好比一个营业中的饭店与其正在工作的员工之间的关系。

1.1.5 任务

线程所要完成的工作就叫做任务
线程的任务就是一个相对概念,包括下载文件,压缩文件,解压文件,监视文件等等。

1.2 多线程编程的简介

多线程编程就是以线程为基本抽线单位的一种编程

1.2.1 什么是多线程编程

多线程编程类似于“和尚挑水”的故事,一个和尚挑水喝,两个和尚抬水喝,三个和尚没水喝。
对应到线程中,就是多线程可能会增加单位时间的完成任务量,提交效率,但也是可能降低程序的计算效率的。

1.2.2为什么使用多线程

举例:
1.在一个软件中多功能能在同一时间进行。
2.在服务器中同一时间常常会收到多条http请求,这是就需要用到多线程来同时处理这些线程,而不是一条一条的执行。
3.某系统需要从指定日志文件中读取信息,若数据上万条,而IO操作又是比较慢的,所以我们需要多线去统计信息。

1.3JavaApi线程简介

java标准库类java.lang.Thread 就是java对线程的实现,Thread类或其子类的一个实例就是一个线程

1.3.1

启动一个线程的实质是请求java虚拟机运行相应的线程,而这个线程具体何时能够运行,是由线程调度器决定的。所以,start方法调用结束后,并不意味着相应的线程已经开始运行,这个线程可能稍后才开始运行,也可能永远不被运行

1.3.2 线程的创建方式

Thread 类有两个常用构造器:Thread() 和 Thread(Runnable target)。
对应的java中创建有两种方式。
第一种(第一个构造器):定义Thread 类的子类,在该类子类中覆盖run方法,并且在该方法中实现线程任务处理逻辑;
第二种(第二个构造器):创建 java.lang.Runnable 接口的实例,并且在该实例的run方法中实现任务处理逻辑,然后以该Runnable 接口实例作为构造器的参数直接创建 一个类的实例。

不管用哪种方法,一旦线程中run方法执行结束,相应的线程的运行也就结束了。

1.3.3 run方法结束

run方法结束包括:
1.正常结束
2.代码中抛出异常而终止

运行结束的线程会像其他java对象一样被java虚拟机垃圾回收。

1.3.4 线程属于“一次性用品”

我们不能通过重新调用一个已经运行结束的线程的start方法来使其重新运行,
事实上,start方法也只能被调用一次,多次调用会抛出illegalStateException异常。

1.3.5 创建线程对象与其他类型对象区别

在java平台中,一个线程就是一个对象,对象的创建离不开内存空间的分配,

线程对象与其他对象区别在于:
1.java虚拟机会为每一个线程分配调用栈所需要的内存空间。
2.java平台中,每个线程可能还会有一个内核线程与之对应。

调用栈:用于跟踪java代码(方法)间的调用关系以及java代码对本地代码的调用。

因此:创建一个线程对象的成本高于其他类型对象

1.3.6 应用代码避免直接调用run方法

应用代码避免直接调用run方法,虽然java虚拟机允许我们直接调用run方法,但是这样做是违背线程的初忠的。

1.3.7 线程的属性

编程的属性包括

编号:

类型 long
说明:表示不同线程,但是某个编号的线程运行结束后,该编号可能被后续的线程使用

名称:

类型 String
说明:面向人的一个属性,java不禁止我们将不同的线程名称属性设置为相同的值

线程类别:

类型 boolean
说明:值为true :守护线程 ,false 用户线程 ,默认值与对应的父线程属性相同
该属性必须在相应的线程启动之前设置,否则会抛出illegalStateExcpetion异常。负责执行关键任务的线程适合设置为守护线程

优先级:

类型 priority
说明:java默认定义了1-10的优先级,默认为5,对于具体的线程,优先级与父线程相同。
ps:一般使用默认线程即可,不恰当的优先级会导致严重问题,(线程饥饿)

1.5线程的层次关系

java虚拟机创建的main线程负责执行java程序的入口方法,所以main方法创建的线程都是main方法的子线程,这些代码的又能创建其他线程,就形成了java线程的层次关系。

1.6线程的生命周期

一个线程从创建,启动,运行结束会经历若干状态

1.6.1 线程的状态

  • NEW:已经创建但是还未启动的线程的状态。因为一个线程只能启动一次,所以所有线程只会处于 一次该状态。
  • RUNNABLE:复合状态,包含两个子状态

READY:处于该状态的线程可以被线程调度器进行调度而使用。从而进去running状态。
RUNNING:表示正在执行运行。即相应的run方法正由处理器执行。

执行yield()方法,即讲running状态的线程,转回ready转台

  • BLOCKED:阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
  • WAITING:一个线程执行了某些特定的方法后就会处于这种等待其他线程执行另外一些特定操作的状态。
    能够使状态变成WAITING的方法有:Thread.join(),LockSupport.park(object)
    能够使WAITING变为Runnbale的方法有:Object.notfy()/notifyAll()和LockSupport.unpark(Object)。
  • TIMED_WAITING:该状态和WAITING类似,差别在于有时间限制的等待状态。
  • TERMINATED:线程执行结束的状态。和new一样只能执行一次。

1.7 线程的监视

线程进行监视的主要途径是获取并且查看程序的线程转储。一个程序的线程转储包含了获取这个线程转储的那一刻该线程的信息。

(二)多线程编程的目标与挑战

2.1 串行,并发,并行

假设我们这里有三件事要完成,做饭(5分钟准备+10分钟等待),做菜(2分钟准备+8分钟等待),洗衣服(10分钟准备+0分钟等待)

串行:

先做饭,饭做好了,在做菜,做菜后,在洗衣服。共花费35分钟。只需投入一个人,逐一完成每一件事儿。

并发:

这种方法也只投入一个人,先做饭,在等待的时间去做菜,在等待菜做好的时候,最后再去洗衣服,共花费5+10+2=17分钟,这种方法类似于我们的统筹安排,虽然也是一个人完成,但是节约了接近一般的时间。

并行:

投入三个人,每个人做一件事儿。最后完成时间15分钟。比并发节约了2分钟。

从软件的角度讲,并发就是在一段时间内交替的方式去完成多个任务,并行就是以齐头并进的方式去完成多个任务。

多线程编程的实际就是将任务的处理方式由串行改为并发,即实现并发化,以发挥并后的优势。

2.2 竞态

一个计算结果的正确性与时间有关的现象被称为竞态。

2.2.1 竞态的模式与静态产生的条件

状态变量:类的实例变量,静态变量
共享变量:可以被多个线程共同访问的变量。

竞态的两种模式:
read-modify-write(读-改-写)和check-then-act(检测而后行动)

2.3线程的安全性

一般而言,如果一个类在单线程的环境下能够正常运行,并且在多线程环境下,在其使用方不必为其做任何改变的情况下也能正常运行,那么我们就成其为线程安全。而且,如果一个类是线程安全的,那它就不会出现竞态。

一个线程如果不是安全的,我们就说它在多线程环境下直接使用存在线程安全问题。线程安全问题主要从三个方面来考虑:原子性,可见性,有序性。

原子性

字面意思为不可分割的,对于涉及共享变量访问的操作,若该操作从其执行线程以外的任意线程来看是不可分割的,那么我们就说该操作为原子操作,则该操作具有原子性。

可见性

一个线程对某一个共享变量进行更新后,后续访问该变量的线程可能无法立刻读取到这个更新的结果,也可能永远也无法读取到这个更新的结果,这就是线程安全问题的另外一个表现形式:可见性。

若后续变量能读取到这个更新的结果,那么我们就称它为可见的,反之,称为不可见的。

2.3.1

硬件和软件的因素都可能导致可见性问题

有序性

在什么情况下一个处理器上运行的一个线程所执行的内存访问操作在另一个处理器上运行的其他线程来看是乱序的。

2.7 上下文切换

在某种程度上可以被看作多个线程共享同一个处理器的产物,处理器从执行一个线程转向执行另外一个线程的时候操作系统所需要做的一个动作,被称为上下文切换,由于处理器资源的稀缺,因此,上下文切换可以被看做多线程的必然副产物。它增加了系统的消耗,不利于系统的吞吐量。

2.8线程的活性故障

线程是为任务而生的,理想情况下,我们希望线程一直处于Runnable状态,导致线程出现飞Runnbale状态的因素除了资源限制外,还有程序自身的缺陷,或者,所线程处于Runnbale状态,但是任务一直都得不到进展的现象就被称为线程活性故障。

2.8.1常见的活性故障

Java 多线程面试题_第1张图片
在这里插入图片描述

你可能感兴趣的:(Java 多线程面试题)