> For the complete documentation index, see [llms.txt](https://shepherd-xie.gitbook.io/be-a-javaer/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://shepherd-xie.gitbook.io/be-a-javaer/di-3-zhang-java-gao-ji-bian-cheng/section-1.md).

# 第 1 节 Java多线程基础实现

***

## 线程与进程

线程与进程的区别。

Java是一门为数不多的多线程支持的编程语言。

如果要想结束多线程之前首先需要来知道什么叫进程？在操作系统定义中，进程指的是一次程序的完整运行，这个运行的过程之中、内存、处理器、IO等资源都要为这个进程进行服务。

在同一个时间段上，会有多个进程去抢占资源，但是在某一个时间点上，只会有一个进程运行。

线程是在进程基础上进一步的划分结果，即：一个进程上可以同时创建多个线程。

线程是比进程更快的处理单元，而且所占的资源也小。

## 多线程的实现

掌握Java中三种多线程的实现方式（JDK1.5之后增加了第三种）。

如果想要在Java之中是实现多线程有两种途径：

* 继承Thread类；
* 实现Runnable接口（Callable接口）；

## 继承Thread类

Thread类是一个支持多线程的功能类，只要有一个子类它就可以实现多线程的支持。

```java
class MyThread extends Thread { // 这就是一个多线程的操作类
}
```

所有程序的起点是main()方法，但是所有线程也一定要有一个自己的起点，那么这个起点就是run()方法，也就是说在多线程的每个主体类之中都必须覆写Thread类中所提供的run()方法。

```java
public void run() {}
```

这个方法上没有返回值，那么也就表示了线程一旦开始就要一直执行，不能够返回内容。

```java
// 线程操作主类
class MyThread extends Thread { // 这就是一个多线程的操作类
	private String name; // 定义类中的属性
	public MyThread(String name) { // 定义构造方法
		this.name = name;
	}
	@Override
	public void run() { // 覆写run()方法，作为线程的主体操作方法
		for (int i = 0; i < 200; i ++) {
			System.out.println(this.name + " ---> " + i);
		}
	}
}
```

本程序类的功能是进行循环的输出操作，所有的线程与线程是一样的，都必须轮流去抢占资源，所以多线程的执行应该是多个线程彼此交替执行，也就是说如果直接调用了run()方法，并不能启动多线程，多线程启动的唯一方法就是Thread类中的start()方法：public void start()（调用此方法执行的方法体是run()方法定义的）。

```java
public class MainClass { // 主类
	public static void main(String[] args) {
		MyThread mt1 = new MyThread("线程A");
		MyThread mt2 = new MyThread("线程B");
		MyThread mt3 = new MyThread("线程C");
		mt1.start();
		mt2.start();
		mt3.start();
	}
}
```

此时每一个线程对象交替执行。

\*\*疑问？\*\*为什么多线程启动不是调用run()而必须调用start()？

打开Java的源代码，来观察一下start()方法的定义：

```java
/**
 * Causes this thread to begin execution; the Java Virtual Machine
 * calls the <code>run</code> method of this thread.
 * <p>
 * The result is that two threads are running concurrently: the
 * current thread (which returns from the call to the
 * <code>start</code> method) and the other thread (which executes its
 * <code>run</code> method).
 * <p>
 * It is never legal to start a thread more than once.
 * In particular, a thread may not be restarted once it has completed
 * execution.
 *
 * @exception  IllegalThreadStateException  if the thread was already
 *               started.
 * @see        #run()
 * @see        #stop()
 */
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();
```

首先方法在Thread类的start()方法里面存在有一个“IllegalThreadStateException”异常抛出。本方法里面使用了throw抛出异常，按照道理来讲应该使用try...catch处理，或者在start()方法声明上使用throws声明，但是此处并没有这样的代码，因为此异常属于RuntimeException的子类，属于选择性处理。如果某一个线程对象重复进行了启动，那么就会抛出此异常。

发现在start()方法里面要调用一的start0()方法，而且此方法的结构与抽象方法类似，唯一不同的是使用了native声明，在Java的开发里面有一门技术称为JNI技术（JavaNativeInterface），这门技术的特点：使用Java调用本机操作系统提供的函数。但是这样的技术有一个缺点，不能离开特定的操作系统。

如果想要线程能够执行，需要操作系统来进行资源分配，所以此操作严格来讲主要是由JVM负责根据不同的操作系统而实现的。

***即：使用Thread类的start()方法不仅仅要启动多线程的执行代码，还要去根据不同的操作系统进行资源的分配。***

## 实现Runnable接口

虽然Thread类可以是实现多线程的主体类定义，但是它有一个问题，Java的单继承局限，正因为如此，在任何情况下针对与类的继承都应该是回避的问题，那么多线程也一样，为了解决单继承局限的问题，在Java里面专门提供了Runnable接口。此接口定义如下：

```java
@FunctionalInterface
public interface Runnable {
	public void run();
}
```

在接口里面任何的方法都是public定义的权限，不存在默认的权限。

那么只需要让一个类实现Runnable接口即可，并且也需要覆写run()方法。

```java
// 线程操作主类
class MyThread implements Runnable { // 这就是一个多线程的操作类
	private String name; // 定义类中的属性
	public MyThread(String name) { // 定义构造方法
		this.name = name;
	}
	@Override
	public void run() { // 覆写run()方法，作为线程的主体操作方法
		for (int i = 0; i < 200; i ++) {
			System.out.println(this.name + " ---> " + i);
		}
	}
}
```

与继承Thread类相比，此时的MyThread类在结构上与之前是没有区别的。但是有一点需要注意的是，如果直接继承了Thread类，那么可以直接继承start()方法，但是如果实现的是Runnable接口，并没有start()方法可以被继承。

不管何种情况下，如果要想启动多线程一定要依靠Thread类完成，在Thread类里面定义有一下的构造方法：

* 构造方法：public Thread(Runnable target)，接收的是Runnable接口对象;

**范例：** 启动多线程

```java
public class MainClass { // 主类
	public static void main(String[] args) {
		MyThread mt1 = new MyThread("线程A");
		MyThread mt2 = new MyThread("线程B");
		MyThread mt3 = new MyThread("线程C");
		new Thread(mt1).start();
		new Thread(mt2).start();
		new Thread(mt3).start();
	}
}
```

此时就避免了单继承局限，那么也就是说在实际工作中使用Runnable接口是最合适的。

## 多线程两种实现方式的区别？（面试题）

首先一定要明确的是，使用Runnable接口与Thread类相比，解决了单继承的定义局限，所以不管后面的区别与联系是什么，至少这一点上就已经有了取舍 —— 如果要使用，一定使用Runnable接口。

观察Thread类的定义。

```java
public class Thread extends Object implements Runnable
```

发现Thread类实现了Runnable接口。那么这样一来程序就变为了以下的形式。

![](/files/-M51Q9sEwfrYz_MY1EFJ)

此时整个的定义结构看起来非常像代理设计模式，如果是代理设计模式，客户端调用的代理类的方法也应该是接口里提供的方法，那么也应该是run()才对。

除了以上的联系之外，还有一点：使用Runnable接口可以比Thread类能够更好的描述出数据共享这一概念。此时的数据共享指的是多个线程访问统一资源的操作。

**范例：** 观察代码（每一个线程对象都必须通过start()启动）

```java
package com.alpha;
class MyThread extends Thread { // 这就是一个多线程的操作类
	private int ticket = 10;
	@Override
	public void run() { // 覆写run()方法，作为线程的主体操作方法
		for (int i = 0; i < 100; i ++) {
			if (this.ticket > 0) {
				System.out.println("卖票，ticket = " + this.ticket --);
			}
		}
	}
}
public class MainClass { // 主类
	public static void main(String[] args) {
		MyThread mt1 = new MyThread();
		MyThread mt2 = new MyThread();
		MyThread mt3 = new MyThread();
		mt1.start();
		mt2.start();
		mt3.start();
	}
}
```

本程序声明了三个MyThread类的对象，并且分别调用了三次start()方法，启动线程对象。但是发现最终的结果是每一个线程对象都在卖各自的10张票，此时并不存在数据共享。

**范例：** 利用Runnable实现

```java
package com.alpha;
class MyThread implements Runnable { // 这就是一个多线程的操作类
	private int ticket = 10;
	@Override
	public void run() { // 覆写run()方法，作为线程的主体操作方法
		for (int i = 0; i < 100; i ++) {
			if (this.ticket > 0) {
				System.out.println("卖票，ticket = " + this.ticket --);
			}
		}
	}
}
public class MainClass { // 主类
	public static void main(String[] args) {
		MyThread mt = new MyThread();
		new Thread(mt).start();
		new Thread(mt).start();
		new Thread(mt).start();
	}
}
```

\*\*面试题：\*\*请解释Thread类与Runnable接口实现多线程的区别？（\*\*面试题：\*\*请解释多线程两种实现方式的区别）

* Thread类是Runnable接口的子类，使用Runnable接口实现多继承可以避免单继承局限；
* Runnable接口实现的多线程可以比Thread类实现的多线程更加清楚的描述数据共享的概念。

\*\*面试题：\*\*请写出多线程两种实现操作。

如上。

## 第三种实现方式

使用Runnable接口实现的多线程可以避免单继承局限，但是Runnable里面的run()方法不能返回操作结果。为了解决这样的矛盾，提供了一个新的接口java.util.concurrent.Callable接口。

```java
@FunctionalInterface
public interface Callable<V> {
	public V call() throws Exception;
}
```

call()方法执行完线程的功能之后可以返回一个结果，而返回结果的类型由Callable接口上的泛型来决定。

**范例：** 定义一个线程主体类

```java
import java.util.concurrent.Callable;
class MyThread implements Callable<String> {
	private int ticket = 10;
	@Override
	public String call() throws Exception {
		for (int i = 0; i < 100; i ++) {
			if (this.ticket > 0) {
				System.out.println("卖票，ticket = " + this.ticket --);
			}
		}
		return "票已卖光！";
	}
}
```

此时观察Thread类里面发现并没有直接支持Callable接口的多线程应用。

从JDK1.5开始提供有java.util.concurrent.FutureTask类。这个类主要是负责Callable接口对象操作的，这个接口的定义结构：

```java
public class FutureTask<V> extends Object implements RunnableFuture<V>
```

```java
public interface RunnableFuture<V> extends Runnable, Future<V>
```

在FutureTask类里面定义有如下的构造方法：public FutureTask(Callable callable)

接收的目的只有一个，那么就是取得call()方法的返回结果。

```java
public class MainClass { // 主类
	public static void main(String[] args) throws Exception {
		MyThread mt1 = new MyThread();
		MyThread mt2 = new MyThread();
		FutureTask<String> task1 = new FutureTask<String>(mt1); // 目的是为了取得cvall()返回结果
		FutureTask<String> task2 = new FutureTask<String>(mt2); // 目的是为了取得cvall()返回结果
		// FuntureTask是Runnable接口子类，所以可以使用Thread类的构造来接收task对象
		new Thread(task1).start();
		new Thread(task2).start();
		// 多线程执行完毕之后可以取得内容，依靠FutureTask的父接口Future中的get()方法完成
		System.out.println("A线程的返回结果：" + task1.get());
		System.out.println("B线程的返回结果：" + task2.get());
	}
}
```

最麻烦的问题在于需要接受返回值信息，并且又要与原始的多线程的实现靠拢（向Thread类靠拢）。

### 总结

1、对于多线程的实现，重点在于Runnable接口与Thread类启动的配合上；

2、对于JDK1.5新特性，了解就行了，知道区别就在于返回结果上。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://shepherd-xie.gitbook.io/be-a-javaer/di-3-zhang-java-gao-ji-bian-cheng/section-1.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
