1. 前言
线程使用是个很有意思的概念,他让一个程序可以分多路并发的执行。在Java学习最初,说到线程脑海里呈现的概念就是Thread类和Runable接口。这里整理下Thread类和Runable是怎么实现多线程的。
2. Thread类和Runable接口的使用——快速入门
Thread类和Runable接口的关系:Thread实现了Runable接口,主要是要来集成Runable中的run方法,让Thread自带个了Runable,同时Runable也可以作为参数传递进Thread方法。

继承Thread来实现多线程:
public class TestTread {
public static void main(String[] args) {
new Thread(()->{
System.out.println("测试Tread类的使用,使用lambda表达式实现");
}).start();
}
}
实现Runable接口并传递给Thread来实现多线程:
```java
public class MyRunable implements Runnable {
@Override
public void run() {
System.out.println("测试Runable接口的使用");
}
}
public class TestRunable {
public static void main(String[] args) {
new Thread(new MyRunable()).start();
}
}
```
3. 线程的基础知识
线程的状态
-
NEW:新建,线程初始化后的状态。
-
RUNNABLE:运行,可以分为REDY(就绪)和RUNNING(运行中)两个状态。
-
BLOCKED:阻塞,线程不能获得到锁时的状态
-
WAITING:无限期等待,线程处于无指定时间等待时的状态。如:sleep()、join()、wait()被调用
-
TIMED_WAITING:限期等待:线程处于有指定时间的等待时的状态。如sleep(timeout)、join(timeout)、wait(timeout)
-
TERMINATED:结束:线程终止状态当线程正常结束或者调用interrupt方法后线程

关于为什么就绪状态和运行状态合并为RUNNABLE?
现在的时分多任务操作系统架构通常都是用所谓的“时间分片”方式进行抢占式轮转调度。这个时间分片通常是很小的,一个线程一次最多只能在cpu上运行10-20ms的时间。通常java的线程状态是用于监控的,如果线程切换得如此之快,那么区分ready和running就没有太大意义了。
Thread对象的重要组成和初始化过程
```java
/**线程名*/
private volatile char name[];
// 优先级
private int priority;
//
private Thread threadQ;
private long eetop;
/* Whether or not to single_step this thread. */
private boolean single_step;
/* Whether or not the thread is a daemon thread. */
// 是否是守护线程,true 是守护线程
// 如果是守护线程的话,是不能够阻止 JVM 的退出的,级别很低
private boolean daemon = false;
/* JVM state */
private boolean stillborn = false;
/**Runable的实现类,线程真正调用的是它的run方法
*线程组是单例的,线程组可以对组内的线程进行批量的操作。
*/
/* What will be run. */
private Runnable target;
/**线程组:每个线程都会被加到线程组中,group.add(this)*/
/* The group of this thread */
private ThreadGroup group;
```
4. 多线程常用的方法
start方法
```java
// 该方法可以创建一个新的线程出来,返回的仍然是主线程
public synchronized void start() {
// 1.对线程状态判断是否为0,即NEW。如果没有初始化,抛异常
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 2.把线程加入线程组
group.add(this);
// started 是个标识符,我们在初始化一些东西的时候,经常这么写
boolean started = false;
try {
// 3.这里会创建一个新的线程,执行完成之后,新的线程已经在运行了,既 target 的内容已经在运行了
start0();
// 这里执行的还是主线程
started = true;
} finally {
try {
// 4.如果失败,把线程从线程组中删除
if (!started) {
group.threadStartFailed(this);
}
// 这里的 catch 捕捉也是值得我们学习的,我们在工作中 catch 时也应该多用 Throwable,少用 exception
// 比如对于异步线程抛出来的异常,Exception 是捕捉不住的,Throwable 却可以
} catch (Throwable ignore) {
//什么也不做。如果start0抛出一个Throwable然后它将被传递到调用堆栈
}
}
}
private native void start0();
```
Thread的静态方法
`public static native void yield();`
线程让步
把CPU让出去,然后当前线程和其他线程一起竞争。避免线程过段使用CPU。
让步不是不执行而是进入REDY,也有可能重新选中自己。
`public static native void sleep(long millis) throws InterruptedException;`
线程睡眠:
把CPU让出去指定时间(时间接受毫秒和纳秒两个入参,如果给定的纳秒大于等于0.5毫秒,算一个毫秒,否则不算)
让出CPU自我沉睡时不会释放锁,执行后进入TIMED_WAITING
sleep和yield的区别:sleep会进入等待,要等待notify来唤醒,而yield是进入就绪(已经唤醒,可以分时间片运行了)
Thread的普通方法
`public final synchronized void join(long millis)`
一种特殊的wait,底层是循环调用wait方法,直到线程不是RUNABLE状态。(判断的当前线程不是RUNABLE状态,)
当前运行线程调用另一线程的join方法:判断另一线程的状态为RUNABLE时,就一直调用wait方法,当前线程会等待。
即当前线程进入阻塞状态直到另一个线程运行结束等待该线程终止。可以达到让步给另一个线程执行,另一个线程执行完当前线程接着执行。
join和wait的区别:他会一直多次调用wait方法知道指定Thread对象的线程结束了,这段时间不会被notify唤醒。
```java
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
// 其他线程好了之后,当前线程的状态是 TERMINATED,isAlive 返回 false
// NEW false
// RUNNABLE true
while (isAlive()) {
// 等待其他线程,一直等待
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
// 等待一定的时间,如果在 delay 时间内,等待的线程仍没有结束,放弃等待
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
```
object的普通方法
`public final void wait() throws InterruptedException`
线程等待
让出CPU资源和锁资源,进入等待/阻塞队列。
wait和sleep的区别:wait会释放锁,sleep不会释放锁。
`public final native void notify();或public final native void notifyAll();`
obj.notify()唤醒在此对象监视器上等待的单个线程(等待/阻塞队列上的),选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
5.异步调用
通常我们使用Thread调用线程,但是Thread不能返回结果,这个时候我们就引出了这节的主角——Callable接口、Future接口和ExecutorService。
首先介绍Callable接口,你可以把他看做是有返回结果的Runable接口。Runable接口的线程逻辑写在run方法中,Callable接口的线程逻辑写在call方法中。(但是Callable接口可没有类比Thread的实现类,不能直接像Thread那样简单的start开启线程)
Future接口的主要方法就是get方法,Callable的call方法在另一个线程执行完成后,通过get方法可以在当前线程获取到这个结果。(注:Future接口还有判断任务是否完成、能够中断任务等功能,这里不展开)
到这里可能就要问了,没有Thread的start怎么开启线程?Future和Callable两个接口怎么就能把结果互相传递了?
这个时候就要引入ExecutorService这个接口了。他有一个很牛逼的实现类——线程池ThreadPoolExecutor。
我们一般把一个线程池实例赋值给ExecutorService接口,然后调用ExecutorService的Future<> submit(Callable callable)方法。在这个方法里完成线程开启,逻辑代码执行,结果交给Future返回。
代码实例如下:
```java
/**
* @author houyi
* @date 2020/8/1 20:37
* Description:
*/
public class TestCallable {
public static void main(String[] args) throws Exception {
MyCallable myCallable = new MyCallable();
//构造线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//从线程池中拿一个线程来执行callable并返回Future到submit变量
Future<String> submit = executorService.submit(myCallable);
//获取结果并输出
System.out.println(submit.get());
//关闭线程池,否则程序会一直运行着,因为线程池还在等着被调线程
executorService.shutdown();
}
static class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("我的Callable实现类调用了call方法");
return "我的Callable实现类调用了call方法并且返回了";
}
}
}
```
最后还要提一下,还有一个FutureTask类是干什么的了?
我最开始也一直以为这是Callable的Thread,结果肯定不是的。FutureTask继承Runable和Future两个接口,主要的构造有传入Callable和传入Runable两种。在它的构造器里一行代码说明了它存在的意义:

Runable接口实现类传进去,通过转换变成了Callable接口实现类,这样就可以让Runable也能返回结果了。实现了兼容旧版的Runable线程逻辑返回结果。不对啊,Runable的run方法返回为void啊。我要他有何用?各位有知道的指导下。

被折叠的 条评论
为什么被折叠?



