Java并发编程(二)创建线程的三种方法

进程与线程

  1. 1.  进程

进程和代码之间的关系就像音乐和乐谱之间的关系一样,演奏结束的时候音乐就不存在了但乐谱还在;程序执行结束的时候进程就消失了但代码还在,而计算机就是代码的演奏家。

2. 线程

线程可以比喻成演奏过程中的某一种乐器的声音,乐器声音的种类可以很少,但是不能一个都没有——一个进程至少包含一个线程。线程是程序执行的核心,就像没有了具体乐器的声音就没有了音乐一样。

创建线程的三种方式

1. 继承Thread方法

     1). 定义线程类

继承Thread方法时,需要重写Thread的run()方法。run()方法中的代码就是你要并发执行的代码。

class ExampleThread extends Thread {
   @Override
    public void run() {
        doSomething();
    }
}    

2). 执行线程类的代码

在main()方法里创建一个ExampleThread对象,调用该对象的start()方法,start()方法会通过对系统底层的一系列操作,创建出一个相应的线程,与当前线程并发执行。如果直接调用run()方法,程序将执行完run()方法后才会执行main()方法中后面的代码,这样就是单线程执行而不是多线程并发执行了。

public class CreateThread {
    public static void main(String[] args) {
        ExampleThread thread = new ExampleThread();
        thread.start();//注意是start(),不是run()
        //下面的代码可以和run()方法里的代码并发执行
        doSomethingElse();
   }
}        

  

2. 实现Runnable接口

1). 定义线程类

使用Runnable接口与继承Thread类的用法类似,也是在新的类中编写run()方法,方法中的内容就是新建线程要执行的代码。

class ExampleThread implements Runnable	{
  @Override
  public void run() {
    doSomething();
  }
}

2). 执行线程类的代码

   实现Runnable接口的类中没有start()方法,因此我们应该为这个线程“找一个”start()方法。Thread类有一个带Runnable参数的构造方法,我们将ExampleThread的对象作为参数传到Thread的构造方法中,这时候再调用Thread对象的start()方法就可以生成一个线程了,是不是有种“借鸡生蛋”的感觉。

public class CreateThread {
  public static void main(String[] args) {
    Runnable runnable = new ExampleThread()
    Thread thread = new Thread(runnable);
    thread.start();//注意是start(),不是run()
    //下面的代码可以和run()方法里的代码并发执行
    doSomethingElse();
  }
} 

3. 实现Callable接口

1). 定义线程类    

前面说的使用Thread和Runnable实现多线程的方法都不能从上一个线程中得到返回值,可是有些时候当一个线程运行结束的时候你想让它返回一些有价值的信息,比如某些操作是否执行成功了。Callable接口就是用于解决这类问题的,Callable接口是一个带泛型的接口,泛型的类型就是线程返回值的类型。实现Callable接口中的call()方法,方法的返回类型与泛型的类型相同。

class ExampleThread implements Callable<String>	{
  @Override
  public String call() {
    doSomething();
    return "Some Value";
  }
}

2). 执行线程类的代码

执行这个线程类的方法与Runnable类似,但是Callable在调用start()方法之前需要先包一层FutureTask,FutureTask用于获得线程返回的数值,具体代码如下:

public class CreateThread {
  public static void main(String[] args) {
    Callable<String> callable = new ExampleThread();
    FutureTask<String> task = new FutureTask<String>(callable);
    Thread thread = new Thread(task);
    thread.start();//注意是start(),不是call()
    doSomethingElse();
    try {
      String returnVal = task.get();//这里就得到了线程的返回值
    } catch (InterruptedException e) {
      e.printStackTrace();
    } catch (ExecutionException e) {
      e.printStackTrace();
    }
  }
}

  

 

这里有一个需要注意的地方,当调用task.get()的时候,新创建的线程可能还没有执行完,这时调用get()方法当前线程就会被阻塞,直到新创建的线程执行结束。因此,task.get()代码最好是在当前线程不得不用这个返回值时再调用,这样当前线程可以继续执行与返回值无关的代码。

总结

通过继承Thread类和实现Runnable接口生成的线程都是没有返回值的,实现Callable接口的线程可以有返回值,但是要注意不要过早的阻塞当前线程。

继承Thread类和实现Runnable接口二者之间各有好处,继承Thread类可以方便的使用到start()方法启动线程,但同时也带来一个弊端:继承使新类和Thread类产生强耦合,此外,由于Java不支持多继承,新的类也不能再继承其他的类。实现Runnable接口与之相反。

那么问题来了,Thread和Runnable该如何选择呢?我的建议是尽量使用Runnable,有的人可能会说:用Runnable还是要调用Thread的start()方法,还不如直接用Thread呢。嘿嘿,其实线程池可以代替Thread的start(),预知详情,且看下文分解。公众号:今日说码。关注我的公众号,可查看连载文章。遇到不理解的问题,直接在公众号留言即可。

猜你喜欢

转载自www.cnblogs.com/victorwux/p/8987048.html
今日推荐