Java线程的使用

1. 前言

线程使用是个很有意思的概念,他让一个程序可以分多路并发的执行。在Java学习最初,说到线程脑海里呈现的概念就是Thread类和Runable接口。这里整理下Thread类和Runable是怎么实现多线程的。

2. Thread类和Runable接口的使用——快速入门

Thread类和Runable接口的关系:Thread实现了Runable接口,主要是要来集成Runable中的run方法,让Thread自带个了Runable,同时Runable也可以作为参数传递进Thread方法。

Java线程的使用_第1张图片

继承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. 线程的基础知识

线程的状态

  1. NEW:新建,线程初始化后的状态。

  2. RUNNABLE:运行,可以分为REDY(就绪)和RUNNING(运行中)两个状态。

  3. BLOCKED:阻塞,线程不能获得到锁时的状态

  4. WAITING:无限期等待,线程处于无指定时间等待时的状态。如:sleep()、join()、wait()被调用

  5. TIMED_WAITING:限期等待:线程处于有指定时间的等待时的状态。如sleep(timeout)、join(timeout)、wait(timeout)

  6. TERMINATED:结束:线程终止状态当线程正常结束或者调用interrupt方法后线程

线程状态图

 

关于为什么就绪状态和运行状态合并为RUNNABLE?

现在的时分多任务操作系统架构通常都是用所谓的“时间分片”方式进行抢占式轮转调度。这个时间分片通常是很小的,一个线程一次最多只能在cpu上运行10-20ms的时间。通常java的线程状态是用于监控的,如果线程切换得如此之快,那么区分ready和running就没有太大意义了。

参考:Java线程的6种状态及切换(透彻讲解)

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 submit = 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两种。在它的构造器里一行代码说明了它存在的意义:

Java线程的使用_第2张图片

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

你可能感兴趣的:(Java基础,线程,Thread,Runable,Callable,Future)