通过继承Thread的方式,可以创建一个线程,需要重写其中的run方法,启动线程时,通过调用start方法启动。代码如下:
/*创建线程需依赖java.lang.Thread类,Thread就是一个线程类 继承类线程Thread*/ public class Test { public static void main(String[] args) { //1)创建线程对象 SubThread s1=new SubThread(); //2)启动子线程 s1.start(); //main线程 for(int j=1;j<101;j++){ System.out.println("j="+j); } } } class SubThread extends Thread{ @Override public void run() { for(int i=1;i<101;i++){ System.out.println("i="+i); } } }
通过实现Runnable接口的方式,可以创建一个线程,需要重写其中的run方法,启动线程时,将自定义类的实例作为一个参数,调用Thread的构造方法,得到一个线程实例,再调用start方法启动。代码如下:
/*Prime实现类Runnable接口,将Prime实例对象保存到Thread线程类*/ public class Test2 { public static void main(String[] args) { Prime p=new Prime();//Runnable接口实现类 /*不能直接p.start(),虽然Prime类和Thread类的接口都是Runnable 但Prime只是实现类,启动子线程还是需要依赖Thread类*/ Thread t=new Thread(p); //将Prime实例对象保存到Thread线程类 t.start(); for(int j=1;j<101;j++){ System.out.println("j="+j); } } } class Prime implements Runnable{ @Override public void run() { for(int i=1;i<101;i++){ System.out.println("i="+i); } } }此外Runnable接口还可以通过Thread线程类实现匿名内部类,代码如下:
/*Thread线程调用匿名内部类Runnable接口*/ public class Test3 { public static void main(String[] args) { Thread t=new Thread(new Runnable() { @Override public void run() { for(int i=1;i<101;i++){ System.out.println("i="+i); } } }); t.start(); for(int j=1;j<101;j++){ System.out.println("j="+j); } } }Thread和Runnable区别:
Thread和Runnable,最直接的区别是:Thread是一个类,需要继承,而Runnable是一个接口,需要实现。
Thead类中,有一个属性,类型是Runnable,名称是target。也就是说,当我们调用start方法启动线程后,事实上在运行时,执行的是target的run方法。注意我们在使用Runnable时做的事情,是先new了一个Runnable类的实例,再将其包装成一个Thread的实例,再执行start方法,就好像是我们把Runnable和Thread作了一定程度的解耦,换句话说,我们把线程的创建和具体的业务做了解耦。这样的好处是什么?一是代码和数据独立,二是多线程可以共享同一个资源。
代码如下:
Runnable runnable=new MyRunnable(); Thread t1=new Thread(runnable); Thread t2=new Thread(runnable); t1.start(); t2.start();我们看到,创建了两个线程,但是它们使用了同一个Runnable实例。也就意味着,如果Runnable实例中包含一个类变量,比如count,那么,两个线程对count的操作是相互影响的,也就是说,count这个变量是两个线程共享的,Runnable实例是两个线程共享的。
通过实现callable接口的方式,可以创建一个线程,需要重写其中的call方法。启动线程时,需要新建一个Callable的实例,再用FutureTask实例包装它,最终,再包装成Thread实例,调用start方法启动,并且,可以通过FutureTask的get方法来获取返回值。代码如下:
/*1)定义类实现Callable接口,通过泛型指定返回值的类型 *2)建立FutureTask类传递参数Callable接口 *3)最后通过Thread线程类启动子线程 * */ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class Test4 { public static void main(String[] args) throws InterruptedException, ExecutionException { Prime1 p = new Prime1(); FutureTask<Integer> task = new FutureTask<>(p); /*FutureTask实现了RunnableFuture<V>接口,该接口继承了Runnable接口, FutureTask的参数是Callable接口*/ Thread t=new Thread(task); /*FutureTask类就是Runnable接口实现类*/ t.start(); System.out.println("main主线程"); System.out.println("result=" + task.get()); /*调用task.get()方法需抛出InterruptedException, ExecutionException异常 以此来获取返回值*/ } } class Prime1 implements Callable<Integer>{ /*1)定义类实现Callable接口,通过泛型指定返回值的类型 2)Runnable接口中的run()方法没有返回值 , Callable接口的call()有返回值 3)重写Callable中的call方法并抛出异常 */ @Override public Integer call() throws Exception { int result=(int) (Math.random()*100); System.out.println("执行子线程, 完成某个计算, 结果为: " + result); return result; } }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的方法,该方法源代码代码如下:
public V get() throws InterruptedException, ExecutionException { int s = state; if (s <= COMPLETING) s = awaitDone(false, 0L); //按住Ctrl去到awaitDone方法内部 return report(s); } //以下关键代码如下 else if (s == COMPLETING) // cannot time out yet Thread.yield(); else if (q == null)省略了与本文无关的代码。我们发现,这里有个关键点是,当判断s == COMPLETING时,就会执行Thread的yield方法。这个方法的作用就是使当前线程进入就绪状态,而不是阻塞状态。
相当于事先为我们准备好了一些线程,放到池子里。我们需要的时候,取用就可以了,而不需要自己创建。
