Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。 这里定义和线程相关的另一个术语 进程:
- 一个进程包括由操作系统分配的内存空间,包含一个或多个线程。
- 一个线程不能独立的存在,它必须是进程的一部分。
- 一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。 并发与并行 并发 指两个或多个事件在同一个时间段内发生 并行 指两个或多个事件在同一时刻发生(同时发生)
- 在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。
- 而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行, 即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核越多,并行处理的程序越多,能大大的提高电脑运行的效率
- 单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同 理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个 线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为 线程调度
在多线程编程时,你需要了解以下几个概念:
- 线程同步
- 线程间通信
- 线程死锁
- 线程控制:挂起、停止和恢复
线程与进程
进程
指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多 个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创 建、运行到消亡的过程
线程
线程是进程中的一个执行单元,负责当前进程中程序的执行
- 一个进程中至少有一个线程
- 一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序
- 多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈
线程调度:
- 分时调度 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间
- 抢占式调度 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为 抢占式调度
- 主线程main和多线程的关系

线程的优先级
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java 线程的优先级是一个整数,其取值范围是 Thread.MIN_PRIORITY(1)-Thread.MAX_PRIORITY(10)
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
一个线程的生命周期
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
下图显示了一个线程完整的生命周期
- 新建状态 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
- 就绪状态 当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
- 运行状态 如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
- 阻塞状态 如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
- 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
- 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
- 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
- 死亡状态 一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态
创建一个线程
Java 提供了三种创建线程的方法:
- 通过实现 Runnable 接口;
- 通过继承 Thread 类本身;
- 通过 Callable 和 Future 创建线程
通过实现 Runnable 接口来创建线程
创建一个线程,最简单的方法是创建一个实现 Runnable 接口的类
定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体,声明如下:
public void run()
创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正 的线程对
Thread(Runnable threadOb,String threadName);
这里,threadOb 是一个实现 Runnable 接口的类的实例,并且 threadName 指定新线程的名字。
新线程创建之后,你调用它的 start() 方法它才会运行。
void start();
下面是一个创建线程并开始让它执行的实例:
class MyRunable implements Runnable {private Thread thread;private String threadName;MyRunable() {};MyRunable(String name) {threadName = name;}@Overridepublic void run() {for (int i = 0; i < 1; i++) {System.out.println(Thread.currentThread().getName());try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}public void start() {if (thread == null) {thread = new Thread(this, threadName);thread.start();}}}public class RunnableDemo {public static void main(String[] args) {// ---------------------------------------------------------------new Thread(new MyRunable(), "实现Runnable接口的线程").start();// ---------------------------------------------------------------new MyRunable("实现实现Runnable的类中调用Thread").start();// ---------------------------------------------------------------new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}}, "匿名内部类方式实现线程的创建").start();// ---------------------------------------------------------------new Thread(() -> {System.out.println(Thread.currentThread().getName());}, "用Lambda表达式实现Runnable接口的线程").start();// ---------------------------------------------------------------}}/*实现Runnable接口的线程实现实现Runnable的类中调用Thread匿名内部类方式实现线程的创建用Lambda表达式实现Runnable接口的线程*/
class RunnableDemo implements Runnable {private Thread thread;private String threadName;RunnableDemo(String name) {threadName = name;System.out.println("Creating " + threadName);}public void run() {System.out.println("Running " + threadName);try {for (int i = 4; i > 0; i--) {System.out.println("Thread: " + threadName + ", " + i);Thread.sleep(50); // 让线程睡眠一会}} catch (InterruptedException e) {System.out.println("Thread " + threadName + " interrupted.");}System.out.println("Thread " + threadName + " exiting.");}public void start() {System.out.println("Starting " + threadName);if (thread == null) {thread = new Thread(this, threadName);thread.start();}}}public class ThreadTestDemo {public static void main(String args[]) {RunnableDemo R1 = new RunnableDemo("Thread-1");R1.start();RunnableDemo R2 = new RunnableDemo("Thread-2");R2.start();}}/*编译以上程序运行结果如下:Creating Thread-1Starting Thread-1Creating Thread-2Starting Thread-2Running Thread-1Thread: Thread-1, 4Running Thread-2Thread: Thread-2, 4Thread: Thread-1, 3Thread: Thread-2, 3Thread: Thread-1, 2Thread: Thread-2, 2Thread: Thread-1, 1Thread: Thread-2, 1Thread Thread-1 exiting.Thread Thread-2 exiting.*/
通过继承Thread来创建线程
创建一个线程的第二种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例
Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例
- 继承类必须重写 run() 方法,该方法是新线程的入口点(线程执行体)
- 它也必须调用 start() 方法才能执行
- 创建Thread子类的实例,即创建了线程对象
该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。
class MyThread extends Thread {public MyThread(String name) { // 定义指定线程名称的构造方法super(name); // 调用父类的String参数的构造方法,指定线程的名称}// 重写run方法,完成该线程执行的逻辑@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(getName() + ":正在执行!" + i);}}}public class ThreadDemo {public static void main(String[] args) {MyThread mt = new MyThread("新的线程!"); // 创建自定义线程对象mt.start(); // 开启新线程for (int i = 0; i < 10; i++) { // 在主方法中执行for循环System.out.println("main线程!" + i);}}}/*main线程!0main线程!1新的线程!:正在执行!0新的线程!:正在执行!1新的线程!:正在执行!2新的线程!:正在执行!3新的线程!:正在执行!4新的线程!:正在执行!5main线程!2main线程!3新的线程!:正在执行!6新的线程!:正在执行!7新的线程!:正在执行!8新的线程!:正在执行!9main线程!4main线程!5main线程!6main线程!7main线程!8main线程!9*/
class ThreadDemo extends Thread {private Thread t;private String threadName;ThreadDemo(String name) {threadName = name;System.out.println("Creating " + threadName);}@Overridepublic void run() {System.out.println("Running " + threadName);try {for (int i = 4; i > 0; i--) {System.out.println("Thread: " + threadName + ", " + i);// 让线程睡眠一会Thread.sleep(50);}} catch (InterruptedException e) {System.out.println("Thread " + threadName + " interrupted.");}System.out.println("Thread " + threadName + " exiting.");}public void start() {System.out.println("Starting " + threadName);if (t == null) {t = new Thread(this, threadName);t.start();}}}public class ThreadTestDemo {public static void main(String args[]) {ThreadDemo T1 = new ThreadDemo("Thread-1");T1.start();ThreadDemo T2 = new ThreadDemo("Thread-2");T2.start();}}// 编译以上程序运行结果如下:// Creating Thread-1// Starting Thread-1// Creating Thread-2// Starting Thread-2// Running Thread-1// Thread: Thread-1, 4// Running Thread-2// Thread: Thread-2, 4// Thread: Thread-1, 3// Thread: Thread-2, 3// Thread: Thread-1, 2// Thread: Thread-2, 2// Thread: Thread-1, 1// Thread: Thread-2, 1// Thread Thread-1 exiting.// Thread Thread-2 exiting.
Thread 方法
下表列出了Thread类的一些重要方法:
| 方法 | 描述 |
|---|---|
| 构造方法: public Thread() : 分配一个新的线程对象。 程对象并指定名字 public Thread(String name) : 分配一个指定名字的新的线程对象 public Thread(Runnable target) : 分配一个带有指定目标新的线程对象 public Thread(Runnable target,String name) : 分配一个带有指定目标新的线 |
|
| public void start() | 使该线程开启 Java虚拟机调用该线程的 run 方法。 |
| public void run() | 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 |
| public final void setName(String name) | 改变线程名称,使之与参数 name 相同。 |
| public final void setPriority(int priority) | 更改线程的优先级。 |
| public final void setDaemon(boolean on) | 将该线程标记为守护线程或用户线程。 |
| public final void join(long millisec) | 等待该线程终止的时间最长为 millis 毫秒。 |
| public void interrupt() | 中断线程。 |
| public final boolean isAlive() | 测试线程是否处于活动状态。 |
| public static void sleep(long millis) | 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行) |
| public static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
测试线程是否处于活动状态。 上述方法是被Thread对象调用的。下面的方法是Thread类的静态方法。
| 序号 | 方法描述 |
|---|---|
| public static void yield() | 暂停当前正在执行的线程对象,并执行其他线程。 |
| public static void sleep(long millisec) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 |
| public static boolean holdsLock(Object x) | 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。 |
| public static Thread currentThread() | 返回对当前正在执行的线程对象的引用。 |
| public static void dumpStack() | 将当前线程的堆栈跟踪打印至标准错误流。 |
如下的ThreadClassDemo 程序演示了Thread类的一些方法:
// 通过实现 Runnable 接口创建线程class RunnableDisplayMessage implements Runnable {private String message;public RunnableDisplayMessage(String message) {this.message = message;}public void run() {while (true) {System.out.println(message);}}}// 通过继承 Thread 类创建线程class ThreadGuessANumber extends Thread {private int number;public ThreadGuessANumber(int number) {this.number = number;}public void run() {int counter = 0;int guess = 0;do {guess = (int) (Math.random() * 100 + 1);System.out.println(this.getName() + " guesses " + guess);counter++;} while (guess != number);System.out.println("** Correct!" + this.getName() + "in" + counter + "guesses.**");}}public class ThreadClassDemo {public static void main(String[] args) {Runnable hello = new RunnableDisplayMessage("Hello");Thread thread1 = new Thread(hello);thread1.setDaemon(true);thread1.setName("hello");System.out.println("Starting hello thread...");thread1.start();Runnable bye = new RunnableDisplayMessage("Goodbye");Thread thread2 = new Thread(bye);thread2.setPriority(Thread.MIN_PRIORITY);thread2.setDaemon(true);System.out.println("Starting goodbye thread...");thread2.start();System.out.println("Starting thread3...");Thread thread3 = new ThreadGuessANumber(27);thread3.start();try {thread3.join();} catch (InterruptedException e) {System.out.println("Thread interrupted.");}System.out.println("Starting thread4...");Thread thread4 = new ThreadGuessANumber(75);thread4.start();System.out.println("main() is ending...");}}/*运行结果如下,每一次运行的结果都不一样。Starting hello thread...Starting goodbye thread...HelloHelloHelloHelloHelloHelloGoodbyeGoodbyeGoodbyeGoodbyeGoodbye.......*/
join()
表示一个线程会无限等待至被等待线程死亡为止
class ThreadA extends Thread {public void run() {synchronized (this) {try {System.out.println("睡眠5秒");Thread.sleep(5000);System.out.println("睡完了");} catch (InterruptedException e) {e.printStackTrace();}}try {System.out.println("再睡眠5秒");Thread.sleep(5000);System.out.println("睡完了");} catch (InterruptedException e) {e.printStackTrace();}}}public class Test {public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();ThreadA a = new ThreadA();a.start();a.join(2000);long end = System.currentTimeMillis();System.out.println("共运行" + (end - start) / 1000 + "秒");}}
通过 Callable 和 Future 创建线程
- 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
- 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
- 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
- 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。 ```java import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask;
class CallableThreadDemo implements Callable
public class CallableThreadDemotest {
public static void main(String[] args) {
CallableThreadDemo ctt = new CallableThreadDemo();
FutureTask
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " 的循环变量i的值" + i);
if (i == 2) {
new Thread(ft, "CallableThreadDemotest有返回值的线程:").start();
}
}
try {
System.out.println("子线程的返回值:" + ft.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
} } ```
创建线程的三种方式的对比
实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去共享同一个资源
- 可以避免java中的单继承的局限
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立
- 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类
采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程
- 扩展:在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用 java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程
