Java多线程基础-多线程的实现方式

多线程的实现方式

方式一: 继承Thread类

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

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

  • 具体步骤:

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

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

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

  • 代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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方法。

      1
      2
      3
      public Thread(Runnable target) {
      init(null, target, "Thread-" + nextThreadNum(), 0);
      }
    • 2、追踪init方法,在init方法体中找到了传递的target参数,赋值给了Thread类的Runnable接口的成员变量的target

      1
      this.target = target;
      1
      2
      /* What will be run. */
      private Runnable target;
    • 3、查看run方法时,发现run方法中有判断,如果target不为null就会调用实现Runnable接口子类对象的run方法

      1
      2
      3
      4
      5
      6
      @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类方式

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //匿名内部类
    new Thread(new Runnable() {
    @Override
    public void run() {
    for (int i = 0; i <1000; i++) {
    System.out.println("mr"+i);
    }
    }
    }).start();

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

    1
    2
    3
    4
    5
    6
    //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接口的线程任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 创建线程池
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;
}

}
  • 利用匿名内部类方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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表达式方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
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返回值异步执行的。
  • 该方式的优缺点:
    • 优点:
      • 有返回值
      • 可以抛出异常
    • 缺点:
      • 代码较复杂,需要利用线程池