● 创建线程有几种不同的方式?你喜欢哪一种?为什么?
考察点:JAVA线程
参考回答:
有三种方式可以用来创建线程:
继承Thread类
实现Runnable接口
应用程序可以使用Executor框架来创建线程池
实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。
● 请解释一下Java多线程回调是什么意思?
考察点:JAVA多线程
参考回答:
所谓回调,就是客户程序C调用服务程序S中的某个方法A,然后S又在某个时候反过来调用C中的某个方法B,对于C来说,这个B便叫做回调方法。
● 请列举一下启动线程有哪几种方式,之后再说明一下线程池的种类都有哪些?
考察点:线程池
参考回答:
①启动线程有如下三种方式:
一、继承Thread类创建线程类
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
package com.thread; public class FirstThreadTest extends Thread{ int i = 0 ; //重写run方法,run方法的方法体就是现场执行体 public void run() { for (;i< 100 ;i++){ System.out.println(getName()+ " " +i); } } public static void main(String[] args) { for ( int i = 0 ;i< 100 ;i++) { System.out.println(Thread.currentThread().getName()+ " : " +i); if (i== 20 ) { new FirstThreadTest().start(); new FirstThreadTest().start(); } } } } |
上述代码中Thread.currentThread()方法返回当前正在执行的线程对象。GetName()方法返回调用该方法的线程的名字。
二、通过Runnable接口创建线程类
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start()方法来启动该线程。
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package com.thread; public class RunnableThreadTest implements Runnable { private int i; public void run() { for (i = 0 ;i < 100 ;i++) { System.out.println(Thread.currentThread().getName()+ " " +i); } } public static void main(String[] args) { for ( int i = 0 ;i < 100 ;i++) { System.out.println(Thread.currentThread().getName()+ " " +i); if (i== 20 ) { RunnableThreadTest rtt = new RunnableThreadTest(); new Thread(rtt, "新线程1" ).start(); new Thread(rtt, "新线程2" ).start(); } } } } |
三、通过Callable和Future创建线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
package com.thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class CallableThreadTest implements Callable<Integer> { public static void main(String[] args) { CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); for ( int i = 0 ;i < 100 ;i++) { System.out.println(Thread.currentThread().getName()+ " 的循环变量i的值" +i); if (i== 20 ) { new Thread(ft, "有返回值的线程" ).start(); } } try { System.out.println( "子线程的返回值:" +ft.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } @Override public Integer call() throws Exception { int i = 0 ; for (;i< 100 ;i++) { System.out.println(Thread.currentThread().getName()+ " " +i); } return i; } } |
②线程池的种类有:
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
● 请简要说明一下JAVA中cyclicbarrier和countdownlatch的区别分别是什么?
考察点:线程
参考回答:
CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
● 请说明一下线程池有什么优势?
考察点:线程池
参考回答:
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能执行。
第三:提高线程的可管理性,线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
● 请回答一下Java中有几种线程池?并且详细描述一下线程池的实现过程
考察点:线程池
参考回答:
1、newFixedThreadPool创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
2、newCachedThreadPool创建一个可缓存的线程池。这种类型的线程池特点是:
1).工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
2).如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
3、newSingleThreadExecutor创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,如果这个线程异常结束,会有另一个取代它,保证顺序执行(我觉得这点是它的特色)。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的 。
4、newScheduleThreadPool创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。(这种线程池原理暂还没完全了解透彻)
● 请说明一下Java中都有哪些方式可以启动一个线程?
考察点:线程
参考回答:
1. 继承自Thread类
2. 实现Runnable接口
3.即实现Runnable接口,也继承Thread类,并重写run方法
● 请列举一下创建线程的方法,并简要说明一下在这些方法中哪个方法更好,原因是什么?
考察点:线程
参考回答:
需要从Java.lang.Thread类派生一个新的线程类,重载它的run()方法;
实现Runnalbe接口,重载Runnalbe接口中的run()方法。
实现Runnalbe接口更好,使用实现Runnable接口的方式创建的线程可以处理同一资源,从而实现资源的共享.
● 请简短说明一下你对AQS的理解。
考察点:多线程
参考回答:
AQS其实就是一个可以给我们实现锁的框架
内部实现的关键是:先进先出的队列、state状态
定义了内部类ConditionObject
拥有两种线程模式独占模式和共享模式。
在LOCK包中的相关锁(常用的有ReentrantLock、 ReadWriteLock)都是基于AQS来构建,一般我们叫AQS为同步器。
● 请简述一下线程池的运行流程,使用参数以及方法策略等
考察点:线程池
参考回答:
线程池主要就是指定线程池核心线程数大小,最大线程数,存储的队列,拒绝策略,空闲线程存活时长。当需要任务大于核心线程数时候,就开始把任务往存储任务的队列里,当存储队列满了的话,就开始增加线程池创建的线程数量,如果当线程数量也达到了最大,就开始执行拒绝策略,比如说记录日志,直接丢弃,或者丢弃最老的任务。
● 线程,进程,然后线程创建有很大开销,怎么优化?
考察点:多线程
参考回答:
可以使用线程池。
● 请介绍一下什么是生产者消费者模式?
考察点:线程
参考回答:
生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据。
优点:支持并发、解耦。
● 请简述一下实现多线程同步的方法?
考察点:多线程
参考回答:
可以使用synchronized、lock、volatile和ThreadLocal来实现同步。
● 如何在线程安全的情况下实现一个计数器?
考察点:多线程
参考回答:
可以使用加锁,比如synchronized或者lock。也可以使用Concurrent包下的原子类。
● 多线程中的i++线程安全吗?请简述一下原因?
考察点:多线程
参考回答:
不安全。i++不是原子性操作。i++分为读取i值,对i值加一,再赋值给i++,执行期中任何一步都是有可能被其他线程抢占的。