线程使用是个很有意思的概念,他让一个程序可以分多路并发的执行。在Java学习最初,说到线程脑海里呈现的概念就是Thread类和Runable接口。这里整理下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(); } }
```
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就没有太大意义了。
参考:Java线程的6种状态及切换(透彻讲解)
```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;
```
```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();
```
`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是进入就绪(已经唤醒,可以分时间片运行了)
`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; } } }
```
`public final void wait() throws InterruptedException`
线程等待
让出CPU资源和锁资源,进入等待/阻塞队列。
wait和sleep的区别:wait会释放锁,sleep不会释放锁。
`public final native void notify();
或public final native void notifyAll();`
obj.notify()唤醒在此对象监视器上等待的单个线程(等待/阻塞队列上的),选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
通常我们使用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变量 Futuresubmit = executorService.submit(myCallable); //获取结果并输出 System.out.println(submit.get()); //关闭线程池,否则程序会一直运行着,因为线程池还在等着被调线程 executorService.shutdown(); } static class MyCallable implements Callable { @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啊。我要他有何用?各位有知道的指导下。