共计 5530 个字符,预计需要花费 14 分钟才能阅读完成。
download:WEB 前端线上零碎课 2022 最新 coderwhy
想到多线程并发就心虚?先来巩固下这些线程基础学识吧!
计算机系统里每个过程(Process)都代表着一个运行着的程序,过程是对运行时程序的封装,零碎进行资源调度和调配的基本单位。
一个过程下可能有很多个线程,线程是过程的子工作,是 CPU 调度和分派的基本单位,用于保障程序的实时性,实现过程外部的并发,线程同时也是操作系统可辨认的最小执行和调度单位。
在 Java 里线程是程序执行的载体,咱们写的代码就是由线程运行的。有的时候为了减少程序的执行效率,咱们不得不使用多线程进行编程,诚然多线程能最大化程序利用 CPU 的效率,但也是程序事变多发、程序员脱发的最大诱因。次要是平时咱们的思维默认是单线程的,写多线程的时候得可能切换一下才行,这就申请咱们对线程的基础学识了解的比较透彻。
Java 中的线程
到目前为止,咱们写的所有 Java 程序代码都是在由 JVM 给创建的 Main Thread 中单线程里执行的。Java 线程就像一个虚构 CPU,可能在运行的 Java 应用程序中执行 Java 代码。当一个 Java 应用程序启动时,它的入口方法 main() 方法由主线程执行。主线程(Main Thread)是一个由 Java 虚拟机创建的运行你的应用程序的非凡线程。
因为 Java 里所有皆对象,所以线程也是用对象示意的,线程是类 java.lang.Thread 类或者其子类的实例。在 Java 应用程序外部,咱们可能通过 Thread 实例创建和启动更多线程,这些线程可能与主线程并行执行应用程序的代码。
创建和启动线程
在 Java 中创建一个线程,就是创建一个 Thread 类的实例
Thread thread = new Thread();
启动线程就是调用线程对象的 start() 方法
thread.start();
当然,这个例子没有指定线程要执行的代码,所以线程将在启动后立即停止。
指定线程要执行的代码
有两种方法可能给线程指定要执行的代码。
第一种是,创建 Thread 的子类,覆盖父类的 run() 方法,在 run() 方法中指定线程要执行的代码。
第二种是,将实现 Runnable (java.lang.Runnable) 的对象传送给 Thread 构造方法,创建 Thread 实例。
其实,还有第三种用法,不过细究下来可归类到第二种的非凡使用形式,上面咱们看看这三种的用法和区别。
通过 Thread 子类指定要执行的代码
通过继承 Thread 类创建线程的步骤:
定义 Thread 类的子类,并覆盖该类的 run() 方法。run() 方法的方法体就代表了线程要实现的工作,因此把 run 方法称为执行体。
创建 Thread 子类的实例,即创建了线程对象。
调用线程对象的 start 方法来启动该线程。
package com.learnthread;
public class ThreadDemo {
public static void main(String[] args) {
// 实例化线程对象
MyThread threadA = new MyThread("Thread 线程 -A");
MyThread threadB = new MyThread("Thread 线程 -B");
// 启动线程
threadA.start();
threadB.start();}
static class MyThread extends Thread {
private int ticket = 5;
MyThread(String name) {super(name);
}
@Override
public void run() {while (ticket > 0) {System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "张票");
ticket--;
}
}
}
}
下面的程序,主线程启动调用 A、B 两个线程的 start() 后,并没有通过 wait() 等待他们执行结束。A、B 两个线程的执行体,会并发地被零碎执行,等线程都间接结束后,程序才会退出。
通过实现 Runnable 接口指定要执行的代码
Runnable 接口里,只有一个 run() 方法的定义:
package java.lang;
public interface Runnable {
public abstract void run();
}
其实,Thread 类实现的也是 Runnable 接口。
在 Thread 类的重载构造方法里,反对接收一个实现了 Runnale 接口的对象作为其 target 参数来初始化线程对象。
public class Thread implements Runnable {
...
public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);
}
...
public Thread(Runnable target, String name) {init(null, target, name, 0);
}
...
}
通过实现 Runnable 接口创建线程的步骤:
定义 Runnable 接口的实现,实现该接口的 run 方法。该 run 方法的方法体同样是线程的执行体。
创建 Runnable 实现类的实例,并以此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象。
调用线程对象的 start 方法来启动线程并执行。
package com.learnthread;
public class RunnableDemo {
public static void main(String[] args) {
// 实例化线程对象
Thread threadA = new Thread(new MyThread(), "Runnable 线程 -A");
Thread threadB = new Thread(new MyThread(), "Runnable 线程 -B");
// 启动线程
threadA.start();
threadB.start();}
static class MyThread implements Runnable {
private int ticket = 5;
@Override
public void run() {while (ticket > 0) {System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "张票");
ticket--;
}
}
}
}
运行下面例程会有以下输入,同样程序会在所以线程执行完后退出。
Runnable 线程 -B 卖出了第 5 张票
Runnable 线程 -B 卖出了第 4 张票
Runnable 线程 -B 卖出了第 3 张票
Runnable 线程 -B 卖出了第 2 张票
Runnable 线程 -B 卖出了第 1 张票
Runnable 线程 -A 卖出了第 5 张票
Runnable 线程 -A 卖出了第 4 张票
Runnable 线程 -A 卖出了第 3 张票
Runnable 线程 -A 卖出了第 2 张票
Runnable 线程 -A 卖出了第 1 张票
Process finished with exit code 0
既然是给 Thread 传送 Runnable 接口的实现对象即可,那么除了一般的定义类实现接口的形式,咱们还可能使用匿名类和 Lambda 表达式的形式来定义 Runnable 的实现。
使用 Runnable 的匿名类作为参数创建 Thread 对象:
Thread threadA = new Thread(new Runnable() {
private int ticket = 5;
@Override
public void run() {while (ticket > 0) {System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "张票");
ticket--;
}
}
}, “Runnable 线程 -A”);
使用实现了 Runnable 的 Lambda 表达式作为参数创建 Thread 对象:
Runnable runnable = () -> { System.out.println(“Lambda Runnable running”); };
Thread threadB = new Thread(runnable, “Runnable 线程 -B”);
因为,Lambda 是无状态的,定义不了外部属性,这里就举个简略的打印一行输入的例子了,理解一下这种用法即可。
获取线程的执行后果
下面两种方法诚然能指定线程执行体里要执行的工作,然而都没有返回值,如果想让线程的执行体方法有返回值,且能被内部创建它的父线程获取到返回值,就需要拆散 J.U.C 里提供的 Callable、Future 接口来实现线程的执行体方法才行。
J.U.C 是 java.util.concurrent 包的缩写,提供了很多并发编程的工具类,前面会粗疏学习。
Callable 接口只申明了一个方法,这个方法叫做 call():
package java.util.concurrent;
public interface Callable<V> {
V call() throws Exception;
}
Future 就是对于具体的 Callable 工作的执行进行勾销、查问是否实现、获取执行后果的。可能通过 get 方法获取 Callable 的 call 方法的执行后果,然而要注意该方法会阻塞直到工作返回后果。
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
Java 的 J.U.C 里给出了 Future 接口的一个实现 FutureTask,它同时实现了 Future 和 Runnable 接口,所以,FutureTask 既可能作为 Runnable 被线程执行,又可能作为 Future 失去 Callable 的返回值。
上面是一个 Callable 实现类和 FutureTask 拆散使用让主线程获取子线程执行后果的一个简略的示例:
package com.learnthread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableDemo implements Callable<Integer> {
@Override
public Integer call() {
int i = 0;
for (i = 0; i < 20; i++) {if (i == 5) {break;}
System.out.println(Thread.currentThread().getName() + " " + i);
}
return i;
}
public static void main(String[] args) {CallableDemo tt = new CallableDemo();
FutureTask<Integer> ft = new FutureTask<>(tt);
Thread t = new Thread(ft);
t.start();
try {System.out.println(Thread.currentThread().getName() + " " + ft.get());
} catch (Exception e) {e.printStackTrace();
}
}
}
下面咱们把 FutureTask 作为 Thread 构造方法的 Runnable 类型参数 target 的实参,在它的基础上创建线程,
执行逻辑。所以本质上 Callable + FutureTask 这种形式也是第二种通过实现 Runnable 接口给线程指定执行体的,只不过是由 FutureTask 包装了一层,由它的 run 方法再去调用 Callable 的 call 方法。例程运行后的输入如下:
Thread-0 0
Thread-0 1
Thread-0 2
Thread-0 3
Thread-0 4
main 5
Callable 更罕用的形式是拆散线程池来使用,在线程池接口 ExecutorService 中定义了多个可接收 Callable 作为线程执行工作的方法 submit、invokeAny、invokeAll 等,这个等学到线程池了咱们再去学习。
初学者常见陷阱 – 在主线程里调用 run()
在刚开始接触和学习 Java 线程相干的学识时,一个常见的谬误是,在创建线程的线程里,调用 Thread 对象的 run() 方法而不是调用 start() 方法。
Runnable myRunnable = new Runnable() {
@Override
public void run() {System.out.println("Anonymous Runnable running");
}
};
Thread newThread = new Thread(myRunnable);
newThread.run(); // 应该调用 newThread.start();
起初你可能没有留意到这么干有啥错,因为 Runnable 的 run() 方法失常地被执行,输入了咱们想要的后果。
然而,这么做 run() 不会由咱们刚刚创建的新线程执行,而是由创建 newThread 对象的线程执行的。要让新创建的线程 –newThread 调用 myRunnable 实例的 run() 方法,必须调用 newThread.start() 方法才行。