某一天你在面试时遇到了线程的相干问题。
面试官:“你晓得有哪几种创立线程的形式吗?”
(此时你的心理流动:哈哈小意思这能难住我,忍住冲动伪装淡定道)
你:“嗯,能够通过实现 Runnable 接口和继承 Thread 类来创立线程。”
面试官:“除了这两种还有其余形式吗?”
你:“emmm… 还有吗?”
面试官:“晓得通过实现 Callable 接口与获取 Future 对象来实现吗?”
你:“emmm 不晓得 … 不过当初晓得了嘻嘻”
面试官:“那创立线程池有哪些形式呢?”
你:“能够通过 ThreadPoolExecutor 构造函数或者 Executors 提供的工厂办法来创立”
面试官:“那通过不同的 Executors 工厂办法创立线程池之间有什么区别呢?”
你:“emmm…“
面试官:“那 ThreadPoolExecutor 构造函数中的工作队列和回绝策略别离有哪些呢?”
你:“emmm…“
(此时你的心理流动:QAQ… 不面了,把劳资简历还我!)
目录
1、线程的定义
2、线程的 6 种状态及切换
3、线程的 4 种创立形式
3.1、实现 Runnable 接口
3.2、继承 Thread 类
3.3、通过 Callable、Future
3.4、通过 JUC 外面的线程池
4、几个常见的线程面试题
注释
1、线程的定义
概念:线程是过程中执行运算的最小单位,是过程中的一个实体,是被零碎独立调度和分派的根本单位,线程本人不领有系统资源,只领有一点在运行中必不可少的资源,但它可与同属一个过程的其它线程共享过程所领有的全副资源。一个线程能够创立和吊销另一个线程,同一过程中的多个线程之间能够并发执行。
2、线程 6 种状态及切换
Java 中线程的状态分为 6 种,定义在 Thread 类的 State 枚举中。
public class Thread implements Runnable {
...
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
...
}
NEW:初始状态,创立一个线程对象时就是该状态。
RUNNABLE:运行状态,它蕴含了就绪(READY)和运行中(RUNNING)两种状态。当线程对象创立后,调用该对象的 start() 办法就会进入就绪状态(READY)。该状态的线程位于可运行线程池中,期待被线程调度选中,获取 CPU 的使用权,在取得 CPU 工夫片后会变为运行中状态(RUNNING)。
BLOCKED:阻塞状态,示意线程此时阻塞于锁。
WAITING:期待状态,进入该状态的线程须要期待其余线程做出一些特定动作(告诉或中断)。
TIMED_WAITING:超时期待状态,该状态与 WAITING 的不同点在于它能够在指定的工夫后自行返回。
TERMINATED:终止状态,示意该线程曾经执行完。
留神下图状态之间的切换。
3、线程的四种创立形式
3.1、实现 Runnable 接口
Runnable 接口源码如下,它只有一个 run() 办法。
public interface Runnable {public abstract void run();
}
示例:
public class ThreadDemo implements Runnable {
@Override
public void run() {System.out.println("通过实现 Runnable 接口创立线程");
}
public static void main(String[] args) {ThreadDemo threadDemo = new ThreadDemo();
Thread thread = new Thread(threadDemo);
thread.start();}
}
3.2、继承 Thread 类
示例:
public class ThreadDemo extends Thread {
@Override
public void run() {System.out.println("通过继承 Thread 类创立线程");
}
public static void main(String[] args) {ThreadDemo threadDemo = new ThreadDemo();
Thread thread = new Thread(threadDemo);
thread.start();}
}
3.3、通过 Callable、Future
通过实现 Runnable 接口与继承 Thread 类的形式创立的线程是没有返回值的,然而在有些状况下,往往须要通过某个线程计算失去的后果供应另一个线程应用,这个时候采纳 Runnable 与 Thread 创立线程并通过自行编写代码实现后果返回的形式会不可避免的呈现许多谬误或性能上的问题。基于此问题,JUC 并发包(java.util.concurrent) 提供了解决方案,实现 Callable 的 call() 办法(这个相似 Runnable 接口),应用 Future 的 get() 办法进行获取。
先来看一下 Callable 接口:
public interface Callable<V> {V call() throws Exception;
}
创立过程为:
1、自定义一个类实现 Callable 接口,重写 call() 办法;
2、应用 JUC 包下的 ExecutorService 生成一个对象,应用 submit() 办法失去 Future 对象;
3、采纳 Future 的 get() 办法获取返回值。
示例:
import java.util.concurrent.*;
/**
* 计算 1 +2+...+20 的后果,开启三个线程,主线程获取两个子线程计算的后果,一个子线程计算 1 +...+10,一个子线程计算 11+...+20。*/
public class ThreadDemo implements Callable {
// 子线程 1,用来计算 1 +...+10
@Override
public Object call() throws Exception {
int count = 0;
for (int i = 1; i <= 10; i++)
count = count + i;
return count;
}
public static void main(String[] args) throws Exception {
// 生成具备两个线程的线程池
ExecutorService executorService = Executors.newFixedThreadPool(2);
// 调用 executorService.submit() 办法获取 Future 对象
Future<Integer> result1 = executorService.submit(new ThreadDemo());
Future<Integer> result2 = executorService.submit(new SubThread());
// 应用 Future 的 get() 办法期待子线程计算实现返回的后果
int result = result1.get() + result2.get();
// 敞开线程池
executorService.shutdown();
System.out.println(result);
}
}
class SubThread implements Callable {
// 子线程 2,用来计算 11+...+20
@Override
public Object call() throws Exception {
int count = 0;
for (int i = 11; i <= 20; i++)
count = count + i;
return count;
}
}
3.4、通过 JUC 外面的线程池
Executors 的工厂办法提供了 5 种不同的线程池,具体能够看 JDK API,如下图。
其实在 3.3、通过 Callable、Future 形式创立线程的示例就能看到通过 Executors.newFixedThreadPool(2) 工厂办法构建线程池。(Executors 的五种工厂办法的区别及优缺点这里就不说了,下章再细说)
4、几个常见的线程面试题
4.1、线程与过程的区别
过程是操作系统进行资源分配的单元,线程是 CPU 调度运行的单位;一个过程中能够蕴含很多线程,线程共享过程的内存等资源;每个过程领有各自独立的一套变量,互相不影响,而线程则共享数据,会存在线程平安问题。
4.2、Thread 的 sleep() 办法和 wait() 办法有什么区别和共同点?
相同点:
- 两者都能够暂停线程的执行,都会让线程进入期待状态。
不同点:
- sleep() 办法没有开释锁,而 wait() 办法开释了锁。
- sleep() 办法属于 Thread 类的静态方法,作用于以后线程;而 wait() 办法是 Object 类的实例办法,作用于对象自身。
- 执行 sleep() 办法后,能够通过超时或者调用 interrupt() 办法唤醒休眠中的线程;执行 wait() 办法后,通过调用 notify() 或 notifyAll() 办法唤醒期待线程。
4.3、为什么启动线程时是调用 start() 办法而不是间接调用 run() 办法?
new 一个 Thread 时,线程会进入初始状态;调用 start() 办法时,会启动一个线程并使线程进入就绪状态,当调配到工夫片后就能够开始运行了。start() 会执行线程的相应筹备工作,而后主动执行 run() 办法的内容,这是真正的多线程工作。而间接执行 run() 办法,会把 run() 办法当成一个 main 线程下的一般办法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
一句话总结就是调用 start() 办法可启动线程并使线程进入就绪状态,而 run() 办法只是 Thread 的一个一般办法调用,还是在主线程里执行。
5、总结
通过本文心愿能对你 Java 线程相干的常识把握和面试能有所帮忙,下章持续介绍线程池的相干常识。文中有谬误的中央,还请留言给予斧正,谢谢。喜爱文章的小伙伴们能够关注点赞珍藏哦~
你要的 Java 材料都在这了,关注公众号即可无套路收费支付!