一、 概念问答
问:什么是进程?
答: 进程是操作系统进行资源(cpu,内存空间,磁盘IO)分配的最小单位,进程与进程之前相互独立。
问:什么是线程?
答: 线程是指进程中的一个执行流程,一个进程可以运行多个线程。Java中线程是指java.lang.Thread类的一个实例。 线程是CPU调动的最小单位,不能独立于进程存在。启动一个应用程序,必然至少有一个线程。真正执行任务的是线程。(为了合理分配资源,一般情况下每个进程都会有最大线程数。Linux 一个进程中最多可以开1000个线程;Window 一个进程中最多可以开1200个线程。)
问:线程和进程之间的关系?
答: 一个进程可以有多个线程,在同一个进程中,线程可以共享该进程的资源(cpu,内存,磁盘···)。
问:CPU核心数和线程数的关系?
答: 一般情况下,核心数和线程数是一比一的关系,即在同一个时间内,一个内核只能运行一个线程。那么问题来了:我们平时在开发中,经常会开很多个线程,那么这又是怎么回事呢?此时就需要引入另外一个概念:CPU时间片轮转机制,也叫RR调度
问:什么是CPU时间片轮转机制?
答: 将CPU的时间进行切片,每个进程分配一个时间段;然后CPU轮流去执行某个时间段。(一般情况下CPU执行一个指令的时间是0.6纳秒(1s=10亿纳秒);普通人的时间感知为0.1s,所以CPU在切换时间片的时候,我们是无法感知的,因此给我们的错觉就是,每个任务都是连续执行的。这就是为什么我们可以开大于内核数量的线程的原因)
问:什么叫并行?
答: 可以同时运行的任务数,叫并行。
例如:8核CPU,在同一时间点可以同时执行8个任务。
问:什么叫并发量?
答: 在单位时间内,能够执行的任务数。
例如:1核CPU,在1秒钟之内,可以执行100个任务;他的并发量就是100。
问:什么叫多线程开发?
答: 顾名思义,在同一个进程(应用)中同时运行多个线程。使用多线程开发的好处就是,使代码模块化,异步化,简单化。与之相对的坏处就是容易出现线程安全问题。
问:线程安全是什么?安全线程又是什么?
答:
- 线程安全就是线程同步的意思,就是当一个程序对一个线程安全的方法或者语句进行访问的时候,其他的不能再对他进行操作了,必须等到这次访问结束以后才能对这个线程安全的方法进行访问。
- 安全线程就是,如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,说明这些线程就是安全的。
问:什么情况下可能出现线程安全?
答: 当线程与线程之间存在资源竞争的时候,容易出现线程安全问题。
问:如何解决线程安全问题?
答: 使用锁(synchronized,volatile)。
问:如何启动一个线程
答: 启动线程有两种方式:Thread和Runnable(JDK官方明确提出,启动线程的方法只有两种);
Thread用法:
//创建一个线程对象
class Thread1 extends Thread{
public void run() {
//do something
}
}
//调用类
public class Main {
public static void main(String[] args) {
//启动线程
new Thread1().start();
}
}
Runnable用法:
//实现 Runnable接口
public class Thread2 implements Runnable{
public void run() {
//do someThing
}
}
//调用类
public class Main {
public static void main(String[] args) {
//启动线程2
new Thread(new Thread2()).start();
}
}
问:Thread和Runnable的区别是什么?
答: 在Java中,Thread是对线程的抽象;而Runnable是对任务(业务逻辑)的抽象
问:启动线程后,又该如何停止线程呢?
答: 使用suspend(),stop(),interrupt(),isInterrupted()和静态方法interred()
- suspend()挂起:让一个线程进行一次上下文切换,使其从可运行状态,转变为挂起状态。此时不会释放资源也不会释放锁。占着资源和锁进入到睡眠状态,容易引发死锁问题。官方不建议使用,已废弃!
- stop()停止:强制干掉当前线程,可能会造成当前线程资源无法释放,也可能会文件写到一半时突然中止,导致文件不完整。该方法过于暴力,官方不建议使用,已废弃!
- interrupt()中断:对线程发起中断,标识该线程应该被中断了,而非立即中断。非强制性!线程可以忽视该标识。
- isInterrupted()是否中断:检查当前线程是否被中断
- static interrupted():检查当前线程是否被中断,然后将中断标识修改为false;
问:什么是守护线程?
答: 守护线程是指为其他线程服务的线程。在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出。因此,JVM退出时,不必关心守护线程是否已结束。
二、Thread详解(Android API 27版本)
根据上面的知识点我们知道,Thread类是Java中对线程的唯一抽象。那么我们对这个Thread类是否足够了解呢?接下来我们就来仔细探讨一下这个Thread类。
线程是什么,我们已经知道了:线程是进程中的一个执行流程。既然是执行流程,那就有开始和结束;那么线程从开始到结束究竟是如何的呢?我们看下图:
从上图可知,一个完整的线程总共5个状态,分别是:新建、就绪、运行、阻塞、死亡。那么这时候就有个疑问了,为什么start()之后没有直接进入运行状态,而是到了就绪状态呢?这就是因为有CPU时间片轮转机制,当start()之后,线程进入到就绪状态(可运行状态),只有当CPU给该线程分配了运行时间(获得执行权),该线程才能进入到运行状态。
每个状态都对应着一定的操作,接下来我们来看看这些操作是什么意思,有什么作用:
start(): 启动线程
通过观察图片上的源码,我们可以得到如下几个信息:
在716行的注释中This method is not invoked for the main method thread or "system" 这句话表明,方法不会被主线程或者系统线程调用,因此该方法就是专门给我们开发者使用的。
- 在 723行和724行这两行代码表明:一个线程只能启动一次,多次启动会抛状态异常!
- 733行:这一行是真正启动线程的代码。这是一个native方法
join(): 该方法的作用就是同步,它可以使并行执行的线程改为顺序执行。也可以改变线程的执行顺序。比如,当前正在执行main线程,此时a线程调用了join()函数,那么main函数就会放弃当前cpu的控制权,并返回a线程继续执行,直到a线程执行完成,才执行main线程。具体代码如下:
会先执行main线程,main线程执行结束之后,然后执行A线程,A线程执行结束之后,然后执行B线程。
调用join()函数之后:因为在main线程中执行了threadA.join(),所以main线程进入就绪状态,线程A开始执行,此时在线程A中又调用了threadB.join(),所以线程A也进入到了就绪状态,线程B开始执行;当线程B执行完成之后,线程A才能继续执行。线程A执行完成之后,main线程才能继续执行。这样就达到了改变线程的执行顺序的目的。
yield: 让当前(调用yeild())的线程让出CPU的占有权,当前线程进入到就绪 状态。操作系统重新分配CPU使用权(此时虽然让出了CPU的使用权,但是不会释放锁)。此时当前线程依然可能被分配到CPU的使用权,从而重新进入到运行状态。
sleep():
wait():
interrupt(): 对线程发起中断,标识该线程应该被中断了,而非立即中断。非强制性!线程可以忽视该标识。
stop(): 强制干掉当前线程,可能会造成当前线程资源无法释放,也可能会文件写到一半时突然中止,导致文件不完整。该方法过于暴力,官方不建议使用,已废弃!
notify():
notifyAll():
setDaemon(boolean on) 将线程标记为守护线程。使用方法:
Thread t = new Thread();
t.setDaemon(true);//setDaemon(),在start()之前调用
t.start();
三、线程安全
synchrond: synchronied关键字也就是我们所谓的锁,为了保证线程安全,经常会用到它。synchronized锁的内容主要是对象,所以称之为对象锁,也叫内置锁。其主要用法如下:
输出结果为:顺序执行
在同一个实例中,多个线程同时调用funName2()和funName3():
输出结果为:同时执行
输出结果为:顺序执行
volatile: