创建线程四种方式
JVM针对操作系统而言是一个进程,一个进程下面可以有多个线程。在java中只有Thread类能通过一个被native关键字(当java调用非java代码写的接口时用此关键字修饰)修饰名为start0()的去向本地的操作系统申请线程的资源。
一、继承Thread类
在java中申请操作系统的线程资源只能使用Thread类的start0()方法。
1.新建一个类继承Thread类,并且重写其中的run方法,run方法里面是开启线程主要做的业务。
public class ThreadTest extends Thread {
@Override
public void run() {
//todo 需要进行的业务
System.out.println("my thread test");
}
}
在Thread源码中,明确指出 Thread的子类需继承该方法,如果此线程是用Runnable对象构造的,则调用的是Runnable的实现类里的run()方法。
/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* <p>
* Subclasses of <code>Thread</code> should override this method.
*
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
2.在main方法中start()方法开启线程
public class TestMain {
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
//调用start方法
threadTest.start();
}
}
我们为什么不调用run()方法而去调用start()方法呢?
在Threa的run()方法的源码中,可以看到,run方法里面并没有开启线程,只是一个普通的方法,调用run()方法只是在当前线程执行run()方法并不会启动新线程,而真正实现多线程是在Thread的start()方法里面。
public synchronized void start() {
//判断线程转态是否已经启动,多次启动会报错
if (threadStatus != 0)
throw new IllegalThreadStateException();
//将线程添加到线程组当中
group.add(this);
boolean started = false;
try {
//启动线程
start0();
started = true;
} finally {
try {
if (!started) {
//启动失败,状态回滚,后续继续尝试启动该线程
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
在start()的源码中,可以看到实际上是通过start0()方法来启动线程的,该方法会新运行一个线程,新线程会去调用run()方法。
当start()方法重复被调用,意味着多次启动该线程时,会抛出 IllegalThreadStateException的异常。
二、实现Runnable接口
新建线程类实现Runnable接口,重写run方法。
public class RunnableTest implements Runnable{
@Override
public void run() {
System.out.println("my runnable test");
}
}
此时该类是没办法新建线程的。还需要将其传到Thread类当中去,通过Thread的satart()方法去调用它。
public class TestMain {
public static void main(String[] args) {
RunnableTest runnableTest = new RunnableTest();
Thread thread = new Thread(runnableTest);
thread.start();
}
}
为什么需要将runnableTest传入Thread,并且Thread.start()能调用runnableTest的run()方法?
这就需要去看Thread的构造函数了
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//...
this.target = target;
//...
}
在构造函数中调用了init()方法,在此方法中可以看到 传入的runnable被赋值给了this.target,而在之前我们看到的run()方法的源码中,当target不为空时执行的是target.run(),所以最终在调用run方法时,调用的是我们传入的runnable的实现的run()方法。
三、实现Callable接口
Callable可以说是一个特殊的Runnable,最大的区别就是在于Callable有返回值,他与Future同用,Callable是产生结果,而Future是用来获取结果的。Future的get()用来等待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;
}
我们通过Callabe的源码可以看到,它是一个函数式接口,只有一个call()方法,call()方法里就是要调用到的代码,和run()方法里一样。
我们先定一个类实现了Callable接口,此时定义的返回类型为String
public class CallableTest implements Callable<String> {
@Override
public String call() throws Exception {
return "my callable test";
}
}
我们在main方法中,需要将Callable的实现类传入FutureTask进行包装,再讲FutureTask传入Theard启动线程执行。
public class TestMain {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableTest callableTest = new CallableTest();
FutureTask<String> futureTask = new FutureTask<>(callableTest);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());
}
}
首先让我们来看看FutureTask这个类,它实现了RunnableFuture接口,而RunnableFuture接口继承了Runnable接口和Future接口
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
由此可以知道,在FutureTask中有一个具体实现run()方法,而Thead.start()调用的是FutureTask里面的run()方法。再让我们看看FutureTask的构造函数,在构造函数中callable被赋值给了类本身的私有属性
private Callable<V> callable;
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
在FutureTask的run()方法中
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
//等待call()方法返回结果
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);
}
}
可以看到 result = c.call();,而c就是刚刚传入FutureTask的Callable,其实就是调用了Callable实现类的call方法进行返回。
/**
* Sets the result of this future to the given value unless
* this future has already been set or has been cancelled.
*
* <p>This method is invoked internally by the {@link #run} method
* upon successful completion of the computation.
*
* @param v the value
*/
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
最后把result结果传入了set方法,赋值给了private Object outcome,当我们调用get方法时,就会将此变量的值输出出来。
四、线程池创建线程
使用线程池来创建能很好的对线程进行复用,我们用Thread创建一个,在线程执行完毕时就会进行销毁,线程的创建和销毁是一个很耗系统资源的事情。我们可以将线程池看做是一个制造并存放线程的工厂,当线程池有线程时直接使用它,没有则新建,用完以后再退回到线程池中。线程池有很多配置参数需要自己去配置,这儿我们使用到了newFixedThreadPool,可以创建固定大小的线程池。
public class TestMain {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<?> future = executorService.submit(new RunnableTest());
Future<?> future1 = executorService.submit(new CallableTest());
executorService.execute(new RunnableTest());
System.out.println(future1.get());
}
}
这儿我们创建了一个包含2个线程的线程池,在执行的时候分别调用了submit()和execute()方法,二者的区别我们可以去看下源码
/**
* Submits a Runnable task for execution and returns a Future
* representing that task. The Future's {@code get} method will
* return {@code null} upon <em>successful</em> completion.
*
* @param task the task to submit
* @return a Future representing pending completion of the task
* @throws RejectedExecutionException if the task cannot be
* scheduled for execution
* @throws NullPointerException if the task is null
*/
Future<?> submit(Runnable task);
submit()方法是属于ExecutorService的方法,并且有返回值,可以传入Runnable和Callable
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
execute是属于 Executor的方法,只能传入Runnable,并且没有返回值。
小结
1.线程调用的总是Thread的run()方法。
2.利用Runnable来创建线程只需要实现接口,而继承Thread类以后就不能再继承其他类了(java的单继承),使用Runnable来创建线程能更加灵活
3.使用Callable可以获取线程的返回值,视使用情况使用。
4.以上四种方法都能创建线程,他们最终的底层都是Thread类,通过Thread类的start0()方法去申请线程资源