文章

Java 多线程基础

2020.12.4 ・ 共 2037 字,您可能需要 5 分钟阅读

Tags: 多线程, Java, 学习笔记

Java 支持多线程开发。在进行并发访问处理时可得到更高的处理性能。

  • 进程:DOS 采用单进程处理,最大的特点就是同一个时间,只运行一个程序。Windows 之后开始了多进程,进行资源的轮流抢占。
  • 线程:在进程基础之上划分的更小的计算单元,依赖于进程,线程的启动速度比进程要快很多,多线程进行并发处理的时候,性能要高于进程。

java.lang.Thread 类为实现多线程类的父类,但并不是实现了这个类就能够实现多线程处理。因为还需要覆写Thread类中public void run()

class MyThread extends Thread {
  public String title;

  public MyThread(String title) {
    this.title = title;
  }

  @Override
  public void run() {
    for (int i = 0; i < 10; i++) {
      System.out.println(this.title + ", i =" + i);
    }
  }
}

多线程要执行的方法都应该在run() 中表明,但是run()是不能被直接调用的,要启动多线程必须使用start().

假如主函数如此定义:

public class Test {
  public static void main(String[] args) {
    new MyThread("A").run();
    new MyThread("B").run();
    new MyThread("C").run();
  }
}

结果:

A, i =0
A, i =1
A, i =2
A, i =3
A, i =4
A, i =5
A, i =6
A, i =7
A, i =8
A, i =9
B, i =0
B, i =1
B, i =2
B, i =3
B, i =4
B, i =5
B, i =6
B, i =7
B, i =8
B, i =9
C, i =0
C, i =1
C, i =2
C, i =3
C, i =4
C, i =5
C, i =6
C, i =7
C, i =8
C, i =9

使用start()时:

public class Test {
  public static void main(String[] args) {
    new MyThread("A").start();
    new MyThread("B").start();
    new MyThread("C").start();
  }
}

结果:

C, i =0
C, i =1
B, i =0
A, i =0
B, i =1
C, i =2
B, i =2
A, i =1
B, i =3
C, i =3
B, i =4
A, i =2
B, i =5
C, i =4
B, i =6
A, i =3
B, i =7
C, i =5
B, i =8
A, i =4
B, i =9
C, i =6
A, i =5
C, i =7
A, i =6
C, i =8
A, i =7
A, i =8
A, i =9
C, i =9

此时,多线程才启动。每一个线程对象只允许启动一次。

观察源码:

public synchronized void start() {
    /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
        }
    }
}

private native void start0();

其中的start0()使用了JNI技术(Java Native Interface), 但是Java开发过程之中并不推荐这样使用。Thread类所提供的start0()就表示此方法依赖于不同的操作系统来实现。未命名绘图

继承Thread受限于单继承,因此有第二种形式提供多线程实现:java.lang.Runnable接口,此接口是一个函数式接口,只有一个run(),因此可以使用lambda表达式操作。

实现此接口时,由于不再继承Thread,那么实现的类也就没有start(), 如果不使用start()无法进行多线程启动。

在Thread构造方法:public Thread(Runnable target)中接收了此接口。

class MyThread implements Runnable {
  public String title;

  public MyThread(String title) {
    this.title = title;
  }

  @Override
  public void run() {
    for (int i = 0; i < 10; i++) {
      System.out.println(this.title + ", i =" + i);
    }
  }
}

public class Test {
  public static void main(String[] args) {
    Thread threadA = new Thread(new MyThread("A"));
    threadA.start(); // 启动多线程
    Thread threadB = new Thread(new MyThread("B"));
    threadB.start(); // 启动多线程
    Thread threadC = new Thread(new MyThread("C"));
    threadC.start(); // 启动多线程
  }
}

也可以利用lambda表达式进行定义:

public class Test {
  public static void main(String[] args) {
    for (int x = 0; x < 3; x++) {
      String title = String.valueOf(x);
      Runnable run =
          () -> {
            for (int i = 0; i < 10; i++) {
              System.out.println(title + ", i =" + i);
            }
          };
      new Thread(run).start();
    }
  }
}

简化版

public class Test {
  public static void main(String[] args) {
    for (int x = 0; x < 3; x++) {
      String title = String.valueOf(x);
      new Thread(
              () -> {
                for (int i = 0; i < 10; i++) {
                  System.out.println(title + ", i =" + i);
                }
              })
          .start();
    }
  }
}

多线程的本质是在于多个线程可以进行统一资源的抢占未命名绘图

class MyThread implements Runnable {
  private int ticket = 5;

  @Override
  public void run() {
    for (int x = 0; x < 100; x++) {
      if (this.ticket >= 0) {
        System.out.println("卖票,票数 = " + this.ticket--);
      }
    }
  }
}

public class Test {
  public static void main(String[] args) {
    MyThread mt = new MyThread();
    new Thread(mt).start(); // 第一个线程启动
    new Thread(mt).start(); // 第二个线程启动
    new Thread(mt).start(); // 第三个线程启动
  }
}

分析本程序执行结构:

未命名绘图

Runnable接口有一个缺点,就是无法获取返回值。从 JDK 1.5 之后就提出了一个新的线程实现接口:java.util.concurrent.Callable ( JUC )

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

未命名绘图

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class MyThread implements Callable<String> {
  public String call() throws Exception {
    for (int x = 0; x < 10; x++) {
      System.out.println("线程执行x = " + x);
    }
    return "执行完毕";
  }
}

public class Test {
  public static void main(String[] args) throws Exception {
    FutureTask<String> task = new FutureTask<>(new MyThread());
    new Thread(task).start();
    System.out.println("线程返回数据" + task.get());
  }
}
  1. Runnable是在 JDK 1.0 的时候提出的多线程的实现接口,Callable是在 JDK 1.5 提出的。
  2. Runnable 只有一个run()没有返回值。
  3. Callable 有call(),有返回值。

多线程开发过程:定义线程主体类,实现接口,通过 Thread 对象,调用主体对象。并不意味调用了start()线程就会运行,因为线程有一套自己的运行状态。

image-20201204212107828

  1. 任何一个线程的对象都应该使用 Thread 进行封装,线程通过start()启动,启动之后进入就绪状态,并没有马上执行。
  2. 进入到就绪状态之后,就等待调度,调度成功则run()。但不能一直执行,中间需要产生一些暂停的状态,例如:某些线程执行一段之间之后就需要让出资源。而后就进入到阻塞状态,当阻塞接触之后则回到就绪。
  3. run()执行完毕之后,该线程的主要任务就执行结束,进入到停止状态。