在java中创建一个线程有3种方法。
一.直接new一个Thread对象
1 2 3 4 5 6 7 8 9
| public void testForCreateThread1(){ Thread thread1 = new Thread("t1") { public void run() { System.out.println("t1 is doing"); } };
thread1.start(); }
|
二.通过Runnable接口辅助创建线程
1 2 3 4 5 6 7 8 9 10 11
| public void testForCreateThread2(){ Runnable runnable = new Runnable() { @Override public void run() { System.out.println("t2 is doing"); } };
Thread thread2 = new Thread(runnable,"t2"); thread2.start(); }
|
针对第二种方法可以通过lambda对其简化
1 2 3 4 5
| public void testForCreateThread3(){ Runnable runnable = () -> System.out.println("t3 is doing"); Thread thread3 = new Thread(runnable, "t3"); thread3.start(); }
|
疑问
以上代码中可以发现有new Runnable这个操作,Runnable是一个接口,为啥能够被实例化?
–解答:在java中接口和抽象类是不能被实例化的,在这里也不例外.而这里其实是使用了匿名内部类,也就是说new出来的这个对象不是Runnable,而是一个实现了该接口的子类,这也是为什么run方法上出现@Override注解的原因。等价于以下代码
1 2 3 4 5 6 7 8 9 10 11 12
| public class Mythread implements Runnable{ public void run(){ System.out.println("t2 is doing"); } }
public void testForCreateThread2(){ Runnable runnable = new Mythread();
Thread thread2 = new Thread(runnable,"t2"); thread2.start(); }
|
可以发现,相较于使用匿名内部类,后者需要额外创建一个类,更加麻烦。因此对于那些只在某些只使用一次的接口实现子类,可以使用匿名内部类的方式创建,这也是我们经常使用的方式,比如在给集合排序时,就会使用匿名内部类来实现Comparator接口。
三.通过FutureTask辅助创建线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public void testForCreateThread4() throws ExecutionException, InterruptedException { FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() { public Integer call() throws InterruptedException { System.out.println("t4 is doing"); Thread.sleep(1000); return 10; } });
Thread thread4 = new Thread(task, "t4"); thread4.start(); int t = task.get(); System.out.println(t); }
|
FutureTask实现了RunnableFuture接口,而RunnableFuture继承了Runnable接口,因此FutureTask也实现了Runnable接口。
FutureTask类中提供了两个构造方法
1 2 3 4 5 6 7 8 9 10 11
| public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; }
public FutureTask(Runnable runnable, V result) { this.callable = Executors.callable(runnable, result); this.state = NEW; }
|
第一种构造方法的Callable也是一个接口,里面有一个抽象方法call(),用于返回计算结果.FutureTask的get方法能够获取该返回值,需要注意的是该get方法是一个阻塞式线程,它会一直等待这个返回值,所以如果你将task.get()语句放在thread4.start()语句之前,就会发生死锁现象。
需要注意的是,以上3中创建线程的方法中,其实只有第一种方法才是真正创建了一个线程,后两者只是创建了线程所要运行的任务,即run方法中的内容,不过由于后两种方法能够将创建线程和线程要运行的任务分开,是编程更加灵活,因此也更受青睐。