---
title: Java多线程
date: 2020-10-24 16:23:00
tags: 
- Java
- 多线程
categories:
- Java基础
---

## 多线程概述

==多线程：栈空间独立，堆内存共享==

多线程是实现并发机制的一种有效手段。进程和线程一样，都是实现并发的一个基本单位。线程是比进程更小的执行单位，线程是在进程的基础上进行的进一步划分。所谓多线程是指一个进程在执行过程中可以产生多个线程，这些线程可以同时存在、同时运行，一个进程可能包含了多个同时执行的线程。

<!-- more -->

### 进程与线程

*   **进程**：
    *   正在运行的应用程序：是指一个内存中运行的应用程序，**每个进程都有一个独立的内存空间**，即每个进程都有着自己的堆、栈等且是互不共享的。
*   **线程**：
    *   进程中的一个**执行路径**（一段程序从执行到结束的整个过程），共享一个内存空间，线程之间可以自由切换，并发执行，<font color=red>一个进程最少有一个线程</font>
    *   线程实际上是在进程的基础上进一步划分的，一个进程执行后，里面的若干执行路径又可以划分为若干个线程

### 线程调度

1.  **分时调度**

*   所有线程轮流使用 CPU 的使用权，平均分配每个线程占用 CPU 的时间。

2.  **抢占式调度**

*   优先让优先级高的线程使用 CPU，如果线程的优先级相同，那么会随机选择一个(线程随机性)，**Java使用的为抢占式调度**。
*   CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核心而言，某个时刻，只能执行一个线程，而 CPU的在多个线程间切换速度相对我们的感觉很快，看上去就是在同一时刻运行。 其实，多线程程序并不能提高程序的运行速度，但能够提高程序运行效率，让CPU的使用率更高。

### 同步与异步&并发与并行

>   **同步**：排队执行，效率低但安全
>
>   **异步**：同时执行，效率高但数据不安全

>   **并发**：指两个或多个事件在<font color=red>同一个时间段内</font>发生。
>
>   **并行**：指两个或多个事件在<font color=red>同一时刻</font>发生（同时发生）。

## 多线程的实现方式

### 继承Thread类

步骤：

1.  创建一个自定义类并继承Thread类；

2.  重写run()方法，创建新的执行任务（通过thread对象的start()方法启动任务，一般不直接调用run()方法）

3.  创建自定义类对象实例，调用start()，让线程执行

代码如下：

```java
//MyThread.java
public class MyThread extends Thread{
    @Override
    public void run() {			//run()方法就是线程要执行的任务的方法
        for (int i = 0; i < 10; i++) {
            System.out.println("MyThread" + i);
        }
    }
}
//ThreadTest.java
public class ThreadTest {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.start();				//启动线程任务
        for (int i = 0; i < 5; i++) {
            System.out.println("MainThread" + i);
        }
    }
}
```

运行结果：

可以看到顺序并不统一，两个线程在交替执行而且各自所占的时间不完全相同，这是线程在抢时间片，谁先抢到谁就执行。

![image-20201020195837780](https://i.loli.net/2020/10/20/t4hNj7XJWOHBuvr.png)

>   **时序图：**
>
>   ![image-20201020202118525](https://i.loli.net/2020/10/20/ZeTkrMvRzSK8t3F.png)
>
>   运行过程中子线程任务中调用的方法都在子线程中运行

>   在上述代码中。如果Thread对象只需要调用1次，也可以通过使用匿名内部类的方式进行简化：
>
>   ```java
>   public class ThreadTest {
>      public static void main(String[] args) {
>          new Thread(){
>              public void run() {
>                  for (int i = 0; i < 5; i++) {
>                      System.out.println("MyRunnable" + i);
>                  }
>              }
>          }.start();
>          for (int i = 0; i < 5; i++) {
>              System.out.println("MainThread" + i);
>          }
>      }
>   }
>   ```

### 实现Runnable接口

Runnable接口代码：

```java
public interface Runnable {
	public abstract void run();
}
```

步骤：

1.  创建一个自定义类实现Runnable接口，并实现其抽象方法run()，编写线程要执行的任务
2.  创建自定义类对象实例
3.  用Thread类创建一个对象实例，并将第二步中的自定义类对象实例作为参数传给其构造函数
4.  调用Thread类实例的start()方法执行线程。

```java
//MyRunnable.java
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("MyRunnable" + i);
        }
    }
}
//RunnableTest.java
public class RunnableTest {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
        Thread t = new Thread(mr);
        t.start();
        for (int i = 0; i < 5; i++) {
            System.out.println("MainRunnable" + i);
        }
    }
}
//运行效果应该跟上面继承Thread类实现多线程效果差不多。
```

>   上述代码也可以通过使用匿名内部类的方式进行简化：
>
>   ```java
>   public class RunnableTest {
>      public static void main(String[] args) {
>          new Thread(new Runnable() {
>              public void run() {
>                  for (int i = 0; i < 5; i++) {
>                      System.out.println("MyRunnable" + i);
>                  }
>              }
>          }).start();
>          for (int i = 0; i < 5; i++) {
>              System.out.println("MainRunnable" + i);
>          }
>      }
>   }
>   ```

>   **==上面两种方式的比较==**
>
>   **继承Thread类**：
>
>   *   优点：直接使用Thread类中的方法，代码简单
>   *   弊端：如果已有父类，不可用（Java不可多继承)
>
>   **实现Runnable接口（更常用）**:
>
>   与继承Threadl类相比具有以下优势：
>
>   *   通过创建任务，给线程分配任务实现多线程，更适合多个线程同时执行相同任务的情况
>   *   可以避免单继承带来的局限性（Java允许实现多个接口，但不能继承多个父类）
>   *   任务和线程分离，提高程序健壮性
>   *   后续学到的线程池技术，它只接收Runnable类型任务，不接收Thread类型线程

>   **==Thread类API==**
>
>   1.  常用构造方法
>
>   | 构造器                                 | 描述                    |
>   | -------------------------------------- | ----------------------- |
>   | `Thread()`                             | 分配新的 `Thread`对象。 |
>   | `Thread(Runnable target)`              | 分配新的 `Thread`对象。 |
>   | `Thread(Runnable target, String name)` | 分配新的 `Thread`对象。 |
>   | `Thread(String name)`                  | 分配新的 `Thread`对象。 |
>
>   2.  常用其他方法
>
>   | 变量和类型      | 方法                            | 描述                                                         |
>   | :-------------- | :------------------------------ | :----------------------------------------------------------- |
>   | `long`          | `getId()`                       | 返回此Thread的标识符。                                       |
>   | `String`        | `getName()`                     | 返回此线程的名称。                                           |
>   | `int`           | `getPriority()`                 | 返回此线程的优先级。                                         |
>   | `void`          | `setPriority(int newPriority)`  | 更改此线程的优先级。                                         |
>   | `Thread.State`  | `getState()`                    | 返回此线程的状态。                                           |
>   | `static Thread` | `currentThread()`               | 返回对当前正在执行的线程对象的引用。                         |
>   | `void`          | `start()`                       | 导致此线程开始执行; Java虚拟机调用此线程的`run`方法。        |
>   | `static void`   | `sleep(long millis)`            | 导致当前正在执行的线程休眠（暂时停止执行）指定的毫秒数，具体取决于系统计时器和调度程序的精度和准确性。 |
>   | `static void`   | `sleep(long millis, int nanos)` | 导致当前正在执行的线程休眠（暂时停止执行）指定的毫秒数加上指定的纳秒数，具体取决于系统定时器和调度程序的精度和准确性。 |
>   | `void`          | `setDaemon(boolean on)`         | 将此线程标记为 daemon线程或用户线程。                        |
>
>   3.  特殊字段：控制线程抢到时间片的几率
>
>   | 变量和类型   | 字段            | 描述                       |
>   | ------------ | --------------- | -------------------------- |
>   | `static int` | `MAX_PRIORITY`  | 线程可以拥有的最大优先级。 |
>   | `static int` | `MIN_PRIORITY`  | 线程可以拥有的最低优先级。 |
>   | `static int` | `NORM_PRIORITY` | 分配给线程的默认优先级。   |
>
>   其他的可以参考Java的API手册

### 实现Callable接口

Callable接口代码：

```java
public interface Callable<V> {
	V call() throws Exception;
}
```

步骤：

1.  创建一个自定义类实现Callable接口，并实现其抽象方法call()，编写线程要执行的任务

    ```java
    class XXX implements Callable<T> {
    	@Override
    	public <T> call() throws Exception {
    		return T;
    	}
    }
    ```

2.  创建FutureTask对象 , 并传入第一步编写的Callable类对象

    ```java
    FutureTask<Integer> future = new FutureTask<>(callable);
    ```

3.  通过Thread，启动线程

    ```java
    new Thread(future).start();
    ```

```java
//MyCallable.java
import java.util.concurrent.Callable;
public class MyCallable<T> implements Callable<T> {
    @Override
    public T call() throws Exception {
        for (int i = 0; i < 5; i++) {
            System.out.println("MyCallable:" + i);
        }
        return null;
    }
}
//CallableTest.java
import java.util.concurrent.FutureTask;
public class CallableTest {
    public static void main(String[] args) {
        MyCallable<String> mc = new MyCallable<> ();
        FutureTask<String> future = new FutureTask<> (mc);
        new Thread(future).start();
        for (int i = 0; i < 5; i++) {
            System.out.println("main" + i);
        }
    }
}
```

>   上述代码也可以通过使用匿名内部类的方式进行简化：
>
>   ```java
>   import java.util.concurrent.Callable;
>   import java.util.concurrent.FutureTask;
>   public class CallableTest {
>      public static void main(String[] args) {
>          new Thread(new FutureTask<>(new Callable<String>() {
>              @Override
>              public String call() throws Exception {
>                  for (int i = 0; i < 5; i++) {
>                      System.out.println("MyCallable:" + i);
>                  }
>                  return null;
>              }
>          })).start();
>          for (int i = 0; i < 5; i++) {
>              System.out.println("main" + i);
>          }
>      }
>   }
>   ```
>
>   

>   **Runnable 与 Callable比较**
>
>   *   相同点：
>       *   都是接口
>       *   都可以编写多线程程序
>       *   都采用Thread.start()启动线程
>
>   *   不同点
>       *   Runnable没有返回值；Callable可以返回执行结果
>       *   Callable接口的call()允许抛出异常；Runnable的run()不能抛出
>
>   Callalble接口支持返回执行结果，需要调用FutureTask.get()得到，此方法会阻塞主进程的继续往下执
>   行，如果不调用不会阻塞。

## 多线程的应用实例

### 设置和获取线程名称

`currentThread()` 可以获取当前正在执行的线程对象

```java
//MyRunnable.java
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());//获取当前线程对象的名称
    }
}
//GetThread.java
public class GetThread {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());//获取当前线程对象的名称
        new Thread(new MyRunnable()).start();
        new Thread(new MyRunnable()).start();
        new Thread(new MyRunnable(),"answer").start();	//给线程指定一个名称 (方法一)
        Thread t = new Thread(new MyRunnable());
        t.setName("anotherWay");					//给线程指定一个名称 (方法二)
        t.start();
    }
}
```

执行结果：

![image-20201020213805266](https://i.loli.net/2020/10/20/DFz1sN5h2wUqyBG.png)

### 线程休眠sleep

`sleep(long millis)`是Thread类的静态方法，类名直接调用即可，单位ms。

```java
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            System.out.print(i + " ");
            Thread.sleep(1000);
        }
    }
}
```

运行结果：每隔1秒打印一个数字。

![](https://i.loli.net/2020/10/20/pIqfd5TD2waWtXo.gif)

>   **线程阻塞**：所有较耗时的操作都能称为阻塞。也叫耗时操作。

### 线程的中断

一个线程是一个独立的执行路径，它是否应该结束，**由其自身决定**。

因为线程执行过程会有很多资源需要使用或释放，如果干涉它的结束，很可能导致资源没能来得及释放，一直占用，从而产生无法回收的内存垃圾。

Java以前提供stop()方法可以结束线程，现在已经过时(不再使用)。现在出了新的方法，**给线程打中断标记**（`interrupt`）来控制它的结束。

具体方法就是 调用`interrupt()`方法，子线程执行时捕获中断异常，并在catch块中，添加处理释放资源的代码。

如下代码所示：main线程执行完后不管子线程是否执行完都中断掉它

```java
//MyRunnable.java
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        //线程任务：打印1-10
        for (int i = 1; i <= 10; i++) {
            System.out.println(Thread.currentThread().getName() + "：" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {//发现中断标记，进入catch块中，进行释放资源处理
                System.out.println(Thread.currentThread().getName()+"：发现中断标记，我自杀了");
                return;	//为了演示，直接结束
            }
        }
    }
}
//InterruptTest.java
public class InterruptTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable());
        t1.setName("myThread");
        t1.start();
        //main线程 打印1-5
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + "：" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }   
        }
        t1.interrupt(); //给线程t1添加中断标记
    }
}
```

运行结果：

![](https://i.loli.net/2020/10/20/7vUXIGJWRbj4tTH.png)

### 守护线程

线程分为**守护线程**和**用户线程**

-   **用户线程**：当一个进程不包含任何存活的用户线程时，进行结束。
-   **守护线程**：守护用户线程，当最后一个用户线程结束时，所有守护线程自动死亡。

直接创建的都是用户线程，

设置线程为守护线程：在启动之前设置 ，语法为：`线程对象.setDaemon(true);`。

```java
//MyRunnable.java
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        //线程任务：打印1-10
        for (int i = 1; i <= 10; i++) {
            System.out.println(Thread.currentThread().getName() + "：" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                return;	//为了演示，直接结束
            }
        }
    }
}
//InterruptTest.java
public class InterruptTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable());
        t1.setName("myThread");
        t1.setDaemon(true);//设置t1为守护线程
        t1.start();
        //main线程 打印1-5
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + "：" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }   
        }
    }
}
```

运行结果

![](https://i.loli.net/2020/10/20/YDGyOnPhEL3eXNz.png)

### 线程安全问题

#### 问题引入

我们先来看个例子：三个窗口（线程）同时卖5张票。

```java
public class Demo1 {
    public static void main(String[] args) {
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }
    static class Ticket implements Runnable{
        private int count = 5;  //票数
        @Override
        public void run() {
            while (count > 0) {
                //卖票
                System.out.println(Thread.currentThread().getName()+"正在卖票");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName()+"出票成功,余票："+count);
            }
        }
    }
}
```

运行结果部分截图：

![](https://i.loli.net/2020/10/21/g31kZ6c4FnywoqQ.png)

我们看到余票出现了负数，显然这是不合理的，这就是线程不安全导致的。出现这种情况的原因：线程争抢，导致线程不安全。 多线程在进行同一卖票任务时，没人干涉，各个窗口疯狂买票，最终导致卖的票超出总票数，余票出现负数。

<font color=red>**线程不安全的原因：**</font>

当多线程并发访问**临界资源**时，如果破坏**原子操作**，可能会造成数据不一致。

-   临界资源：共享资源（同一对象），一次仅允许一个线程使用，才可保证其正确性。
-   原子操作：不可分割的多步操作，被视作一个整体，其顺序和步骤不可打乱或缺省。

多个线程争抢同一个数据，使得数据在判断和使用时出现不一致的情况。那如何解决呢？

解决方法：保证一段数据同时只能被一个线程使用（排队使用），也就是线程同步，给线程加锁（synchronized）

我们有以下三种方法解决线程不安全的问题：同步代码块、同步方法、显示锁

#### 同步代码块

使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块

多个同步代码块如果使用**相同的锁对象**, 那么他们就是同步的

语法格式：`synchronized(锁对象) {}`

任何对象都可以作为**锁对象**存在。

还以上面卖票的代码为例，给卖票的线程加锁

![image-20201021174518339](https://i.loli.net/2020/10/21/fcxmMsCzRarQS8P.png)

#### 同步方法

以方法为单位进行加锁。把synchronized关键字修饰在方法中。

还以上面卖票的代码为例，写一个synchronized修饰的方法sale()执行卖票任务，

![](https://i.loli.net/2020/10/21/SoCgaNvHtdc6GwX.png)

#### 显式锁

>   上面三种方法中，同步代码块和同步代码都是隐式锁

`Lock l = new ReentrantLock()`：自己创建一把锁

`lock()`：加锁 			`unlock()`：解锁

还以上面卖票的代码为例

![](https://i.loli.net/2020/10/21/MdGWmJql95V1uEX.png)

>   **显式锁和隐式锁的区别：**
>
>   | 区别         | synchronized                                                 | lock                                                         |
>   | ------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
>   | 原始构成     | Java关键字，由JVM维护，是JVM层面的锁                         | JDK1.5之后的类，使用lock是在调用API，是API层面的锁           |
>   | 使用方式     | 隐式锁，不需要手动获取和释放锁，只需要写synchronized，不用进行其他操作 | 显式锁，需要手动获取和释放锁，如果没有释放锁，可能会出现死锁 |
>   | 等待中断     | 不会中断，除非抛出异常或正常运行完成                         | 可以中断，1：调用设置超时方法tryLock(long timeout ,timeUnit unit)；2：调用lockInterruptibly()放到代码块中，然后调用interrupt()方法可以中断 |
>   | 加锁公平     | **非公平锁**                                                 | 可以是**公平锁**也可以是**非公平锁**，默认是非公平锁。可以在其构造方法传入Boolean值，true公平锁，false非公平锁 |
>   | 绑定多个条件 | 没有。不能精确唤醒线程，要么随机唤醒一个线程，要么唤醒所有等待线程 | 用来实现分组唤醒需要唤醒的线程，可以精确唤醒线程             |
>   | 性能         | JDK1.5时性能较低，JDK1.6时性能优化，与lock相较无异           | JDK1.5时性能更高，JDK1.6时synchronized优化赶上lock           |
>   | 加锁方式     | 线程获取独占锁（CPU悲观锁机制），只能依靠阻塞等待线程释放锁。在CPU转换线程阻塞时会引起线程上下文切换，当竞争锁的线程过多时，会引起CPU频繁上下文切换导致效率低下 | 使用乐观锁机制（CAS操作 Computer and Swap），假设不会发生冲突，一旦发生冲突失败就重试，直到成功为止。 |
>
>   **公平锁**：先来先得，排队执行
>
>   **非公平锁**：抢占式的，谁抢到是谁的

>   更多关于线程安全的问题可以看下面这篇文章
>
>   [如果你这样回答“什么是线程安全”，面试官都会对你刮目相看（建议珍藏)](https://mp.weixin.qq.com/s/WDeewsvWUEBIuabvVVhweA)

### <font color=red>线程死锁</font>

#### 概述

当两个或两个以上的线程在执行过程中，因为争夺资源而造成的一种相互等待的状态，由于存在一种环路的锁依赖关系而永远地等待下去，如果没有外部干涉，他们将永远等待下去，此时的这个状态称之为死锁。

多个线程相互占用对方的资源的锁，而又相互等对方释放锁，此时若无外力干预，这些线程则一直处于阻塞的假死状态，形成死锁。

举个例子，如下图所示，在线程A持有锁L并想获得锁M的同时，线程B持有锁M并尝试获得锁L，那么这两个线程将永远地等待下去，这种情况就是死锁形式。

![](https://i.loli.net/2020/10/21/oC9p8qRF7h1d4DS.png)

**死锁产生的条件：**

-   **互斥条件**：指进程对所分配到的资源进行排它性使用，即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源，则请求者只能等待，直至占有资源的进程用完释放。
-   **请求和保持条件**：指进程已经保持至少一个资源，但又提出了新的资源请求，而该资源已被其它进程占有，此时请求进程阻塞，但又对自己已获得的其它资源保持不放。
-   **不剥夺条件**：指进程已获得的资源，在未使用完之前，不能被剥夺，只能在使用完时由自己释放。
-   **环路等待条件**：指在发生死锁时，必然存在一个进程——资源的环形链，即进程集合{A，B，C，···，Z} 中的A正在等待一个B占用的资源；B正在等待C占用的资源，……，Z正在等待已被A占用的资源。

#### 如何避免死锁

1.  按顺序加锁：如果每个线程都按同一个的加锁顺序这样就不会出现死锁。

2.  给锁加时限：每个线程获取锁的时候加上个时限，如果超过某个时间就放弃锁。

3.  死锁检测：按线程间获取锁的关系检测线程间是否发生死锁，如果发生死锁就执行一定的策略，如终断线程或回滚操作等。

>   更多关于线程死锁的问题可以看下面这篇文章，以上内容也是来自这篇文章：
>
>   [多线程 死锁详解](https://mp.weixin.qq.com/s/kvvJ9_xKaOMobaX7PZ5VCQ)

### 多线程通信

Object方法中提供了一些线程间相互通信的方法

| 变量和类型 | 方法                                   | 描述                                                         |
| ---------- | -------------------------------------- | ------------------------------------------------------------ |
| `void`     | `notify()`                             | 唤醒正在此对象监视器上等待的单个线程。                       |
| `void`     | `notifyAll()`                          | 唤醒等待此对象监视器的所有线程。                             |
| `void`     | `wait()`                               | 导致当前线程等待它被唤醒，通常是 通知或 中断 。              |
| `void`     | `wait(long timeoutMillis)`             | 导致当前线程等待它被唤醒，通常是 通知或 中断 ，或者直到经过一定量的实时。 |
| `void`     | `wait(long timeoutMillis,  int nanos)` | 导致当前线程等待它被唤醒，通常是 通知或 中断 ，或者直到经过一定量的实时。 |

**什么时候需要通信**

多个线程并发执行时, 在默认情况下CPU是随机切换线程的，如果我们希望他们有规律的执行, 就可以使用通信。

#### 生产者与消费者

>   看下面代码，有Cooker类，Waiter类，Food类
>
>   厨师cooker为生产者线程，服务员waiter为消费者线程，食物food为生产与消费的物品；
>
>   假设目前只有一个厨师，一个服务员，一个盘子。理想状态是：厨师生产一份饭菜，服务员端走一份，且饭菜的属性未发生错乱；
>
>   厨师可以制作两种口味的饭菜，制作100次；
>
>   服务员可以端走饭菜100次；

```java
public class Demo {
    public static void main(String[] args) {
        Food f = new Food();
        new Cooker(f).start();
        new Waiter(f).start();
    }
    static class Cooker extends Thread{		//生产者线程
        private Food f;
        public Cooker(Food f){
            this.f = f;
        }
        public void run() {
            //生产100个菜
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0){
                    f.setNameAndTaste("菜1","味道1");
                } else {
                    f.setNameAndTaste("菜2","味道2");
                }
            }
        }
    }
    static class Waiter extends Thread {  //消费者线程
        private Food f;
        public Waiter(Food f){			  
            this.f = f;
        }
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                f.get();
            }
        }
    }
    static class Food {
        private String name;
        private String taste;
        public void setNameAndTaste(String name,String taste){		//生产
            this.name = name;			//先设定名称
            try {
                Thread.sleep(100);		//为使线效果明显，中间休眠一段时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.taste = taste;			//休眠后设定味道
        }
        public void get(){ //消费
            System.out.println("服务员端走的菜名称是：" + name + "，味道：" + taste);
        }
    }
}
```

运行结果

![](https://i.loli.net/2020/10/21/CAYzM4DNI36Pg7V.png)

原因：我们在设定菜名和味道的`setNameAndTaste`方法中,先设定名称，然后休眠一段时间，再设定的味道，中间休眠的那段时间很可能发生时间片丢失，使得菜属性产生混乱。

**解决方式一**：

为了防止在生产过程中setNameAndTaste出现时间片切换，可以用synchronized修饰此方法；

```java
public synchronized void setNameAndTaste(String name,String taste){
    //...
}
public synchronized void get(){  // 消费
	//...
}
```

运行结果

![](https://i.loli.net/2020/10/21/HhAMOSIL9Ymn3lE.png)

可以看出，依然不符合实际情况，这是因为synchronized只是确保了方法内部不会发生线程切换，但并不能保证生产一个消费一个的逻辑关系

**解决方式二**：

在解决方案一的基础上，进行线程之间的通信

```java
private boolean flag = true;				//默认为true，表示可以做饭
```

厨师做完饭后喊醒服务员，自己睡着。服务员送完饭后喊醒厨师，自己睡着；将Food类左如下修改

![](https://i.loli.net/2020/10/21/flHwKZ9nbGrg8mT.png)

运行结果，做一道菜，端走一道。

![image-20201021210352200](https://i.loli.net/2020/10/21/m7pIQqgSCZz59HG.png)

## 线程的六种状态

Enum Thread.State描述了六种线程的状态，如下表所示

| Enum Constant   | 描述                                               |
| --------------- | -------------------------------------------------- |
| `BLOCKED`       | 线程的线程状态被阻塞等待监视器锁定。(阻塞)         |
| `NEW`           | 尚未启动的线程的线程状态。(创建)                   |
| `RUNNABLE`      | 可运行线程的线程状态。（就绪和运行）               |
| `TERMINATED`    | 终止线程的线程状态。（消亡）                       |
| `TIMED_WAITING` | 具有指定等待时间的等待线程的线程状态。(有限期等待) |
| `WAITING`       | 等待线程的线程状态。(无限期等待)                   |

## 线程池Executors

>   普通线程的执行流程:
>
>   创建线程 → 创建任务 → 执行任务 → 关闭线程
>
>   如果并发的线程数量很多，并且每个线程都是执行一个时间很短的任务就结束了，这样频繁创建线程就会大大降低 系统的效率，因为频繁创建线程和销毁线程需要时间。 线程池就是一个容纳多个线程的容器，池中的线程可以反复使用，省去了频繁创建和销毁线程对象的操作，节省了大量的时间和资源。

线程池的好处

*   降低资源消耗。
*   提高响应速度。
*   提高线程的可管理性。

Java中有四种线程池(ExecutorService)：缓存线程池、定长线程池、单线程线程池、周期性任务定长线程池

### 缓存线程池

长度无限制

执行流程:

1.  判断线程池是否存在空闲线程

2.  存在则使用

3.  不存在,则创建线程 并放入线程池, 然后使用

```java
ExecutorService service = Executors.newCachedThreadPool(); //获取缓存线程池对象
//向线程池中 加入 新的任务
service.execute(new Runnable() {
	@Override
	public void run() {
		//线程任务代码
	}
});
```

### 定长线程池

长度是指定的数值

步骤：

1.  判断线程池是否存在空闲线程
2.  存在则使用
3.  不存在空闲线程，线程池未满的情况下,则创建线程 并放入线程池, 然后使用
4.  不存在空闲线程，且线程池已满的情况下,则等待线程池存在空闲线程

```java
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
	public void run() {
		//线程任务代码
	}
});
```

### 单线程线程池

步骤：

1.  判断线程池的那个线程是否空闲
2.  空闲则使用
3.  不空闲则等待池中的单个线程空闲后使用

```java
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
	public void run() {
        //线程任务代码
    }
});
```

### 周期性任务定长线程池

步骤：

1.  判断线程池是否存在空闲线程
2.  存在则使用
3.  不存在空闲线程，且线程池未满的情况下，则创建线程，并放入线程池后使用
4.  不存在空闲线程，且线程池已满的情况下，则等待线程池存在空闲线程

周期性任务执行时：定时执行, 当某个时机触发时, 自动执行某任务

```java
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 定时执行
* 参数1. runnable类型的任务
* 参数2. 时长数字   5 
* 参数3. 时长数字的单位 TimeUnit.SECONDS
*/
service.schedule(new Runnable() {
	public void run() {
		//线程任务代码
	}
},5,TimeUnit.SECONDS);
/**
* 周期执行
* 参数1. runnable类型的任务
* 参数2. 时长数字(延迟执行的时长)	5
* 参数3. 周期时长(每次执行的间隔时间) 2
* 参数4. 时长数字的单位  TimeUnit.SECONDS
*/
service.scheduleAtFixedRate(new Runnable() {
	public void run() {
		//线程任务代码
	}
},5,2,TimeUnit.SECONDS);
```





