创建线程的4种方式
创建一个空线程
public class EmptyThreadDemo {
public static void main(String[] args) {
//使用Thread类创建和启动线程
Thread thread = new Thread();
Print.tco("线程名称:"+thread.getName());
Print.tco("线程Id:"+thread.getId());
Print.tco("线程状态:"+thread.getState());
Print.tco("线程优先级"+thread.getPriority());
Print.tco(Thread.currentThread().getName()+"运行结束");
thread.start();
}
}
首先创建一个空线程,通过该线程在堆内存的引用地址获取到该线程的名称,ID,状态,优先级。
此时线程并没有启动,其线程状态是New。然后用thread.start()启动该线程,线程会去执行用户代码逻辑块,逻辑块的入口是run()方法,我们可以看看run方法的源码:
public void run() {
if (target != null) {
target.run();
}
}
target是Thread类中的一个实例属性,它是这样定义的。
private Runnable target;
它是一个Runnable类型的属性,Runnable是一个接口类,里面有定义一个方法便是run(),这也意味着在新线程启动后,会以run()方法为代码逻辑块入口执行用户代码,而内部进一步调用了target目标实例执行类的run()方法,而我们并没有去实现这个方法,所以什么都没有执行,该线程也称空线程结束了,整个JVM进程也结束。
工具类
这些工具类的方法后续会用上,便于编码。
public class ThreadUtil{
public static String getCurThreadName(){
return Thread.currentThread().getName();
}
public static void sleepMillSeconds(int millsecond){
LockSupport.parkNanos(millsecond*100L*100L);
}
public static void execute(String cfo){
synchronized(System.out){
System.out.println(cfo);
}
}
}
public class Print{
public static void tco(Object s){
String cfo = "["+ThreadUtil.getCurThreadName()+"]"+s;
ThreadUtil.execute(cfo);
}
}
通过继承Thread类的方式创建线程目标类
前面的例子向我们说明了线程start之后,如何执行用户定义的线程代码逻辑。因此我们想要线程去执行我们的代码就主要有两种方式:
- 继承Thread类去重写run()方法。
- 实现Runnable接口的run()方法,并将实现好的接口的实现类以构造参数的形式传入Thread的target实例属性中。
接下来我们来以代码诠释第一种方式
public class CreateDemo{
private static final int MAX = 5;
private static int treadNo = 1;
static class DemoThread extends Thread{
public DemoThread(){
//调用父类的构造方法
super("DemoThread-"+treadNo++);
}
@Override
public void run(){
for(int i = 0;i < MAX;i++){
Print.tco(getName()+", 轮次为:"+i);
}
Print.tco(getName()+" 运行结束.");
}
public static void main(String[] args){
Thread thread = null;
for(int i = 0;i < 2;i++){
thread = new DemoThread();
thread.start();
}
Print.tco(getCurThreadName()+" 运行结束.");
}
}
}
例子中,我们建了一个静态内部类去继承Thread类,调用其带String的构造方法构造该实现类,重写Thread类的run()方法,添加属于我们的逻辑代码块。
这里的代码逻辑是循环5次,每次输出当前运行线程的名字以及轮次。
至于为什么是静态内部类,主要是为了方便调用外部类的属性,而如果该内部类不是静态的话还需要new外部类才new当前内部类。当然将其写为外部类,依然不影响后面的输出结果。
通过实现Runnable接口创建target执行目标类来创建线程目标类
在我们用代码演示之前,我们可以来看一下Thread的构造方法有哪些?
图中我们可以看到,Thread给我们提供了样式丰富的构造方法,其中有Runnable的也居多。因此我们可以以Runnable为构造参数的形式给Thread实例类传入target实例属性。
构造参数String类型实则为所创建线程的名称。
接着我们来用代码真正实现
public class CreateDemo2 {
public static final int MAX = 5;
static int threadNo = 1;
static class RunTarget implements Runnable{
@Override
public void run() {
String name = getCurThreadName();
for (int i = 0; i < MAX; i++) {
Print.tco(name+",轮次:"+i);
}
Print.tco(name+" 运行结束.");
}
}
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
Thread thread = new Thread(new RunTarget(),"RunnableThread-"+threadNo++);
thread.start();
}
Print.tco(getCurThreadName()+",运行完毕.");
}
}
这里我们可以看到我们实现了Rnnable接口的run()方法,将这个target目标执行类以构造参数的形式传入了我们所创建Thread实例类,当start()的时候,JVM就会启动线程运行用户逻辑代码,也就是我们实现Runnable接口run()方法的逻辑代码。
通过匿名类来创建Runnable线程目标类
通过优雅的实现方式来创建Runnable线程目标类
public class CreateDemo2_2 {
public static final int MAX = 5;
static int threadNo = 1;
public static void main(String[] args) {
Thread thread = null;
for (int i = 0; i < 2; i++) {
thread = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < MAX; j++) {
Print.tco(getCurThreadName()+",轮次:"+j);
}
Print.tco(getCurThreadName()+" 运行结束.");
}
},"RunnableThread-"+threadNo++);
thread.start();
}
Print.tco(getCurThreadName()+" 运行结束.");
}
}
通过Lambda表达式创建Runnable线程目标类
通过优雅的实现方式来创建Runnable线程目标类
public class CreateDemo2_3{
private static final int MAX = 5;
private static int treadNo = 1;
public static void main(String[] args){
Thread tread = null;
for(int i = 0;i < 2;i++){
tread = new Thread(()->{
for(int j = 0;j < MAX;j++){
Print.tco(getCurThreadName()+",轮次为:"+j);
}
Print.tco(getCurThreadName()+" 运行结束.");
},"RunnableThread-"+treadNo++);
thread.start();
}
Print.tco(getCurThreadName()+" 运行结束.");
}
}
继承Thread类来创建线程目标类和通过实现Runnable接口创建线程目标类有什么不同吗?
- 第一种方式创建线程目标类,由于每次创建类的内存地址都是不一样的,因此每个数据资源的内存地址都是不一样的,所以每个线程目标类都有其唯一的数据资源,在执行线程时,只是对着自己的数据资源进行业务处理,不会影响其他线程的数据资源。
-- 第一种方式创建线程目标类的优点:由于是继承了Thread类,其子类便享有父类的getName()、getID()、getStatus()等方法,可以很轻松的访问当前线程的各种信息状态和对当前线程进行操作。
-- 第一种方式的缺点: 由于一个类仅仅只能继承一个父类(不包括接口),所以在当前类继承了其他父类时,便用不了以继承Thread的方式来创建线程目标类了。 - 第二种方式以实现Runnable接口的方式得到target目标类,在用这个target目标类以构造参数的形式传入Thread实例中,得以创建真正的线程。这里我们可以发现多个线程用的target目标执行实现类都是用的同一个引用地址,也即多个线程使用的数据资源都是同一个。也就是说使用实现Runnable接口来创建线程目标类,其多个线程业务逻辑并行处理同一个数据资源。
-- 第二种方式创建线程目标类的优点:更好地体现了面向对象的设计思想。通过实现Runnable接口的方式设计多个target执行目标执行类可以更加方便,清晰地执行逻辑和数据存储的分离。
-- 第二种方式创建线程目标类的缺点:由于数据资源是被多个线程共享的,所以对数据资源做共享操作的时候会出现线程安全的问题。而且由于target目标类不是继承Thread的,所以要得到当前线程的信息,只能以Thread.currentThread()来获取当前在cpu时间片运行的线程来获取信息。
通过创建FutureTask和实现Callable接口来创建线程目标类
前面的两种方式其实都有一个共同的缺陷:由于run()方法的返回值类型是void类型,我们在线程异步执行完成之后是拿不到线程执行完成后的结果,很多时候我们想要了解线程异步执行的时候的状态,结果,前面的两种方式并不足以满足我们的需求。
于是为了解决这个问题,在JDK1.5版本提供了一种新的多线程创建方法:便是使用Callable接口和FutureTask相结合来创建线程目标类。
首先我们先从Callable接口的源码定义来认识一下它
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
我们从源码认识到,Callable是一个函数式接口,其中有唯一方法为call()方法,其方法的返回值是Callable接口的泛型形参,方法还有一个Exception的异常声明,容许方法的实现可以有异常抛出,且不做捕获。
不难看出call()方法的功能比run()方法要丰富多了,它多了返回值,对了异常的声明,功能十分强大。但是其能代替Runnable实例作为Thread的target执行类吗?显然这是不能的,上文我们提到target实例的属性是Runnable,而其是Callable类型的,所以并不能作为target来运行。
那么我们要通过何种方式来让线程启动的时候,进入run()方法里面运行的是call()方法里的代码逻辑块呢?
接下来我们来认识一下RunnableFuture接口
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
我们可以看到RunnableFuture接口继承了Runnable接口,使其实现类可以作为target目标类,同时它还继承了Future接口,那么这个接口赋予了RunnableFuture什么接口方法呢?我们来查看一下Future接口。
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
通过查看其实现我们可以知道Future接口赋予了RunnableFuture五个接口方法,分别是:
- cancel():取消异步任务的执行。
- isCancelled():查看异步任务是否取消了。
- isDone():查看异步任务是否执行完成。
- get():阻塞性获取异步任务的执行的结果。
- get(long timeout,TimeUnit unit):限时的阻塞性获取异步任务的执行的结果。
那么此时RunnableFuture接口就拥有了可以作为target实现类,可以获取线程的执行结果,执行状态的方法。那么最后就要实现该接口了,JDK已经帮我们实现好了,其名字叫做FutureTask
此时的FutureTask既能作为一个Runnable类型的target执行目标直接被Thread执行,有拥有着可以获取Callable执行结果,执行状态的能力。那么FutureTask是如何和Callable联系上的呢?我们可以查看FutureTask,其中有一个实例属性:
private Callable<V> callable;
其属性是用来保存并发执行的Callable类型的任务的,我们再来看看Future实现run方法的内部代码
public void run() {
if (state != NEW ||
!RUNNER.compareAndSet(this, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
protected void set(V v) {
if (STATE.compareAndSet(this, NEW, COMPLETING)) {
outcome = v;
STATE.setRelease(this, NORMAL); // final state
finishCompletion();
}
}
此时我们恍然大悟,在run()方法中调用了Callable的call()方法,并将方法的返回值"set"起来了,那么它保存在哪呢?它是保存在属性outcome中,方便get()的获取。
最终我们可以捋一下Callable接口和FaskTask是怎么创建线程目标类的。
于是该线程的执行流程便是:
- 首先线程start(),JVM会启动线程执行用户代码逻辑块,代码逻辑块的入口是run(),而run方法中调用了target执行类的run()方法,此时这个target便是我们已构造参数形式传入到Thread中的FutureTask,调用其run()方法,里面又调用了callable.call()方法,执行结果会保存在属性outcome中,静待调用线程调用。
我们用一个例子简单展现一下
public class CreateDemo3 {
public static final int MAX_TURN = 5;
public static final int COMPUTE_TIMES = 100000000;
static class ReturnableTask implements Callable<Long>{
@Override
public Long call() throws Exception {
long startTime = System.currentTimeMillis();
Print.tco(getCurThreadName()+" 线程开始运行.");
Thread.sleep(1000);
for (int i = 0; i < COMPUTE_TIMES; i++) {
int j = i * 10000;
}
long used = System.currentTimeMillis()-startTime;
Print.tco(getCurThreadName()+" 线程运行结束.");
return used;
}
}
public static void main(String[] args) throws InterruptedException {
ReturnableTask task = new ReturnableTask();
FutureTask<Long> futureTask = new FutureTask<Long>(task);
Thread thread = new Thread(futureTask,"returnableThread");
thread.start();
Thread.sleep(500);
System.out.println(getCurThreadName()+" 让子弹飞一会儿");
System.out.println(getCurThreadName()+" 做一点自己的事情");
for (int i = 0; i < COMPUTE_TIMES; i++) {
int j = i * 10000;
}
System.out.println(ThreadUtil.getCurThreadName()+" 获取并发任务执行结果.");
try {
System.out.println(thread.getName()+" 线程占用时间:"+futureTask.get());
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(ThreadUtil.getCurThreadName()+" 运行结束.");
}
}
通过线程池来创建线程目标类
前面的许多例子所创建的Thread实例类都在执行完成之后销毁了,这些线程实例都是不可复用的。实际上线程的创建,销毁在时间成本上,资源成本(因为线程创建需要JVM分配栈内存等)上耗费都很高,在高并发的场景下,断然不能频繁的进行线程的创建和销毁,需要的是线程的可复用性。此时需要的是池技术,JAVA中提供了一个静态工厂来创建不同的线程池,该静态工厂为Executors工厂类。
接下来我们使用一个例子来实现线程池,以及线程的调度执行
/**
* 第四种方式创建线程类:通过线程池创建线程
*/
public class CreateDemo4 {
public static final int MAX = 5;
//创建一个包含三个线程的线程池
private static ExecutorService pool = Executors.newFixedThreadPool(3);
static class DemoThread implements Runnable{
@Override
public void run() {
for (int i = 1; i <= MAX; i++) {
Print.tco(ThreadUtil.getCurThreadName()+",DemoThread轮次:"+i);
ThreadUtil.sleepMilliSeconds(10);
}
}
}
static class ReturnableTask implements Callable<Long>{
//返回并发执行的时间
@Override
public Long call() throws Exception {
long startTime = System.currentTimeMillis();
Print.tco(ThreadUtil.getCurThreadName()+" 线程运行开始.");
for (int i = 1; i <= MAX; i++) {
Print.tco(ThreadUtil.getCurThreadName()+",Callable轮次:"+i);
ThreadUtil.sleepMilliSeconds(10);
}
long used = System.currentTimeMillis() - startTime;
Print.tco(ThreadUtil.getCurThreadName()+" 线程运行结束");
return used;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
pool.execute(new DemoThread());//执行线程实例,无返回
pool.execute(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= MAX; i++) {
Print.tco(ThreadUtil.getCurThreadName()+",Runnable轮次:"+i);
ThreadUtil.sleepMilliSeconds(10);
}
}
});
Future<Long> submit = pool.submit(new ReturnableTask());
Long res = submit.get();
System.out.println("异步任务的执行结果为:"+res);
Thread.sleep(10);
System.out.println(ThreadUtil.getCurThreadName()+" 线程结束.");
}
}