线程创建方式

1.Thread
通过继承Thread的方式,可以创建一个线程,需要重写其中的run方法,启动线程时,通过调用start方法启动。形如:

2.Runnable
通过实现Runnable接口的方式,可以创建一个线程,需要重写其中的run方法,启动线程时,将自定义类的实例作为一个参数,调用Thread的构造方法,得到一个线程实例,再调用start方法启动。形如:

3.Callable
通过实现callable接口的方式,可以创建一个线程,需要重写其中的call方法。启动线程时,需要新建一个Callable的实例,再用FutureTask实例包装它,最终,再包装成Thread实例,调用start方法启动,并且,可以通过FutureTask的get方法来获取返回值。形如:

4.线程池
了解了以上三种线程的创建方式,线程池就好理解了。它相当于事先为我们准备好了一些线程,放到池子里。我们需要的时候,取用就可以了,而不需要自己创建。
线程池的线程,一定是通过实现Runnable或Callable接口的方法创建的,通过继承Thread类创建的线程实例,无法放到线程池中。

 2.通过Executer框架的工具类Executors创建 

newCachedThreadPool-----》创建一个可缓存线程池,如果线程池长度超过需要处理需要,克灵活回收空线程,若无可回收,则新建线程。
newFixedThreadPool------>创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newSchedulThreadPool----》创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor----->创建一个单线程化的线程池,它会只用唯一的工作线程来执行任务,保证所有任务按照指定顺序FIFO、LIFO,优先级执执

Thread和Runnable
  最直接的区别是,Thread是一个类,需要继承,而Runnable是一个接口,需要实现。我们知道,Java中,是单继承多实现的,即一个类,通过extends只能继承一个类,而通过implements关键字,可以实现多个接口。所以,就这两种方法而言,更推荐使用实现Runnable接口的方式。这样,extends可以留给更需要的人。
  Runnable还有一个优势,我们可以通过分析Thread的源码发现。Thead类中,有一个属性,类型是Runnable,名称是target。也就是说,当我们调用start方法启动线程后,事实上在运行时,执行的是target的run方法。注意我们在使用Runnable时做的事情,是先new了一个Runnable类的实例,再将其包装成一个Thread的实例,再执行start方法,就好像是我们把Runnable和Thread作了一定程度的解耦,换句话说,我们把线程的创建和具体的业务做了解耦。这样的好处是什么?一是代码和数据独立,二是多线程可以共享同一个资源。
代码和数据独立很好理解,那么,多线程共享资源是什么意思?看如下:

我们看到,创建了两个线程,但是它们使用了同一个Runnable实例。也就意味着,如果Runnable实例中包含一个类变量,比如count,那么,两个线程对count的操作是相互影响的,也就是说,count这个变量是两个线程共享的,Runnable实例是两个线程共享的。
 
Callable的特殊之处
  首先,它的执行方法call有返回值。查看Callable接口的源码,发现它还支持通过泛型的方式,来规定call方法的返回值,这使我们的使用更加灵活。
  call方法和Runnable的run方法还有一个不同点是可以抛出异常。看源码得知,它会抛出Exception异常。
 
  也就是说,Callable接口给我们带来了更加灵活的线程使用体验,不仅可以去获取一个线程的返回值,还可以对线程中出现的异常进行处理。例如,我们在示例代码中,调用了FutureTask的get方法。这个方法就能够得到Callable中call方法的返回值。
  事实上,我们看下FutureTask的源码就会发现,它实现了RunnableFuture接口,而RunnableFuture又继承了Runnable和Future接口(Java的接口可以多继承)。这也就使得FutureTask能够作为Thread构造方法的一个参数,又能通过get方法来获取到返回值。
这里还有一个点是,get方法会使当前线程等待得到返回值。换句话说,如果A线程中通过调用了get方法,要取得B线程的返回值。那么,A线程在执行到get方法时,如果B线程还没有执行完毕,A线程就会等待,一直等到B线程执行完成,A线程才会继续运行。为什么这里用了“等待”这个词来描述,而没有使用阻塞呢?这点,我们通过get方法的源码可以看出来。
我们发现,get方法调用了一个awaitDone的方法,该方法有如下关键代码:

省略了与本文无关的代码。我们发现,这里有个关键点是,当判断s == COMPLETING时,就会执行Thread的yield方法。这个方法的作用就是使当前线程进入就绪状态,而不是阻塞状态。
 
posted @ 2023-10-31 13:09  壹索007  阅读(7)  评论(0编辑  收藏  举报