一、进程与线程

  • 进程:进程是处于运行过程中的程序,具有独立的功能,是系统进行资源分配和调度的独立单位
    1. 独立性:进程是系统中独立存在的实体,它拥有自己独立的资源,每一个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个进程不能直接访问其他进程的地址空间
    2. 动态性:进程是一个正在系统中活动的指令集合,包含了时间的概念,具有自己的生命周期和状态
    3. 并发性:在单个处理器上,多个进程可以并发地执行,并且在执行时它们彼此之间不会互相的影响
  • 线程
    1. 线程扩展了进程的概念,使得同一个进程可以同时并发处理多个任务
    2. 线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程
    3. 线程不拥有系统资源,它与父进程里的其他线程共享父进程所拥有的全部系统资源
  • 总结
    1. 系统可以同时执行多个任务,每个任务就是进程
    2. 进程可以同时执行多个任务,每个任务就是线程
  • 线程的优点
    1. 容易共享内存
      1. 进程之间不能共享内存,但线程之间共享内存非常容易
      2. 与分隔的进程相比,线程之间隔离程度要小,它们共享内存、文件句柄、其他每个进程应有的状态
    2. 运行效率更高
      1. 系统创建进程时要为其分配系统资源,但创建线程时不需要
    3. 编程方式简单
      1. Java 内置了多线程功能的支持,并不是简单地对操作系统的底层进行调度,编程更方便

二、并行与并发

  • 并行
    1. 在同一时刻,有多条指令在多个处理器上同时执行
  • 并发
    1. 在同一时刻,某一个处理器只能执行一条指令
    2. 多个进程的指令可以被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果
    3. 对于多核计算机,当进程数多于核心数时,也会存在并发的行为,也就是同时存在并行与并发!

三、线程的创建方式

3.1 Java 的线程模型

  1. Java 使用 Thread 类代表线程,所有线程对象都必须是 Thread 类或其子类的实例
  2. 线程用于完成一定的任务(执行一段程序流),Java 使用线程执行体来代表这段程序流
  3. 线程的使用过程:定义线程执行体—->创建线程对象—->调用线程对象的方法以启动线程

    3.2 三种方式

    3.2.1 继承 Thread 类

    ```java / 1、定义Thread类的子类,并重写该类的run()方法 2、创建该子类的实例,即创建线程对象 3、调用线程对象的start()方法。即启动这个线程 / public class test_1 { public static void main(String[] args) { // 主线程
    1. new Foo().start(); // 子线程
    } }

class Foo extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + “—->” + i); } } }

<a name="L0E6N"></a>
### 3.2.2 实现 Runnable 接口
```java
/*
1、定义Runnable接口的实现类,并实现该接口的run()方法(线程体)
2、创建该实现类的实例,并以此作为target来创建Thread对象
3、调用Thread对象的start()方法来启动线程
*/
public class test_2 {
    public static void main(String[] args) {
        new Thread(new RunnableTest()).start();
    }
}

class RunnableTest implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

3.2.3 实现 Callable 接口

/*
1、定义Callable接口的实现类,并重写该接口的call()方法(线程体)
2、创建该实现类的实例,并使用Future接口(使用其实现类FutureTask)来包装Callable对象
最后使用Future对象(线程的返回值)作为target来创建Thread对象
3、调用Thread对象的start()方法来启动线程
4、调用Future对象的get()方法获取线程的返回值
*/
public class tast_3 {
    public static void main(String[] args) throws Exception {
        FutureTask<String> task = new FutureTask<>(new CallableTest());
        new Thread(task).start();
        System.out.println(task.get());
    }
}

class CallableTest implements Callable<String> {
    @Override
    public String call() throws Exception {
        return Thread.currentThread().getName() + "--->" + "Hello Callable!";
    }
}

3.2.4 三种方式的比较

  • 继承父类的方式
    • 优点:编程比较简单
    • 缺点:线程类已经继承了 Thread 类,所以不能再继承其他的父类
  • 实现接口的方式
    • 优点:线程类只实现了接口,还可以继承于其他的父类
    • 缺点:编程比较麻烦
  • 建议采用实现 Runnable 接口的方式,若需要获取返回值则采用实现 Callable 接口的方式

四、线程的生命周期

  • 线程被创建后,不会立刻进入运行状态,也不会一直处于运行状态
  • 线程生命周期中需经历以下5种状态:
    1. 新建(New)
    2. 就绪(Ready)
    3. 运行(Running)
    4. 阻塞(Blocked)
    5. 死亡(Dead)

image.png


五、控制线程

5.1 线程休眠(sleep)

  1. sleep( ) 会让线程进入阻塞状态,而 yield( ) 会让线程进入就绪状态
  2. sleep( ) 给其他线程运行的机会,不理会其他线程的优先级看;yield( ) 考虑线程的优先级,只会给优先级相同或者优先级更高的线程执行机会

    5.2 等待线程(join)

  • 例如在 main 线程中 AThread.join( ),意思就是 main 线程会等待 AThread 死亡后才会继续向下执行

    public class JoinTest {
      public static void main(String[] args) throws InterruptedException {
          testJoinThread();
      }
    
      private static class JoinThread implements Runnable {
          @Override
          public void run() {
              for (int i = 0; i < 30; i++) {
                  System.out.println(Thread.currentThread().getName() + "--->" + i);
              }
          }
      }
    
      public static void testJoinThread() throws InterruptedException {
          new Thread(new JoinThread()).start(); // 开启第一个线程
          for (int i = 0; i < 30; i++) {
              System.out.println(Thread.currentThread().getName() + "--->" + i);
              if (i == 10) {
                 Thread thread = new Thread(new JoinThread());
                 thread.start(); // 开启第二个线程
                 thread.join(); // 第二个线程会让主线程等待自己死亡
              }
          }
      }
    }
    

    5.3 后台线程(Daemon)

  1. 也叫守护线程,或精灵线程
  2. 它是在后台运行的,任务是为其他线程提供服务
  3. 如果所有的前台线程死亡,则后台线程会自动死亡
  4. 线程默认是前台线程,Thread 类提供如下方法来设置后台线程:
    1. void setDaemon(boolean on);
    2. boolean isDaemon();
  5. 前台线程都死亡后,JVM 会通知后台线程死亡,但从它接收指令到做出响应,需要一定的时间

    5.4 线程优先级

  6. 线程运行时拥有优先级,优先级高的线程则拥有较多的运行机会

  7. 线程默认的优先级与它的父线程相同,而主线程具有普通优先级
  8. Thread 提供了如下成员来处理线程的优先级:
    1. public final static int _MIN_PRIORITY _= 1; // 线程可以拥有的最低优先级
    2. public final static int _NORM_PRIORITY _= 5; // 分配给线程的默认优先级
    3. public final static int _MAX_PRIORITY _= 10; // 线程可以拥有的最大优先级
    4. public final int getPriority() // 返回此线程的优先级
    5. public final void setPriority(int newPriority) // 设置此线程的优先级
  9. 线程的优先级需要操作系统的支持,虽然 Java 支持10种优先级,但操作系统支持的优先级可能少于10种,所以最好不要通过数字设置线程的优先级,而是尽可能采用静态变量来指定

六、线程安全问题

  • 多线程访问同一共享变量就可能会产生线程安全问题
  • 解决方法(线程同步)
    1. 同步代码块:synchronized (obj) {//需要同步的代码块,obj为同步监视器,可以是任意对象,建议采用并发访问的资源作为同步监视器}
    2. 同步方法:public synchronized void f( )
    3. 同步锁(需要手动解锁) ```markdown

      第一步

      Lock lock = new ReentrantLock();

第二步

lock.lock() try{ // 需要同步的代码 }finally{ lock.unlock(); }

![image.png](https://cdn.nlark.com/yuque/0/2021/png/2396573/1633960667380-2666488c-3629-4842-9138-7f921c712759.png#clientId=u0fd5dd52-3972-4&from=paste&height=197&id=ubb3a2f10&margin=%5Bobject%20Object%5D&name=image.png&originHeight=393&originWidth=1628&originalType=binary&ratio=1&size=231422&status=done&style=shadow&taskId=u3a13b93a-a67e-4362-8684-b92737b063e&width=814)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/2396573/1633960931570-d6f77d81-6ae8-4c4a-9848-77266b3a5030.png#clientId=u0fd5dd52-3972-4&from=paste&height=391&id=u0d80722f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=781&originWidth=1831&originalType=binary&ratio=1&size=530555&status=done&style=shadow&taskId=ud1aa50c7-b722-492a-9ff1-96913324968&width=915.5)

---

<a name="bIk6x"></a>
# 七、死锁

- 死锁的产生:当两个线程互相等待对方释放同步监视器时,会发生死锁
```java
public class DeadTest {
    public static void main(String[] args) {
        String a = "a";
        String b = "b";
        new Thread(new FirstThread(a, b)).start();
        new Thread(new SecondThread(a, b)).start();
    }

    private static class FirstThread implements Runnable {
        private String a;
        private String b;

        public FirstThread(String a, String b) {
            this.a = a;
            this.b = b;
        }

        @Override
        public void run() {
            synchronized (a) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (b) {
                    System.out.println("First");
                }
            }
        }
    }

    private static class SecondThread implements Runnable {
        private String a;
        private String b;

        public SecondThread(String a, String b) {
            this.a = a;
            this.b = b;
        }

        @Override
        public void run() {
            synchronized (b) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (a) {
                    System.out.println("Second");
                }
            }
        }
    }
}
  • 如何避免死锁:
    1. 避免多次锁定
      • 尽量避免同一个线程对多个同步监视器进行锁定
    2. 按相同顺序加锁
      • 如果多个线程需要对多个同步监视器加锁,则应该保证它们以相同的顺序请求加锁
    3. 使用可以超时释放的锁
      • 调用 Lock 对象的 tryLock(time,unit) 方法,当超过指定时间后它会自定释放锁

八、线程通信

  1. 在某些业务中,A 线程负责修改数据,B 线程负责使用数据
  2. 在数据修改前,B 线程处于等待状态,直至得到 A 线程的通知
  3. 在数据修改后,A 线程通过某种机制,通知 B 线程去使用数据
  4. 线程通信的前提是要支持并发读写数据,所以线程通信前必须要对共享资源加锁

    8.1 通过 synchronized 同步的通信

    Object.class
void wait() 调用该方法的线程进入等待状态(区别于阻塞状态,阻塞状态下线程依旧持有锁),并释放此对象的锁
void wait(long timeout) 同上,但可设置最长等待时间
void notify() 通知一个在此对象上等待的线程,使其从 wait( ) 返回
void notifyAll() 通知所有在此对象上等待的线程,使其从 wait( ) 返回
/*
模拟了秒杀业务场景
*/
public class Demo1 {
    public static void main(String[] args) {
        Product product = new Product(0); // 设置初始库存为0
        // 模拟 100 位顾客
        for (int i = 1; i <= 100; i++) {
            new Thread(new buyTask(product), "顾客" + i).start();
        }

        // 模拟倒计时
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(new sellTask(product), "商家").start();
    }

    // 商品类
    private static class Product {
        int amount; // 库存

        public Product(int amount) {
            this.amount = amount;
        }

        public int getAmount() {
            return amount;
        }

        public void setAmount(int amount) {
            this.amount = amount;
        }
    }

    // 买家线程
    private static class buyTask implements Runnable {
        private Product product;

        public buyTask(Product product) {
            this.product = product;
        }

        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            synchronized (product) {
                while (product.getAmount() == 0) { // 库存不足
                    try {
                        System.out.println(name + "已做好准备,等待抢购...");
                        product.wait(); // 进入等待状态
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 库存充足,开始抢购
                product.setAmount(product.getAmount() - 1);
                System.out.println(name + "抢购到 1 件商品,库存为:" + product.getAmount());
            }
        }
    }

    // 卖家线程
    private static class sellTask implements Runnable {
        private Product product;

        public sellTask(Product product) {
            this.product = product;
        }

        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            synchronized (product) {
                product.setAmount(10); // 上架10件商品
                System.out.println(name + "上架了10件商品,剩余库存为:" + product.getAmount());
                product.notifyAll(); // 通知所有在product上等待的线程从wait()返回,可以开始抢购
            }
        }
    }
}

8.2 通过 Lock 同步的通信

Condition condition = lock.newCondition();
Condition 接口

void await() 调用该方法的线程进入等待状态
boolean await(long time, TimeUnit unit) 同上,但 time 为等待的最长时间,unit 为时间单位,如果在方法返回之前可检测到等待时间已经过去,则为 false ,否则为 true
void signal() 唤醒一个在此 Condition 上等待的线程
void signalAll() 唤醒所有在此 Condition 上等待的线程
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo2 {
    private static Lock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        Product product = new Product(0); // 设置初始库存为0
        // 模拟 100 位顾客
        for (int i = 1; i <= 100; i++) {
            new Thread(new buyTask(product), "顾客" + i).start();
        }

        // 模拟倒计时
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(new sellTask(product), "商家").start();
    }

    // 商品类
    private static class Product {
        int amount; // 库存

        public Product(int amount) {
            this.amount = amount;
        }

        public int getAmount() {
            return amount;
        }

        public void setAmount(int amount) {
            this.amount = amount;
        }
    }

    // 买家线程
    private static class buyTask implements Runnable {
        private Product product;

        public buyTask(Product product) {
            this.product = product;
        }

        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            lock.lock();
            try {
                while (product.getAmount() == 0) { // 库存不足
                    try {
                        System.out.println(name + "已做好准备,等待抢购...");
                        condition.await(); // 进入等待状态
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 库存充足,开始抢购
                product.setAmount(product.getAmount() - 1);
                System.out.println(name + "抢购到 1 件商品,库存为:" + product.getAmount());
            } finally {
                lock.unlock();
            }
        }
    }

    // 卖家线程
    private static class sellTask implements Runnable {
        private Product product;

        public sellTask(Product product) {
            this.product = product;
        }

        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            lock.lock();
            try {
                product.setAmount(10); // 上架10件商品
                System.out.println(name + "上架了10件商品,剩余库存为:" + product.getAmount());
                condition.signalAll(); // 通知所有在product上等待的线程从wait()返回,可以开始抢购
            } finally {
                lock.unlock();
            }
        }
    }
}

九、阻塞队列

9.1 生产者消费者模式

  1. 某个模块负责产生数据(生产者),另一个模块负责处理数据(消费者)
  2. 需要有一个缓冲区位于生产者与消费者之间,作为沟通的桥梁
  3. 生产者只负责把数据放入缓冲区,而消费者从缓冲区取出数据

image.png

9.2 BlockingQueue

  • BlockingQueue 是 Queue 的子接口,主要作用是作为线程通信的工具
  • BlockingQueue 增加了两个支持阻塞的方法
    • void put(E e):尝试把元素 e 放入队列中,如果该队列的元素已满,则阻塞该线程
    • E take():尝试从队列头部取出元素,如果该队列的元素已空,则阻塞该线程 ```java import java.util.Random; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue;

public class Demo3 { public static void main(String[] args) { BlockingQueue queue = new ArrayBlockingQueue<>(10); new Thread(new Producer(queue), “p1”).start(); new Thread(new Customer(queue), “c1”).start(); new Thread(new Customer(queue), “c2”).start(); new Thread(new Customer(queue), “c3”).start(); }

// 生产者
private static class Producer implements Runnable {
    private BlockingQueue<Long> queue;

    public Producer(BlockingQueue<Long> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(new Random().nextInt(1000));
                queue.put(System.currentTimeMillis());
                String name = Thread.currentThread().getName();
                System.out.println(name + "生产了1条数据,剩余:" + queue.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 消费者
private static class Customer implements Runnable {
    private BlockingQueue<Long> queue;

    public Customer(BlockingQueue<Long> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(new Random().nextInt(4000));
                queue.take();
                String name = Thread.currentThread().getName();
                System.out.println(name + "消费了1条数据,剩余:" + queue.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

}


---

<a name="Qkf8N"></a>
# 十、线程组

- **ThreadGroup **类代表线程组,它可以包含一批线程,并对这些线程进行统一的管理:
   1. 每个线程都有对应的线程组,若程序未显示指定线程的线程组,则该线程属于默认线程组
   1. 默认情况下,子线程和它的父线程属于同一个线程组,而 main 线程属于 main 线程组
   1. 一旦线程加入某个线程组,则该线程一直属于这个线程组,中途不允许修改其线程组
- ThreadGroup 的构造器:
   - `public ThreadGroup(String name)`
   - `public ThreadGroup(ThreadGroup parent, String name)`
- Thread 的构造器:
   - `public Thread(ThreadGroup group, String name)`
   - `public Thread(ThreadGroup group, Runnable target)`
   - `public Thread(ThreadGroup group, Runnable target, String name)`
- ThreadGroup 中常用的处理线程的方法包括:
   1. 返回线程组的名称
   1. 返回当前线程组的父线程组
   1. 中断此线程组中所有的线程
   1. 设置(返回)线程组的最高优先级
   1. 设置(返回)线程组为后台线程组
   1. 异常相关
      1. UncaughtExceptionHandler 代表异常处理器
      1. Thread 类提供了 set 方法来指定该线程的异常处理器
      1. ThreadGroup 类默认已经实现了这个异常处理器接口;当一个线程抛出异常时,JVM 会先查找该线程对应的异常处理器,若找到则调用该异常处理器来处理异常,否则 JVM 将调用该线程所属的线程组方法来处理这个异常
```java
public class Demo4 {
    public static void main(String[] args) {
        // 主线程的线程组
        ThreadGroup group = Thread.currentThread().getThreadGroup();
        // 线程组名、是否后台线程、线程组中的活跃线程数
        System.out.println(group.getName() + "," + group.isDaemon() + "," + group.activeCount());
        // 打印线程组中的线程
        group.list();

        // 子线程的线程组
        Thread thread = new Thread(new ThreadTask());
        thread.start();
        System.out.println(thread);

        // 自定义的线程组
        group = new ThreadGroup("TEST");
        group.setDaemon(true);
        thread = new Thread(group, new ThreadTask());
        thread.start();
        System.out.println(thread);

        // 销毁的后台线程
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(group.isDestroyed() + "," + group.getName());

        // 异常处理器
        // 自定义异常处理器
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(t + "--->" + e);
            }
        });
        System.out.println(3 / 0);
    }

    private static class ThreadTask implements Runnable {
        @Override
        public void run() {
            System.out.println(System.currentTimeMillis());
        }
    }
}

十一、线程池

  • 启动线程的成本是比较高的,通过线程池可以实现线程的复用,从而提高性能
  • 线程池可以控制程序中的线程数量,避免超出系统的负荷,导致系统崩溃
  • 创建线程池之后,它会自动创建一批空闲的线程;程序将线程体传给线程池,它就会启动一个空闲的线程来执行该线程体;当线程体执行结束后,该线程并不会死亡,而是变回空闲状态继续使用

    11.1 创建并使用线程池

  • ExecutorService 接口代表线程池

  • ScheduledExecutorService 是其子接口,代表可执行定时任务的线程池
  • Executors 是一个工厂类,该类中包含了若干静态方法,用来创建线程池 ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit;

public class Demo5 { private static ExecutorService threadPool = Executors.newFixedThreadPool(3); private static ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3);

public static void main(String[] args) {
    // 定义线程体
    Runnable threadTask = new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " 执行了一个线程任务");
        }
    };

    // ExecutorService
    for (int i = 0; i < 10; i++) {
        threadPool.submit(threadTask);
    }

    // 定义线程体
    Runnable scheduledTask = new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " 执行了一个定时线程任务");
        }
    };

    // ScheduledExecutorService,延迟5秒执行,间隔3秒,单位为秒
    for (int i = 0; i < 5; i++) {
        scheduledPool.scheduleAtFixedRate(scheduledTask, 5, 3, TimeUnit.SECONDS);
    }
}

}

<a name="WRvzS"></a>
## 11.2 ForkJoinPool

- Fork/Join 是一种思想,旨在充分利用多核资源,用来执行并行任务
- ForkJoin 是 ExecutorService 的实现类,是上述思想的实现
- 它的做法是:将一个大的任务分割成若干个小任务,最终汇总每个小任务的结果,从而得到大任务的结果

![image.png](https://cdn.nlark.com/yuque/0/2021/png/2396573/1633944044752-9f9bd2b7-5415-488a-9aaf-4c6f9a801bf5.png#clientId=u0fd5dd52-3972-4&from=paste&height=521&id=uabe954c5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=694&originWidth=593&originalType=binary&ratio=1&size=68115&status=done&style=shadow&taskId=ubf4bcd1e-47ec-4358-a710-a48980de17e&width=445)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/2396573/1633944208537-f63b6292-3ac5-41bc-87cf-409053cfb307.png#clientId=u0fd5dd52-3972-4&from=paste&height=230&id=u8586e501&margin=%5Bobject%20Object%5D&name=image.png&originHeight=459&originWidth=962&originalType=binary&ratio=1&size=102134&status=done&style=shadow&taskId=ub9512ef6-f45f-42fc-b8f2-0def54a1f95&width=481)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/2396573/1633944306448-0467a738-f0f6-494c-98c2-7ce93109a76c.png#clientId=u0fd5dd52-3972-4&from=paste&height=218&id=uef5b4c01&margin=%5Bobject%20Object%5D&name=image.png&originHeight=435&originWidth=1333&originalType=binary&ratio=1&size=122738&status=done&style=shadow&taskId=u343c57e2-1e69-4a8f-93d2-b951dec1be2&width=666.5)
```java
import java.util.concurrent.*;

/**
 * ForkJoinPool 计算数组 1-100 的和
 * 有点像递归
 */
public class Demo6 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        int[] nums = new int[100];
        for (int i = 1; i <= 100; i++) {
            nums[i - 1] = i;
        }

        ForkJoinPool joinPool = new ForkJoinPool();
        Future<Integer> future = joinPool.submit(new FjTask(nums, 0, nums.length - 1));
        System.out.println(future.get());
    }

    private static class FjTask extends RecursiveTask<Integer> {
        // 定义一个阈值,只有大于这个阈值才会拆分
        private static final int H = 10;

        private int[] nums;
        private int start, end; // 数组下标

        public FjTask(int[] nums, int start, int end) {
            this.nums = nums;
            this.start = start;
            this.end = end;
        }

        @Override
        protected Integer compute() {
            Integer sum = 0;

            if (end - start < H) { // 小于阈值,直接算
                for (int i = start; i <= end; i++) {
                    sum += nums[i];
                }
            } else { // 否则,拆分
                int mid = (end + start) / 2;
                FjTask left = new FjTask(nums, start, mid);
                FjTask right = new FjTask(nums, mid + 1, end);
                // 拆分
                left.fork();
                right.fork();
                // 合并
                sum = left.join() + right.join();
            }

            return sum;
        }
    }
}

十二、ThreadLocal

ThreadLocal 是一个工具类,可以将数据绑定到当前线程上,从而实现线程间数据的隔离
image.png

public class Demo7 {
    private static ThreadLocal<Object> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        new Thread(new ThreadTask(100)).start();
        new Thread(new ThreadTask(200)).start();
        new Thread(new ThreadTask(300)).start();
    }

    public static void first(){
        System.out.println(Thread.currentThread().getName() + "执行了first()");
        second();
    }

    public static void second() {
        System.out.println(Thread.currentThread().getName() + "执行了second()");
        third();
    }

    public static void third() {
        System.out.println(Thread.currentThread().getName() + "执行了third()");
        System.out.println(Thread.currentThread().getName() + "绑定了" + threadLocal.get());
    }

    private static class ThreadTask implements Runnable {
        private Object value;

        public ThreadTask(Object value) {
            this.value = value;
        }

        @Override
        public void run() {
            threadLocal.set(value); // 放入threadLocal
            first();
        }
    }
}

十三、线程安全的集合

  1. 包装不安全的集合

image.png

  1. 线程安全的集合

image.png