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 call() throws Exception;
}
Future 就是关于详细的 Callable 任务的执行停止取消、查询能否完成、获取执行结果的。能够经过 get 办法获取 Callable 的 call 办法的执行结果,但是要留意该办法会阻塞直到任务返回结果。
public interface Future
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
@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 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() 办法才行。