【多线程】创建线程的三种方式

进程与线程的区别


进程就是一个应用程序在处理机上的一次执行过程,它是一个动态的概念,而线程是进程中的一部分,进程包含多个线程在运行。举一个在博客中看到的例子。
进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。
这里写图片描述

一个车间里,可以有很多工人。他们协同完成一个任务。
这里写图片描述
线程就好比车间里的工人。一个进程可以包括多个线程。车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。

总结即:
a.联系 一个进程可包含多个线程。
b.资源 进程之间资源独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
c.通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
d.调度和切换:线程上下文切换比进程上下文切换要快得多。

创建方式


一:子类覆盖父类run方法

继承Thread类的方法尽管是一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:

public class FirstThreadTest extends Thread{  
public static void main(String[] args) {
     Thread thread = new Thread(){
         @Override
         public void run(){
             while(true){
                 try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                 System.out.println(Thread.currentThread().getName());
            }
         }
     };
     thread.start();
    }
}

二:实现Runnable接口

如果自己的类已经extends另一个类,就无法直接extends Thread,此时,必须实现一个Runnable接口。
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

(3)调用线程对象的start()方法来启动该线程。
要点如下:

 Thread thread2=new Thread(new Runnable(){
         public void run(){
             while(true){
                 try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                 System.out.println(Thread.currentThread().getName());
            }
         }
     });

事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码:

public void run() {
  if (target != null) {
   target.run();
  }
}

ExecutorService、Callable、Future

实现有返回结果的多线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。

(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
放入项目中一个示例:

            ExecutorService executorService = Executors.newCachedThreadPool();
            List<Future<PaperDetail>> resultList = new ArrayList<Future<PaperDetail>>();
            for (PaperDetail returnSinglePaperDetail : returnPaperDetail) {
                //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中
                Future<PaperDetail> future = executorService.submit(new myCallable(returnSinglePaperDetail));
                //将任务执行结果存储到List中
                resultList.add(future);
            }
            List<PaperDetail> listPapaerDeatil=new ArrayList<PaperDetail>();

            //遍历任务的结果
            for (Future<PaperDetail> fp: resultList) {
                try {
                    PaperDetail enPaperDetail =fp.get();
                    listPapaerDeatil.add(enPaperDetail);
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }

                //启动一次顺序关闭,执行以前提交的任务,但不接受新任务。如果已经关闭,则调用没有其他作用。
                executorService.shutdown();
            }


public class myCallable implements Callable<PaperDetail>{
         public myCallable(PaperDetail paperDetail) {
                this.paperDetail = paperDetail;
            }

          public PaperDetail 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()方法,会阻塞直到计算完成。

for (Future<PaperDetail> fp: resultList)

这一句执行的时候,说明已经有线程执行结束。我们应用其返回结果。

对比:


采用实现Runnable、Callable接口的方式创见多线程时,优势是:

线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。但是方式一不能继承其他类了。

在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

附:本文提到的 进程与线程的一个简单解释 参考博客如下:
进程与线程的一个简单解释

猜你喜欢

转载自blog.csdn.net/u010176014/article/details/52214870