了解完线程之后,我们来看一下常见的面试题。
sleep和wait的区别
1、sleep 是线程的静态方法,wait 是Object的方法。
2、sleep 方法输入参数为毫秒,时间到了会拥有cpu执行权,wait 可以使用 notify 或者 notifyall 方法唤醒后获得cpu执行权。
3、sleep 不会释放对象锁,wait 会释放对象锁。(第三条是面试官主要考点)
线程有几种状态
这一题考察的是线程的基础知识。可参考java线程学习(一):线程
依据官方给出的API
NEW (至今尚未启动的线程处于这种状态。)
RUNNABLE (正在 Java 虚拟机中执行的线程处于这种状态。)
BLOCKED (受阻塞并等待某个监视器锁的线程处于这种状态。)
WAITING (无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。)
TIMED_WAITING (等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。)
TERMINATED (已退出的线程处于这种状态。)
以上是六中状态。根据各个状态可以这么理解
当创建new Thread 及 Thread的子类的时候,线程进入NEW 新建状态,调用线程的start()方法,线程具有CPU执行权,当该线程分配到CPU时进入 RUNNABLE 运行状态,当CPU没有执行到的时候进入BLOCKED 受阻塞状态,运行结束或者调用stop()方法时候,线程进入TERMINATED 死亡状态。在线程执行时,调用sleep()方法,传入毫秒参数,线程进入TIMED_WAITING休眠状态,时间结束,重新获得CPU执行权,CUP执行则到运行状态,反之受阻塞状态。另外,如果调用Object的wait()方法,线程进入WAITING 等待状态,只有调用Object的notify()或notifyAll()方法才能重新获得CPU执行权,CUP执行则到运行状态,反之受阻塞状态。值得注意的是在给定时间点上,一个线程只能处于一种状态。
实现线程的方法
本这一题考察的是线程的基础知识(如果可以答出各个实现方法的优劣势,我相信面试官一定能对你有更好的印象)
1、继承Thread,重写run方法实现线程。
2、实现Runnable接口,重写run方法实现线程。
3、实现Callable,重写call方法实现线程。
继承Thread,重写run方法实现线程:
public class NewThreadDemo {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
public static class MyThread extends Thread{
@Override
public void run(){
System.out.println("继承Thread,重写run方法实现线程");
}
}
}
实现Runnable接口,重写run方法实现线程:
public class NewThreadDemo {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
public static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable接口,重写run方法实现线程");
}
}
}
实现Callable,重写call方法实现线程:
public class NewThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建callable对象
MyCallable myCallable = new MyCallable();
FutureTask<Integer> result = new FutureTask<>(myCallable);
Thread thread = new Thread(result);
thread.start();
// 取得返回值
Integer num = result.get();
System.out.println(num);
}
public static class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("实现Callable,重写call方法实现线程");
return 1;
}
}
}
同样都是线程,那么这三种方法有什么区别呢。
我们都知道,接口可以多实现,而继承只能单继承,所以实现Runnable接口,避免了继承Thread单继承的局限性。而且实现Runnable接口的方式更加符合面向对象的特点,线程分为两部分,一部分是线程对象,一部分是线程任务。继承Thread类,线程对象跟线程任务耦合在一起,一旦创建了Thread的子类,既是线程对象,又有线程任务,而实现Runnable接口,将线程任务单独分离出来封装成一个对象,类型就是Runnable接口类型,Runnable接口对线程的对象和任务进行了解耦,所以我们通过使用实现Runnable接口的方式使用线程。(java线程学习(一):线程)
实现Runnable接口的方式也满足不了我们日常的需求,比如最大的缺点,没有返回值,不能抛出异常,在JDK1.5 新特性中,给出了Callable这个接口有返回值也可以抛出异常。(java线程学习(三):Callable和Future)
run跟start的区别
这一题问的是最最最最最基础的多线程知识。
1、start()方法是用来启动线程的,真正的实现了多线程运行,不需要等待run方法体执行完毕,可以接着继续执行后面的代码
2、run()方法仅仅只是一个普通方法的调用,程序还说要按照顺序执行,执行结束run方法体才能接着执行后面的代码,所以实际上还是只有一条线程,没有达到多线程的目的
-》调用start()才开启多线程,调用run()只是调用普通方法。
线程池-什么是线程池
以简单理解成容纳多个线程的容器,其中的线程是可以反复使用的,省去了频繁创建对象的操作。
线程池-为什么使用线程池
在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足
线程池-JDK中提供了哪几种线程池的实现
static ExecutorService newFixedThreadPool(int nThreads) | 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。 |
static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) | 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,在需要时使用提供的 ThreadFactory 创建新线程。 |
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) | 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。 |
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) | 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。 |
现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行
这一题考察的是对线程的join()方法。可参考java线程学习(一):线程
public class ThreadJoin {
public static void main(String[] args) throws InterruptedException {
MyRunnable myRunnable = new MyRunnable();
// 创建新线程T1,T2,T3
Thread t1 = new Thread(new MyRunnable(), "t1");
Thread t2 = new Thread(new MyRunnable(), "t2");
Thread t3 = new Thread(new MyRunnable(), "t3");
// 开启新线程 t1
t1.start();
// 调用该线程join()方法
t1.join();
// 开启线程 t2
t2.start();
// 调用该线程join()方法
t2.join();
// 开启线程 t3
t3.start();
// 调用该线程join()方法
t3.join();
}
public static class MyRunnable implements Runnable {
@Override
public void run() {
// 输出1到10
Stream.iterate(1, n -> n + 1).limit(10)
.forEach(n -> System.out.println(Thread.currentThread().getName() + ":" + n));
}
}
}
线程一运行10次,线程二运行50次,然后线程一继续运行10次,线程二运行50次,如此循环10次
这一题考察的是对线程的锁和wait
解法一:使用常规synchronize和wait
public class ThreadDemo {
public static void main(String[] args) {
RuMe r = new RuMe();
new Thread(() -> {
for (int i = 1; i < 11; i++) {
r.th1(i);
}
}).start();
for (int i = 1; i < 11; i++) {
r.th2(i);
}
}
public static class RuMe {
Object o = new Object();
private boolean flag = true;
public void th1(int numA) {
synchronized (o) {
while (!flag) {
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 1; i < 11; i++) {
System.out.println("线程一跑到第: " + i + "次,总共循环" + numA + "次");
}
flag = false;
o.notify();
}
}
public void th2(int numA) {
synchronized (o) {
while (flag) {
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 1; i < 51; i++) {
System.out.println("线程二跑到第: " + i + "次,总共循环" + numA + "次");
}
flag = true;
o.notify();
}
}
}
}
解法二:使用lock和condition
public class ThreadDemo {
public static void main(String[] args) {
RunMethod r = new RunMethod();
new Thread(() -> {
for (int i = 1; i < 11; i++) {
r.threadOne(i);
}
}
).start();
for (int i = 1; i < 11; i++) {
r.mainThread(i);
}
}
public static class RunMethod {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
private boolean flag = true;
public void threadOne(int numA) {
lock.lock();
try {
while (flag) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 1; i < 11; i++) {
System.out.println("线程一跑到第: " + i + "次,总共循环" + numA + "次");
}
flag = false;
condition.signal();
} finally {
lock.unlock();
}
}
public void mainThread(int numA) {
lock.lock();
try {
while (!flag) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 1; i < 51; i++) {
System.out.println("线程二跑到第: " + i + "次,总共循环" + numA + "次");
}
flag = true;
condition.signal();
} finally {
lock.unlock();
}
}
}
}