多线程经典面试题总结
1.什么是线程
- 线程是程序执行的一条路径,一个进程中可以包含多条线程
- 多线程并发执行可以提高程序的效率,可以同时完成多项工作
2.多线程的应用场景
- 红蜘蛛同时共享屏幕给多个电脑
- 迅雷开启多条线程一起下载
- QQ同时和多个人一起视频
- 服务器同时处理多个客户端请求
3.多线程并行和并发的区别
- 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行(需要多核CPU)
- 并发是指两个任务都请求运行,而处理器只能接受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,
使人感觉两个任务都在运行 - 举例:我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行
- 如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊,这就叫并发
4.Java程序运行原理和JVM的启动是多线程的吗
-
A:Java程序运行原理:
- Java命令会启动java虚拟机,启动JVM等于启动了一个应用程序,也就是启动了一个进程,
该进程会自动启动一个"主线程",然后主线程去调用某个类的main方法
- Java命令会启动java虚拟机,启动JVM等于启动了一个应用程序,也就是启动了一个进程,
-
B:JVM的启动是多线程的吗
- JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的
5.多线程程序实现的方式
1.继承Thread
- 定义类继承Thread
- 重写run方法
- 把新线程要做的事写在run方法中
- 创建线程对象
- 开启新线程,内部会自动执行run方法
2.实现Runnable
- 定义类实现Runnable接口
- 实现run方法
- 把新线程要做的事写在run方法中
- 创建自定义的Runnable的子类对象
- 创建Thread对象,传入Runnable
- 调用start()开启新线程,内部会自动调用Runnable的run()方法
- a.继承Thread:由于子类重写了Thread类的run(),当调用start()时,直接找子类的run()方法
- b.实现Runnable:构造函数中传入了Runnable的引用,成员变量记住了它,start()调用run()方法时,
内部判断成员变量Runnable的引用是否为空,不为空编译时看的是Runnable接口的run()方法,
运行时执行的是接口实现类的run()方法
3.实现Callable接口,重写call函数
Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。
6.Thread和Runnable两种方式的区别
- 继承Thread
- 好处是:可以直接使用Thread类中的方法,代码简单
- 弊端是:如果已经有了父类,就不能用这种方法
- 实现Runnable接口
- 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
- 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码相对复杂
7.获取名字和设置名字
- 1.获取名字:
- 通过getName()方法获取线程对象的名字//默认从Thread-0开始命名,下一个为Thread-1
- 2.设置名字:
- 通过构造函数可以传入String类型的名字:,或者通过setName(String)方法可以设置线程对象的名字
获取当前线程的对象
Thread.currentThread(),主线程也可以获取
8.同步代码块
- 1.什么情况下需要同步:
- 当多线程并发,有多段代码同时执行时,我们希望某一段代码执行的过程中,CPU不要切换到其他线程工作,
这时就需要同步, - 如果两段代码是同步的,那么同一时间只能执行一段,在一段代码没执行结束之前,不会执行另外一段代码
- 当多线程并发,有多段代码同时执行时,我们希望某一段代码执行的过程中,CPU不要切换到其他线程工作,
- 2.同步代码块:
- 使用synchronized关键字加上一个锁对象来定义一段代码,这就叫同步代码块,
- 多个同步代码块,如果使用相同的锁对象,那么他们就是同步的,这个要特别注意,
9.同步方法
- 使用synchronized关键字修饰一个方法,该方法中所有的代码都是同步的
- 非静态的同步方法的锁对象是this,静态的同步方法的锁对象是该类的字节码对象
10.线程安全问题
- 多线程并发操作同一数据时,就有可能出现线程安全问题,如出现数据丢失,读写异常的情况
- 使用同步技术可以解决这种问题,把操作数据的代码进行同步,不要多个线程一起操作
用同步代码块时要保证是同一把锁,如果用继承线程类创建四个线程时this对象都不同,而用实现Runnable接口时,虽然
也创建了四个线程类对象,但是传入构造的都是同一个Runnable对象引用即this,所以为了避免这种麻烦,以后用同步代码块时,
锁对象最好用同步代码块所在类的字节码对象,这样可以保证是同一把锁,或者在同步代码块所在类定义一个静态成员变量,如
private static Object obj = new Object();用obj作为锁,但是麻烦,所以,统一用同步代码块所在类的字节码对象做同步锁
但是,如果用的是同步方法就要分情况了,非静态同步方法的锁对象是this,而静态同步方法的锁对象是该类的字节码对象!!!
11.为什么wait方法和notify方法定义在Object这类中
因为锁对象可以是任意对象,Object是所有的类的基类,所以wait方法和notify方法需要定义在Object这个类中
12.sleep方法和wait方法的区别
1.每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。
sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
2.wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
3.sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
4.sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
5.wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
13.线程的六种状态
-
初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
-
运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
-
阻塞(BLOCKED):表示线程阻塞于锁。
-
等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
-
超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
-
终止(TERMINATED):表示该线程已经执行完毕。
*多线程在项目中的应用
很多面试官都会问多线程在项目中的实际应用场景,这个时候我们通常不知道如何回答。因为我们大多数程序员通常都是和业务代码打交道,需要用到多线程的地方我们容器和框架一般都替我们处理好了,所以我们很少有机会接触到多线程编程。但是实际上依然还有一些场景可以使用多线程来处理,这里我列举一下在实际项目中用到的多线程:
1.批量页面静态化在系统中,商品详情页我们使用freemarker来进行页面静态化,每天夜里12点开始要对所有商品页面进行一遍静态化,由于商品数量比较多 如果使用单线程将耗时过长,我们使用一个定长线程池进行批量执行,将任务放在队列中,多个线程同时领取并执行;
2.订单处理(用户下单后可能支付状态不明确,我们后台可以通过多线程去主动核实第三方支付状态,来更新我们系统的订单状态);
3.登录后用户信息处理(用户登录后应该通知各相关系统将用户常用数据进行缓存 以快速响应登录用户);
该问题需要大家在后面实战项目学习中自己思索!!!