Java中多线程常用的两种方式和Callable方式

多线程的实现方式

方式一: 继承Thread类

  • Thread类实现了Runnable接口,在java.long包下。

  • 创建执行线程方法一:将类继承Thread类,重写Thread类的run方法。接下来就可以分配并启动该子类的实例。

  • 具体步骤:

    1. 继承Thread类
    2. 重写run方法
    3. 将执行的代码写在run方法中
    4. 创建Thread类的子类对象
    5. 使用start方法开启线程。
  • 注意:调用run方法不能开启多线程

  • 只有调用了start()方法,才会表现出多线程的特性,不同线程的run()方法里面的代码交替执行。如果只是调用run()方法,那么代码还是同步执行的必须等待一个线程的run()方法里面的代码全部执行完毕之后另外一个线程才可以执行其run()方法里面的代码。

  • 一个线程不能多次开启是非法的

  • 代码示例:

    public class ThreadTest {
    
      public static void main(String[] args) {
          //4,创建Thread类的子类对象
          MyThread mt = new MyThread();
          mt.start();//5,使用start方法开启线程
          for (int i = 0; i < 10000; i++) {
              System.out.println("main" + i);
          }
      }
    }
    class  MyThread extends  Thread{ //1.继承Thread类
    
      //2,重写run方法
      @Override
      public void run(){
          //3,将执行的代码写在run方法中
          for (int i = 0; i <10000 ; i++) {
              System.out.println("mt"+i);
          }
      }
    }
    

方式二:实现Runnable接口(常用,优点多)

  • 声明实现Runnable接口的类,实现Runnable接口中仅有的run方法,然后分配实例对象,在创建Thread时作为一个参数来传递并启动。

  • 具体步骤

    • 1,定义类实现Runnable接口
    • 2,在该类中实现Runnable接口中的run()方法
    • 3,线程中具体要执行的东西写在run()方法中
    • 4,创建Thread类的对象,并在该对象中传入该实现Runnable接口的对象作参数
    • 5,Thread类的对象调用start()方法开启新线程,其内部会自动的调用run方法
    public class RunnableTest {
      public static void main(String[] args) {
          MyRunnable  mr =  new MyRunnable(); //4、创建自己定义的Runnable实现类的对象
          Thread  thread = new Thread(mr); //5、创建Thread类的对象,并将自定义Runnable实现类的对象作为参数传递给Thread的构造函数
          thread.start(); //使用thread类的start方法开启线程。
    
          for (int i = 0; i < 1000; i++) {
              System.out.println("main+"+i);
          }
      }
    }
    //1、定义一个Runnable实现类
    class MyRunnable implements Runnable{
      //2、实现Runnable接口中的抽象方法
      @Override
      public void run() {
          //3、在run方法中写入要使用多线程的具体方法
          for (int i = 0; i <1000; i++) {
              System.out.println("mr"+i);
          }
      }
    }
    
  • 实现Runnable接口方式的实现原理

    • 1、查看Thread 类的构造函数,传递了Runnable接口的引用,直接调用了init方法。
     public Thread(Runnable target) {
            init(null, target, "Thread-" + nextThreadNum(), 0);
        }
    • 2、追踪init方法,在init方法体中找到了传递的target参数,赋值给了Thread类的Runnable接口的成员变量的target
    this.target = target;
     /* What will be run. */
        private Runnable target;
    • 3、查看run方法时,发现run方法中有判断,如果target不为null就会调用实现Runnable接口子类对象的run方法
        @Override
        public void run() {
            if (target != null) {
                target.run();
            }
        }

为什么实例效果不明显?

  • 多线程指的是多个线程的代码块可以同时运行,而不必一个线程去等待另一个线程执行完才可以进行。
  • 对于单核CPU来说,无法做到真正意义上的多线程特性。只能会让用户看起来像是同时执行的,因为每个时间点上,CPU都会执行特定的代码,由于CPU执行代码时间非常快,多个线程代码块就会轮询执行,速度很快,但是同一个线程进行的轮询操作。
  • 具体执行某段代码多长时间和分时机制系统密切相关。
  • 分时系统CPU时间划分为多个时间片,操作系统以时间片为单位执行各个线程的代码,时间片越小,执行效率越高。

多线程的两种实现方式的区别

  • 源码中的区别
    • 继承Thread类方式:由于子类重写了Thread类的run(),当调用start()时,直接找子类的run()方法(Java虚拟机自动完成)
    • 实现Runnable方式:构造函数中传入了Runnable的引用,传给了Thread类中的成员变量,start()调用了run()方法时的内部判断成员变量Runnable的引用是否为空,若不为空,编译时看的是Runnable的run(),运行时执行的是具体实现类中的run()
  • 优缺点:
    • 继承Thread类方式
    • 好处:可以直接使用Thread类中的方法,代码简单
    • 弊端:同样也是面向对象中的继承的缺点:如果该具体类已经有了其他的父类,那么就不能多重继承Thread类,就不能使用这种方法。此时面向接口编程的优势脱颖而出。
    • 实现Runnable接口方式
    • 好处:即继承的弊端:即使自己定义的线程类有了其他父类也可以实现该Runnable接口。Java中的接口是多实现的,继承是单继承,比较有局限性。
    • 弊端:不能直接使用Thread类中的方法,需要先把Runnable具体实现类对象传递给Thread类并获取到线程对象后,才能得到Thread类的方法,代码相对复杂

匿名内部类实现线程的两种方式

即直接使用匿名内部类的方式简化代码:

  • 继承Thread类方式

    //匿名内部类
    new Thread(){
      @Override
      public void run() {
          for (int i = 0; i < 1000; i++) {
              System.out.println("t+"+i);
          }
      }
    }.start();
  • 实现Runnable接口方式

    //匿名内部类
    new Thread(new Runnable() {
      @Override
      public void run() {
          for (int i = 0; i <1000; i++) {
              System.out.println("mr"+i);
          }
      }
    }).start();

    Runnable接口是一个函数式接口,可以直接用Lambda表达式代替:

    //Lambda表达式
    new Thread(()->{
      for (int i = 0; i <1000; i++) {
          System.out.println("mr"+i);
      }
    }).start();

方式三:实现Callable接口

  • 步骤:
    1. 创建实体类,实现Callable接口
    2. 实现接口中的call()方法
    3. 利用 ExecutorService线程池对象 的 <T> Future<T> submit(Callable<T> task()方法提交该Callable接口的线程任务。
// 创建线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以执行Runnable对象或者Callable对象代表的线程
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));

// V get()
Integer i1 = f1.get();
Integer i2 = f2.get();

System.out.println(i1);
System.out.println(i2);
// 结束
pool.shutdown();
public class MyCallable implements Callable<Integer> {

    private int number;
    public MyCallable(int number) {
        this.number = number;
    }
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int x = 1; x <= number; x++) {
            sum += x;
        }
        return sum;
    }

}
  • 利用匿名内部类方式:
ExecutorService service = Executors.newSingleThreadExecutor();
         Future<String> future = service.submit(new Callable() {
             @Override
             public String call() throws Exception {
                 return "通过实现Callable接口";
             }
         });
         try {
             String result = future.get();
             System.out.println(result);
         } catch (InterruptedException e) {
             e.printStackTrace();
         } catch (ExecutionException e) {
             e.printStackTrace();
         }
  • Lambda表达式方式:
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //使用Executors工厂类创建一个单线程池
        ExecutorService es =  Executors.newSingleThreadExecutor();
        //使用这个单线程提交一个Callable接口线程服务,返回值为String
        //Callable接口是一个函数式接口,Java8开始可以直接使用Lambda表达式表示
        //其内部实现了call()方法  V call() throws Exception;
        //并得到该结果值打印
        System.out.println( es.submit(()->"使用lambda表达式的Callable接口").get());
        es.shutdown(); //关闭该线程池
    }

}
  • 实现callable接口,提交给ExecutorService返回值异步执行的。
  • 该方式的优缺点:
    • 优点:
    • 有返回值
    • 可以抛出异常
    • 缺点:
    • 代码较复杂,需要利用线程池

猜你喜欢

转载自blog.csdn.net/hxhaaj/article/details/81004712