[多线程] 新建线程以及线程的运行
1、如何新建一个线程
/** * 继承Thread类实现线程 * */ public class CreateByExtends extends Thread { @Override public void run() { System.out.println("Thread By Extends Thread"); for (int i = 0; i < 10; i++) { System.out.println("Thread:" + i + "次"); } } }
/** * 通过实现Runnable接口实现线程 * * @author ss * */ public class CreateByImplement implements Runnable { @Override public void run() { System.out.println("Thread By implements Runnable"); for (int i = 0; i < 10; i++) { System.out.println("Runnable:" + i + "次"); } } }
2、对线程进行调用
1 public class Test01 { 2 public static void main(String[] args) { 3 /** 4 * 通过继承Thread类的线程 5 */ 6 Thread t1 = new CreateByExtends(); 7 8 /** 9 * 通过实现Runnable接口的线程 10 */ 11 Runnable r1=new CreateByImplement(); 12 /** 13 * 两个线程随机获得时间片,顺序随机 14 */ 15 t1.start(); 16 new Thread(r1).start(); 17 18 } 19 }
(1)通过start()方法进行线程调用,实现Runnable接口的线程,必须将Runnable作为Thread类的参数,然后通过Thread的start方法来创建一个新线程来执行该子任务。如果调用Runnable的run方法的话,是不会创建新线程的,这根普通的方法调用没有任何区别。 事实上,查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。
(2)线程在系统中通过获取时间片的方式运行,所以顺序是随机的
输出的结果为:
Thread By Extends Thread
Thread By implements Runnable
Thread:0次
Runnable:0次
Thread:1次
Thread:2次
Thread:3次
Thread:4次
Runnable:1次
Thread:5次
Runnable:2次
Runnable:3次
Runnable:4次
Runnable:5次
Runnable:6次
Runnable:7次
Runnable:8次
Thread:6次
Runnable:9次
Thread:7次
Thread:8次
Thread:9次
那如果我们直接调用线程中的run()方法呢:
1 public class Test01 { 2 public static void main(String[] args) { 3 /** 4 * 通过继承Thread类的线程 5 */ 6 Thread t1 = new CreateByExtends(); 7 8 /** 9 * 通过实现Runnable接口的线程 10 */ 11 Runnable r1=new CreateByImplement(); 12 13 //如果调用线程的run方法 14 t1.run(); 15 new Thread(r1).run(); 16 } 17 }
运行结果为:
Thread By Extends Thread
Thread:0次
Thread:1次
Thread:2次
Thread:3次
Thread:4次
Thread:5次
Thread:6次
Thread:7次
Thread:8次
Thread:9次
Thread By implements Runnable
Runnable:0次
Runnable:1次
Runnable:2次
Runnable:3次
Runnable:4次
Runnable:5次
Runnable:6次
Runnable:7次
Runnable:8次
Runnable:9次
(1)这里线程运行的结果是按顺序的,所以我们可以得知,我们只是调用了run()方法而已,并不是两个线程。
(2)通过run方法调用并不会创建新的线程,而是在主线程中直接运行run方法,跟普通的方法调用没有任何区别;
(3)新线程创建的过程不会阻塞主线程的后续执行
通过两种方式创建线程有什么区别呢?
直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,但是由于Java只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。
其他实现线程的方法:
使用ExecutorService、Callable、Future实现有返回结果的多线程
可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。
3、通过实现Callable接口,实现线程:
1 /** 2 * 通过实现Callable接口实现有返回结果的线程 3 * 4 */ 5 public class CreateByCallable implements Callable<Object>{ 6 7 @Override 8 public Object call() throws Exception { 9 System.out.println("Thread By implements Callable<V> "); 10 for(int i=0;i<10;i++){ 11 System.out.println("Callabel<V>:"+i+"次"); 12 } 13 return "call方法执行结束,返回这句话"; 14 } 15 16 }
调用线程:
1 public class Test02 { 2 public static void main(String[] args) { 3 4 /** 5 * 通过实现Callable<V>接口实现的线程 6 */ 7 Callable<Object> c1 = new CreateByCallable(); 8 9 // 调用实现Callable接口的线程,需要使用FutureTask实现类的支持,用于接受结果 10 FutureTask<Object> future = new FutureTask<>(c1); 11 12 new Thread(future).start(); 13 14 // 接受线程运行后的结果 15 try { 16 Object result = future.get();// FutureTask可用于闭锁,类似于CountDownLatch的作用 17 System.out.println(result.toString()); 18 } catch (InterruptedException | ExecutionException e) { 19 e.printStackTrace(); 20 } 21 22 } 23 }
结果:
Thread By implements Callable<V> Callabel<V>:0次 Callabel<V>:1次 Callabel<V>:2次 Callabel<V>:3次 Callabel<V>:4次 Callabel<V>:5次 Callabel<V>:6次 Callabel<V>:7次 Callabel<V>:8次 Callabel<V>:9次 call方法执行结束,返回这句话
执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。
1 public static void main(String[] args) { 2 3 int taskSizes=5; 4 //创建一个线程池 5 ExecutorService pool=Executors.newFixedThreadPool(taskSizes); 6 //创建有多个返回值的任务 7 List<Future> list=new ArrayList<Future>(); 8 for(int i=0;i<taskSizes;i++){ 9 Callable c=new CreateByCallable(); 10 Future f=pool.submit(c); 11 list.add(f); 12 } 13 //关闭线程池 14 pool.shutdown(); 15 16 for(Future f:list){ 17 try { 18 System.out.println(f.get().toString()); 19 } catch (InterruptedException|ExecutionException e) { 20 e.printStackTrace(); 21 } 22 } 23 }
运行结果:
Thread By implements Callable<V> Thread By implements Callable<V> Thread By implements Callable<V> Callabel<V>:0次 Callabel<V>:0次 Callabel<V>:0次 Callabel<V>:1次 Callabel<V>:2次 Callabel<V>:3次 Callabel<V>:4次 Callabel<V>:1次 Callabel<V>:5次 Thread By implements Callable<V> Callabel<V>:1次 Callabel<V>:0次 Callabel<V>:6次 Callabel<V>:2次 Callabel<V>:7次 Callabel<V>:1次 Callabel<V>:2次 Callabel<V>:2次 Callabel<V>:8次 Callabel<V>:3次 Callabel<V>:9次 Callabel<V>:3次 Callabel<V>:4次 Callabel<V>:3次 Callabel<V>:5次 Callabel<V>:4次 Callabel<V>:5次 Callabel<V>:6次 Callabel<V>:6次 Callabel<V>:4次 Callabel<V>:7次 Callabel<V>:7次 Callabel<V>:8次 Callabel<V>:5次 Callabel<V>:9次 Callabel<V>:8次 Callabel<V>:9次 Callabel<V>:6次 Callabel<V>:7次 Callabel<V>:8次 Callabel<V>:9次 Thread By implements Callable<V> call方法执行结束,返回这句话 Callabel<V>:0次 call方法执行结束,返回这句话 call方法执行结束,返回这句话 call方法执行结束,返回这句话 Callabel<V>:1次 Callabel<V>:2次 Callabel<V>:3次 Callabel<V>:4次 Callabel<V>:5次 Callabel<V>:6次 Callabel<V>:7次 Callabel<V>:8次 Callabel<V>:9次 call方法执行结束,返回这句话
上述代码中Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads)
创建固定数目线程的线程池。
public static ExecutorService newCachedThreadPool()
创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
public static ExecutorService newSingleThreadExecutor()
创建一个单线程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。