一、线程的创建方式
1、继承Thread类
(1)创建一个继承Thread的子类,并重写run方法:
@Slf4j //需要安装lombok
class MyThread extends Thread{
@Override
public void run(){
log.info("继承Thread创建线程");
}
}
(2)创建Thread的子类对象,并调用start方法启动线程:
@Slf4j
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
log.info("主线程");
}
}
输出:
10:48:04.033 [Thread-0] INFO com.demo.MyThread - 继承Thread创建线程
10:48:04.033 [main] INFO com.demo.ThreadTest - 主线程
2、实现Runnable接口
(1)创建实现Runnable接口的类,实现抽象run方法:
@Slf4j
class MyThread implements Runnable{
@Override
public void run(){
log.info("继承Thread创建线程");
}
}
(2)创建实现Runnable接口类的对象,将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象,然后调用start方法:
@Slf4j
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
log.info("主线程");
}
}
(3)使用匿名内部类的方式:
@Slf4j
public class ThreadTest {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
log.info("匿名内部类的方式");
}
};
Thread thread = new Thread(runnable);
thread.start();
log.info("主线程");
}
}
(4)使用lambda表达式简写:
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
上面是Runable接口,里面只有一个抽象方法,被注解FunctionalInterface标记,这时候就可以使用lambda表达:
@Slf4j
public class ThreadTest {
public static void main(String[] args) {
Runnable runnable = () -> log.info("lambda表达式的方式");
Thread thread = new Thread(runnable);
thread.start();
log.info("主线程");
}
}
或者这样写:
@Slf4j
public class ThreadTest {
public static void main(String[] args) {
Thread thread = new Thread(() -> log.info("lambda表达式的方式"));
thread.start();
log.info("主线程");
}
}
甚至可以这样:
@Slf4j
public class ThreadTest {
public static void main(String[] args) {
new Thread(() -> log.info("lambda表达式的方式")).start();
log.info("主线程");
}
}
输出:
10:48:42.618 [main] INFO com.demo.ThreadTest - 主线程
10:48:42.618 [Thread-0] INFO com.demo.ThreadTest - lambda的方式
3、使用FutureTask和Callable创建
FutureTask能够接受Callable类型的参数,用来处理有返回结果的线程,FutureTask实现了RunnableFuture接口,RunnableFuture接口继承Runnable, Future接口,Future提供了一些方法可以获取线程返回值;Callable接口和Runnable接口非常类似,只有一个call方法,但是有返回值并且可以抛出异常,可以把一个线程的返回值传给另一个线程。
@Slf4j
public class ThreadTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> task = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
log.info("FutureTask创建线程");
Thread.sleep(1000);
return "ok";
}
});
Thread thread = new Thread(task);
thread.start();
log.info("{}",task.get());
}
}
当然也可以简化成如下代码:
@Slf4j
public class ThreadTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> task = new FutureTask<>(() -> {
log.info("FutureTask创建线程");
Thread.sleep(1000);
return "ok";
});
Thread thread = new Thread(task);
thread.start();
log.info("{}",task.get());
}
}
输出:
12:18:12.489 [Thread-0] INFO com.demo.ThreadTest - FutureTask创建线程
12:18:13.492 [main] INFO com.demo.ThreadTest - ok
当调用get方法时,主线程会阻塞,直到里面的线程执行完才会拿到返回值结束。
4、使用线程池创建线程
由于Executors框架的提供创建线程池的方法不推荐使用,这里使用ThreadPoolExecutor来创建线程池,线程池的具体参数会在后面的章节介绍,这里只演示如何通过线程池创建线程:
@Slf4j
public class ThreadTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8, 200, 10L, TimeUnit.SECONDS, new SynchronousQueue());
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
log.info("running...");
}
});
log.info("主线程");
threadPoolExecutor.shutdown();
}
}
或者简写
@Slf4j
public class ThreadTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8, 200, 10L, TimeUnit.SECONDS, new SynchronousQueue());
threadPoolExecutor.execute(() -> log.info("running..."));
log.info("主线程");
threadPoolExecutor.shutdown();
}
}
10:43:36.105 [pool-1-thread-1] INFO com.demo.ThreadTest - running...
10:43:36.105 [main] INFO com.demo.ThreadTest - 主线程
5、Thread和Runnable的关系和比较
(1)关系
Thread实现了Runnable接口;使用Runnable接口方式创建线程,实际是把实现Runnable接口的对象实例当做参数传给Thread,跟踪Thread的构造方法(JDK1.8):
//1、Thread 460行-462行
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
//2、Thread 374行-350行
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
...
this.target = target; //3、Thread 413行
...
}
//4、Thread 742行-747行
@Override
public void run() {
if (target != null) {
target.run();
}
}
根据源码可以看到传入的Runnable实例最终还是在Thread的run方法中被调用,而用继承Thread的方式是重写Thread的run方法。
(2)比较
继承Thread类的创建方式是把线程和任务合并在了一起,而实现Runnable接口的方式是把线程和任务分开了更容易与线程池高级API配合;Java只支持单继承,而可以实现多个接口,所以实现Runnable接口的创建方式更灵活(推荐使用)。