三种使用线程的方法(JAVA笔记-线程基础篇)

线程(英语:thread)是操作系统能够进行运算调度的最小单位。大部分情况下,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。——维基百科
简单来讲一个程序就是一个进程,而程序里又有很多功能,这些功能不可能一个执行完再去执行另一个,这样就太蠢了,这些这些功能需要同时执行,这就需要“同时运行”多个线程了。

好了,你通过百度或者google已经基本了解了线程的概念了,已经知道线程到底是个什么东西了。现在开始用代码来“玩”线程。

简单的使用线程

简单的使用线程有三个方法。为什么说是简单呢?因为后面为了提高效率可能还需要用到线程池等。

  1. 继承Thread类,重写run()方法。

    • 实例代码:

      public class MyThread extends Thread {
          
          //这里写线程需要实现的功能
          @Override
          public void run() {
              //获取当前正在运行的线程名
              String threadName=Thread.currentThread().getName();
              System.out.println(threadName + "  is running!");
          }
      }
      

      说明:
      currentThread():获取当前这段代码在哪个线程中运行。
      getName():获取线程的名称。(没错,每个线程都是有一个名字的。我们在学习的时候可以用这个方法来判断是哪个线程执行了我们的代码。我们知道java有一个main方法入口,执行这个main方法其实也是一个线程,叫:main)

    • 测试代码:

      public class MyTest {
      
          public static void main(String[] args) {
              //声明线程对象实例
              MyThread myThread=new MyThread();
              //启动线程
              myThread.start();
          }
      }
      

      输出:Thread-0 is running!

      想想一下,上面写的小程序用到了几个线程来执行呢?
      因该是两个。其中一个是运行main()函数的主线程-main,还有一个就是我们在主线程里面开启的另一个线程-Thread-0

    • 还记得getName()方法嘛?我们用它来测试一下就知道一共用到了几个线程了。只要修改main()方法。

      public class MyTest {
          public static void main(String[] args) {
              //声明线程实例
              MyThread myThread=new MyThread();
              //启动线程
              myThread.start();
              //输出执行main()方法的线程名
              System.out.println(Thread.currentThread().getName());
          }
      }
      

      输出
      main is running!
      Thread-0 is running!

  2. 实现Runnable接口,重写run()方法。

    • 实例代码:

      public class MyRunableThread implements Runnable {
          public void run() {
              //获取当前正在运行的线程名
              String threadName=Thread.currentThread().getName();
              System.out.println(threadName + " is running! Use Runnable!");
          }
      }
      
    • 测试代码

        public class MyTest {
            public static void main(String[] args) {
                //声明Runnable的实现对象
                MyRunableThread myRunableThread=new MyRunableThread();
                //使用MyRunableThread声明线程实例
                Thread thread=new Thread(myRunableThread);
                //启动线程
                thread.start();
            }
        }
      

      输出: Thread-0 is running! Use Runnable!

    • 如果看下Thread这个类的源码,就会发现它也实现了Runnable接口(部分代码如下)。
      public class Thread implements Runnable {...}

    • 所以说某种程度上这两种方法算是一个方法,只是写法不一样而已。

    • 而且Thread这个类里面有很多被native方法,叫本地方法。如:public static native void yield();。因为线程是需要底层系统来实现的,而jvm帮我们屏蔽了底层的实现。所以使用线程是离不开Thread类的。

      就算是第二种方法,也还把实现对象当作参数传给Thread的构造方法,然后再用Thread对象来启动线程。

  3. 实现Callable接口,重写call()

    • 小故事
      主线程main开启一个线程小弟Thread-0,然后告诉Thread-0:你去计算一下1+1等于多少,然后告我。

      我们发现上面的两种方式的run()方法是没有返回值的。所以线程执行的结果无法获取了。如果像上面的例子,想获取线程执行后的结果就可以用Callable接口。

    • CallableThread没有直接联系
      之前我们知道线程的操作离不开Thread类。因为都要靠它里面的本地方法嘛。但是看看Thread的源码就会发现,没有参数是Callable的构造函数。。。。。完了?????
      其实不然,java提供了一个第三者FutureTaskCallableThread类联系在了一起。
    • 第三者FutureTaskRunnable
      我们使用Runnable方法实现线程的时候,知道可以把Runnable当作参数传到Thread的构造函数里面。
      我们来看看FutureTask的实现。
      public class FutureTask<V> implements RunnableFuture<V> {...}
      
      发现FutureTask实现了一个叫RunnableFuture的接口,再来瞅瞅RunnableFuture是何方神圣。
      public interface RunnableFuture<V> extends Runnable, Future<V> {
          void run();
      }
      

      哈哈,我们发现了熟悉的Runnablerun()方法。既然FutureTask是间接的实现了Runnable方法,那么它就可以当作参数传给Thread的构造函数了。

    • 第三者FutureTaskRunnable
      我们看看FutureTask的的源码,发现它有一下这个构造函数。
      public FutureTask(Callable<V> callable) {...}
    • 实例代码:
      //这里的<Object>是指的返回值的类型。因为Object是所有类的父类,为了简便写了Object。
      public class MyCallableThread implements Callable<Object> {
          
          //call()方法跟run()一样,当线程开启的时候会自动执行,但是它更牛逼,有返回值。
          public Object call() throws Exception {
              //获取当前正在运行的线程名
              String threadName=Thread.currentThread().getName();
              System.out.println(threadName + "  is running!");
              //计算1+1
              int result=1+1;
              //返回计算结果
              return " 1 + 1 = "+result;
          }
      }
      
    • 测试代码
      public class MyTest {
          public static void main(String[] args) throws Exception {
              MyCallableThread myCallableThread=new MyCallableThread();
              FutureTask futureTask=new FutureTask(myCallableThread);
              Thread thread=new Thread(futureTask);
              thread.start();
              Object answer = futureTask.get();
              System.out.println("Thread-0 的计算结果:"+answer);
          }
      }
      

      输出:
      Thread-0 is running!
      Thread-0 的计算结果: 1 + 1 = 2

总结

  1. 一般线程操作都需要用到Thread类。
  2. Runnable接口的实现类可以当作参数传给Thread的构造函数。
  3. Callable不能直接与Runnable直接由关系,需要借助第三者FutureTask才能和Thread有关系。
  4. 为什么我们重写的是run()方法,或者是call()方法,但是启动线程的时候调用的是start()方法呢?
    这个问题其实很简单,run()call()方法是写我们需要运行的代码的地方。
    start()他调用的是Thread类中的一个 本地方法 ,注意是 本地方法,而我们知道线程本质上是操作系统来实现的,所以这个start()就是告诉操作系统,我们要开始-start()一个线程,然后让开始的这个线程去运行-run()或者调用-call()方法。

Callable的设计好巧妙。

预告

这只是线程使用的最简单的方式,当然还有更高级的时候方法。
如果有缘,我们下一篇笔记见。

posted @ 2020-06-01 22:34  BobCheng  阅读(579)  评论(0编辑  收藏  举报