Java 并发系列之一

  • 2020 年 3 月 14 日
  • 筆記

Java 并发系列之一

简单的总结了一些 Java 常用的集合之后,发现许多集合都针对多线程提供了支持,比如 ConcurrentHashMap 使用分段锁来提高多线程环境下的性能表现与安全表现。所以我打算接着对 Java 并发的相关内容做一个简单总结。

线程与进程

进程是操作系统分配资源的基本单位,也就是说进程是运行中的程序。

线程是进程中的基本执行单元,每个进程都至少拥有一个线程。线程不独立拥有操作系统资源,线程共享进程的操作系统资源。

处理并发问题为什么使用多线程而不是多进程,在我看来主要有两点。一是进程间通信难度大于线程间通信,会增加开发难度,二是线程间切换效率高于进程间切换,选择多线程更适合并发场景。

线程的生命周期

这里我们只简单介绍线程 new->Runnable->Running->Dead 这个流程,不考虑 Block 的情况。

  1. 当我们创建一个 Thread 对象时,这个线程就进入了 new 的状态。
  2. 当时机成熟,我们调用这个对象的 start() 方法时,这个线程就进入了 Runnable 的状态。
  3. 然后这个线程就会等待 CPU 资源,如果获取到 CPU 资源就会自动运行。
  4. 运行结束后线程就会进入到 Dead 状态。

需要注意的是一个 Thread 对象只有一次调用 start() 方法的机会,无论这个线程是否顺利执行结束。

创建线程

创建线程也就是线程生命周期中的 new 状态。在 Java 中创建线程有 3 中方式:

  1. 继承 Thread 类并重写 run 方法
  2. 实现 Runnable 接口
  3. 实现 Callable 接口

继承 Thread 类来创建线程

使用继承 Thread 的方式可以直接在 run 方法中利用 this 来获取当前线程的信息,而不需要通过 Thread 类的静态方法 currentThread() 方法来获取当前的线程。

public class MyThread extends Thread {      @Override      public void run() {          System.out.println(this.getName()+"正在运行");      }  }    public static void main(String[] args) {      Thread thread = new MyThread();      thread.start();  }  //输出结果:Thread-0 正在运行

如果想要快速实现一个匿名的类来执行某个简单操作的话,可以用下面的方式:

public static void main(String[] args) {      new Thread() {          @Override          public void run() {              System.out.println(this.getName() + "正在执行");          }      }.start();  }  //输出结果:Thread-0 正在运行

实现 Runnable 接口来创建线程

由于 Java 是不支持多继承的,所以如果要继承 Thread 类以外的类,使用 Runnable 来实现或许是个不错的选择。

public class MyRunnable implements Runnable {      @Override      public void run() {          System.out.println(Thread.currentThread().getName()+"正在运行");      }  }    public static void main(String[] args) {      Runnable runnable = new MyRunnable();      Thread thread = new Thread(runnable);      thread.start();  }  //输出结果:Thread-0 正在运行

其实 Runnable 接口只包含一个 run 方法,本质上还是通过重写 Thread 类的 run 方法来达到创建线程的目的。Runnable 还是一个函数式接口,所以想要声明一个匿名的 Thread 类还能通过下面的方式:

public static void main(String[] args) {      new Thread(() -> {          System.out.println(Thread.currentThread().getName() + "正在运行");      }).start();  }  //输出结果:Thread-0 正在运行

实现 Callable 接口来创建线程

上面两种创建线程的方式都是不支持返回值的,如果需要线程在执行之后提供返回值,就可以通过 Callable 来创建线程。使用 Callable 创建线程分为以下几个步骤:

  1. 创建类实现 Callable 接口的 call() 方法
  2. 使用 FutureTask 来包装上一步创建的类
  3. 使用 FutureTask 来创建 Thread 类
  4. 使用 FutureTask 对象的 get() 方法来获取返回值
public class MyCallable implements Callable<Integer> {      @Override      public Integer call() throws Exception {          System.out.println(Thread.currentThread().getName()+"正在运行");          return 316495132;      }  }    public static void main(String[] args)      throws ExecutionException,InterruptedException {      Callable callable = new MyCallable();      FutureTask<Integer> futureTask = new FutureTask<>(callable);      Thread thread = new Thread(futureTask);      thread.start();      System.out.println("返回值为:" + futureTask.get());  }  //输出结果:  //Thread-0 正在运行  //返回值为:316495132

小结

  1. 如果没有特殊的需求,实现 Runnable 接口或许是一个比较好的选择
  2. 如果需要线程执行完成后提供返回值,就只能选择继承 Callable 接口