一、进程和线程的区别?

    进程是操作系统分配资源的最小单位,进程会加载到CPU中运行,每隔一个时间片就会切换。进程之间空间独立,互不影响。
    线程是操作系统调度的最小单位,他是属于进程。一个进程可以有多个线程,但是一个线程只能有一个进程。进程之间共享进程堆区和方法区,从而很容易达到线程间通信,但也是由于这个导致线程存在安全问题。

    1.1 线程存在哪些状态?

    1、NEW(新建);
    2、RUNNABLE(准备就绪);
    3、BLOCKED(阻塞);
    4、WAITING(不见不散); - 线程处于阻塞状态,等待事件发生
    5、TIME_WAITING(过时不候); - 线程在规定时间内处于阻塞,超过时间就不管了,继续运行。
    6、TERMINATED(终结)

    1.2 wait和sleep区别?

    共同点: 两者都是让线程处于阻塞状态。

    1. wait是Object类提供的方法;sleep是Thread类提供的静态方法
    2. sleep方法不会释放锁,也不会占用锁,wait会释放锁,前提需要加锁。
    3. 他们都可以被interupted方法中断。
    4. 两个方法唤醒之后都是在原来的程序继续执行。

    1.3 并发和并行?

    并发: 多个线程使用同一个CPU,CPU通过切换线程达到线程之间同时运行。每过一个时间片就会切换一个线程,从而达到同时运行的效果。但其实不是。并发是多个线程互相抢占CPU资源的。
    并行: 多线程使用多个CPU, 不同的CPU共同协助处理这些线程的任务。CPU运行互不影响。并行并不会。并行只有在多CPU才能执行。

    1.3 管程 - (Java锁)

    在操作系统称为Mintor监视器,保证同一时间只能有一个线程被保护的资源。
    JVM同步基于进入和退出实现的,是通过管程对象控制的(也就是锁)。

    1.4 用户线程和守护线程

    用户线程:Java自定义的线程都是用户线程。
    守护线程:是一种特殊线程,在后台运行,GC垃圾回收线程。
    守护线程的优先级很低, 当主线程结束的时候,守护线程资源回收,但是用户线程仍然可以运行。

    二、Lock接口(关于锁使用接口)

    2.1 Synchronized关键字

    • Synchronized是一种同步锁。
    1. 修饰一个代码块,被修饰的代码块称为同步代码块,在{}范围内的代码 Synchronized(this) { 同步代码块}
    2. 修饰一个方法,该方法称为同步方法。
    3. 修饰一个静态方法, 会改变其修饰的范围。
    4. 修饰一个类,修饰的是这个类的所有实例化对象。

    2.2 Lock接口 (手动加锁和解锁)

    ReentrantLock实现类: 这是一个可重入锁,使用的时候需要手动加锁和解锁。
    private final ReentrantLock lock = new ReentrantLock();
    lock.lock - 加锁
    lock.unlock - 解锁

    start()方法会立刻创建线程吗?
    不一定,因为start()方法底层调用native本地方法。这个取决于操作系统。

    可重入锁和不可重入锁:
    可重入锁: 一个线程获取这个锁,里面再次获取锁时可以直接获取不需要竞争。 最鲜明的就是递归调用同步方法。 synchronized 和 ReentrantLock 都是可重入锁。
    不可重入锁: 一个线程获取这个锁,里面再次获取这个锁需要竞争。

    Lock和synchronized的区别?

    1. Lock是一个接口, synchronized是关键字。
    2. synchronized关键字会自动加锁和解锁,Lock需要手动加锁和解锁。
    3. synchronized发生异常时,会自动释放占有锁,不会导致死锁,而Lock发生异常,则有可能导致死锁产生,需要搭配finally强制释放锁。
    4. Lock可以知道获取锁资源是否成功, synchronized则不能。
    5. Lock可以让等待锁资源的线程中断。而synchronized却不行它会一致等待下去。
    6. Lock可以提高多个线程进行操作效率。

    如果竞争不是很激烈,两者的性能差距不大,如果竞争很激烈, Lock远远大于synchronized的效率。

    2.3 虚拟唤醒

    notify 和 notifyAll (notify唤醒某个线程,notifyAll唤醒所有的线程)
    所谓虚拟唤醒: 生产线程生产完毕之后,唤醒其他线程,但是又唤醒了其他生产线程,就这样下去,导致消费小城不能获取锁资源。对于整体来说性能下降。

    我们可以将入口方法中的条件判断从if改变为while。但是还是会存在无意思的唤醒操作。

    2.4 线程间通信

    // 共享资源变量
    private int number = 0;

    // 消费方法
    public synchronized void sale() throws InterruptedException {
    while (1 != number) {
    // 资源为空, 处于阻塞状态
    this.wait();
    }
    number—;
    // 唤醒生产现场进行生产
    System.out.println(Thread.currentThread().getName() + “::” + number);
    this.notifyAll();
    }

    public synchronized void add() throws InterruptedException {
    while (0 != number) {
    this.wait();
    }
    number++;
    System.out.println(Thread.currentThread().getName() + “::” + number);
    this.notifyAll();
    }

    下面是Lock实现的生产消费模型:

    class ShareLockClass {
    // 共享资源变量
    private int number = 0;
    // 创建一个可重入锁 (也就是一般的锁,获取锁资源,其他线程不能操作,释放才能操作)
    private final ReentrantLock lock = new ReentrantLock();
    // 创建条件变量
    private final Condition condition = lock.newCondition();
    // 消费方法
    public void sale() throws InterruptedException {
    // 加锁
    lock.lock();
    try {
    while (1 != number) {
    // 资源为空, 处于阻塞状态
    condition.await();
    }
    number—;
    System.out.println(Thread.currentThread().getName() + “::” + number);
    // 唤醒生产现场进行生产
    condition.signalAll();
    } finally {
    lock.unlock();
    }
    }

    1. public void add() throws InterruptedException {<br /> lock.lock();<br /> try {<br /> while (0 != number) {<br /> condition.await();<br /> }<br /> number++;<br /> System._out_.println(Thread._currentThread_().getName() + "::" + number);<br /> condition.signalAll();<br /> } finally {<br /> lock.unlock();<br /> }<br /> }<br />}

    2.5 线程间定制化通信

    class ShardLock {
    // 创建锁资源
    private final ReentrantLock lock = new ReentrantLock();
    // 创建条件变量1
    private final Condition cond1 = lock.newCondition();
    // 创建条件变量2
    private final Condition cond2 = lock.newCondition();
    // 创建条件变量3
    private final Condition cond3 = lock.newCondition();

    1. private int flag = 1;
    2. public void Print5(int num) {<br /> // 加锁<br /> lock.lock();<br /> try {<br /> // 首先判断标志位是否为5<br /> while (1 != flag) {<br /> // 处于阻塞状态并且释放锁资源<br /> cond1.await();<br /> }<br /> for (int i = 0; i < 5; i++) {<br /> System._out_.println(Thread._currentThread_().getName() + "::" + "执行了 A, 第" + i + "次, 第"+ num + "轮");<br /> }<br /> // 更改标志位<br /> flag = 2;<br /> // 唤醒2, 并且释放锁资源。<br /> cond2.signal();<br /> } catch (InterruptedException e) {<br /> e.printStackTrace();<br /> } finally {<br /> lock.unlock();<br /> }<br /> }
    3. public void Print10(int num) {<br /> // 加锁<br /> lock.lock();<br /> try {<br /> // 首先判断标志位是否为10<br /> while (2 != flag) {<br /> // 处于阻塞状态并且释放锁资源<br /> cond2.await();<br /> }<br /> for (int i = 0; i < 10; i++) {<br /> System._out_.println(Thread._currentThread_().getName() + "::" + "执行了 B, 第" + i + "次, 第"+ num + "轮");<br /> }<br /> // 更改标志位<br /> flag = 3;<br /> // 唤醒3, 并且释放锁资源。<br /> cond3.signal();<br /> } catch (InterruptedException e) {<br /> e.printStackTrace();<br /> } finally {<br /> lock.unlock();<br /> }<br /> }<br /> public void Print15(int num) {<br /> // 加锁<br /> lock.lock();<br /> try {<br /> // 首先判断标志位是否为15<br /> while (3 != flag) {<br /> // 处于阻塞状态并且释放锁资源<br /> cond3.await();<br /> }<br /> for (int i = 0; i < 15; i++) {<br /> System._out_.println(Thread._currentThread_().getName() + "::" + "执行了 C, 第" + i + "次, 第"+ num + "轮");<br /> }<br /> // 更改标志位<br /> flag = 1;<br /> // 唤醒2, 并且释放锁资源。<br /> cond1.signal();<br /> } catch (InterruptedException e) {<br /> e.printStackTrace();<br /> } finally {<br /> lock.unlock();<br /> }<br /> }<br />}

    三、集合的线程安全

    3.1 ArrayList

    ArrayList是线程不安全的集合。

    解决方案:
    1、 vector, 他会在里面的方法添加synchronized保证线程安全。JDK1.0提供的方案,太古老
    List list = new Vector<>();
    2、Collections.synchronizedList创建List对象。
    List list = Collections.synchronizedList(new ArrayList<>());
    3、以上提供的方法比较古老,实际上使用JUC提供的方法偏多。 CopyOnWriteArrayList接口,采用写时拷贝技术实现。对于读的时候支持并发读,但是写的时候会创建一个集合,写完毕之后和老的集合融合。之后读取使用新的集合。适合于多读小写的场景。
    List list = new CopyOnWriteArrayList<>();

    3.2 HashSet和HashMap

    原生提供的是线程不安全的HashSet和HashMap

    解决方案:
    1、JUC提供了针对HashSet的线程安全集合CopyOnWriteArraySet集合。
    HashSet使用HashMap实现相关的接口
    而CopyOnWriteArraySet则是使用CopyOnWriteArrayList实现相关的接口。
    Set concurrentHashSet = new CopyOnWriteArraySet<>();
    2、JUC提供了针对HashMap线程安全集合ConcurrentHashMap集合。
    Map concurrentHashMap = new ConcurrentHashMap<>();

    **四、多线程锁


    4.1 synchronized锁的原理**

    1. synchronized加在方法上,实际上是锁的这个对象。 调用同一个类对象中的synchronized方法,就会产生竞争行为,因为抢的是同一把锁。
    2. 静态synchronized方法锁的Class方法,而不是对象。所以判断是否会产生竞争行为就看是否抢占同一把锁。

    static方法锁的是当前类的class对象,而普通方法锁的是实例化对象。

    image.png

    4.2 公平锁和非公平锁

    不公平锁多个消费线程,结果都被消费者1消费完毕了,消费者2和3没有消费。导致一个线程把所有活都干了,饿死其他线程。
    公平锁多个消费线程,保证每个消费者线程均匀消费。

    各自优缺点:
    不公平锁可能会导致线程饿死,但是执行效率快
    公平锁: 每个线程均匀使用,但是执行效率会低些。

    使用: private final ReentrantLock lock = new ReentrantLock(true); true为公平锁; false为不公平锁

    4.2 可重入锁(ReentrantLock; 递归锁)

    可重入锁: 线程使用获取锁资源,使用完毕之后释放锁资源。
    synchronized和ReentrantLock都是可重入锁。
    synchronized是隐式的可重复锁,ReentrantLock是显式的可重入锁。

    4.3 死锁

    概念: 两个或者两个以上的进程,互相抢占对方已经占有的锁资源导致互相阻塞,并且不释放自己占有的锁资源。
    Java查看死锁情况:jps查看进程id, jstack 进程id 查看调用堆栈信息。

    五、Callable接口
    image.png

    Callable接口是Java新创建线程的一种方式。之前使用继承Thread方法创建,或者使用Runnable接口实现。
    之前创建线程的方式无法获取子线程执行状态和信息, 因此Callable的诞生就是为了解决无法监控子线程信息状态的情况。

    5.1 Callable和Runnable接口对比?

    1. Callable需要实现call方法, Runnable需要实现run方法。
    2. Callable有返回值, 但是Runnable没有返回值。
    3. call可以引发异常, 但是run方法不会。
    4. Callable不能替换Runnable, 因为Thread构造器类没有Callable。
    5. Callable创建线程需要使用中间关系。FutureTask 未来托管

    image.png

    何为FutureTask未来任务?

    Future对于具体的Runnable或者任务执行取消、查询、是否完成、获取结果。
    其中提供了cancle取消任务,取消成功返回true, 失败返回false
    get方法获取执行结果,如果线程没有执行完毕则让调用者处于阻塞状态。
    isDone方法判断子线程是否执行完成。
    FutureTask继承Future接口。

    // 创建Callable接口,并且交托给FutureTask任务接管
    FutureTask task = new FutureTask<>(() -> {
    return true;
    });

    new Thread(task, “bbb”).start();
    while (!task.isDone()) {
    // 判断子线程是否执行完毕, 执行完毕返回true, 否则返回false
    System.out.println(“子线程还在执行中”);
    }
    // 如果子线程没有执行完毕,则会让主线程阻塞,执行完毕则返回执行成功状态
    System.out.println(task.get());

    这样通过Callable搭配FutureTask任务接管就可以实现对子线程的监控和获取执行成功的状态信息。弥补了Thread和Runnable接口创建线程的不足之处。

    六、JUC并发编程辅助类(计数器、循环栅栏、信号灯)

    6.1 计数器(CountDownLatch)

    概述:它里面有一个计数器,初始化时会赋值起始计数器。当一个线程执行await方法会处于阻塞方法,直接计数器=0所有等待的线程被唤醒。

    作用:用它目的可以通过计数器限制,限制线程。他和循环栅栏很像,但是他的计数不能重置。

    其中提供了await、countDown、getCount等方法。
    await方法, 如果计数器不为0,则阻塞直到计数器=0,或者阻塞阶段被中断。
    coutnDown,让计数器—

    6.2 循环栅栏(CyclicBarrier)

    概述:他可以设置一个屏障数,当一个线程调用await方法,就算打破一层屏障,屏障数减1,如果屏障数>0,则await方法会让处于阻塞状态,如果屏障数等于0,则所有由于await方法阻塞的线程被唤醒执行。

    作用:可以让线程们在事件触发时一同触发。事件未触发时处于准备状态。

    构造方法中有两个
    CyclicBarrier cyclicBarrier = new CyclicBarrier(number)
    CyclicBarrier cyclicBarrier = new CyclicBarrier(number, ()-> {
    System.out.println(“我终于突破屏障了”);
    });

    可以设置Runnable方法一个方法,等屏障突破之后就会执行。

    6.3 信号灯(Semaphore)

    概括:可以设置一个信号灯数量,当一个线程获取到信号时则可以执行,否则处于阻塞竞争信号灯状态。

    作用: 这个可以保证线程有条稳的执行,有点像交通灯一样。
    其中提供acquire抢占一个信号, release释放信号。

    七、读写锁、乐观锁、悲观锁

    7.1 锁分类
    7.1、悲观锁: 也就是传统的加锁操作,每个线程只有获取到锁资源才能操作。其他线程只能等待锁资源释放竞争锁资源。好处在于能够解决并发中的各种问题,缺点在于不支持并发操作,效率低下。 只能串行。

    7.2、乐观锁:允许线程们获取锁资源,但是操作完毕提交时,需要判断自己的版本号是否和数据库中的版本号一致,如果一致则认为第一次修改则修改,修改完成之后会更改数据库中的版本号。 这样其他线程再进来发现版本号不一致则无法修改。 只能重新获取最新数据库资源继续操作,以此类推。

    7.3、表锁: 对数据库中的表中一行进行操作,我们把整张表锁起来,不允许别的线程访问这张表
    特点: 开销小, 加锁快,不会出现死锁,同样发生锁冲突大,并发性低
    7.4、行锁:把数据库中的一行锁起来,不允许别的线程访问这一行,但是可以访问别的行。
    特点:开销大,加锁慢,会出现死锁,发生锁冲突小,并发性高。

    表锁和行锁的使用:表锁适合以查询为主,只有按照索引更新数据的应用,行锁更加适合大量按照索引条件更新少量不同数据,同时又有并发查询的应用。

    7.5、读写锁:读写锁分为读锁和写锁,对于读锁来说它属于共享锁,他允许多个线程同时获取锁资源读取。 对于写锁来说它属于独享锁,每个线程需要竞争锁资源进行写操作。
    不过读操作需要写操作结束才能执行, 写操作可以进行读操作(锁降级)

    读锁和写锁都会产生死锁。
    读锁的死锁操作:线程1对数据库资源进行读操作和写操作, 线程2也对资源进行读操作和写操作。 线程1的写操作再等待线程2的读操作结束,线程2的写操作也再等待线程1的读操作结束。 导致死锁。
    写锁的死锁操作:有两条记录, 线程1获取第一条记录的写锁资源, 线程2获取第二条记录的写锁资源,单线程1还想操作第二条记录,线程2箱操作第一条记录。导致互相阻塞。

    读写锁的使用:
    1、创建读写锁: private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    2、rwLock.writeLock().lock() & rwLock.writeLock().unlock()
    3、rwLock.readLock().lock() & rwLock.readLock().unlock()

    class Mycache {
    // 创建共享数据
    private volatile Map map = new HashMap<>();
    // 创建一个读写锁
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();

    1. public void put(String key, Object value) {<br /> // 添加写锁<br /> rwLock.writeLock().lock();<br /> try {<br /> System._out_.println(Thread._currentThread_().getName() + "正在写操作" + key);<br /> try {<br /> TimeUnit._MICROSECONDS_.sleep(300);<br /> } catch (InterruptedException e) {<br /> e.printStackTrace();<br /> }
    2. map.put(key, value);<br /> System._out_.println(Thread._currentThread_().getName() + "写完了" + key);<br /> } finally {<br /> // 释放写锁<br /> rwLock.writeLock().unlock();<br /> }<br /> }
    3. public Object get(String key) {<br /> // 添加读共享锁<br /> rwLock.readLock().lock();<br /> try {<br /> System._out_.println(Thread._currentThread_().getName() + "正在读取操作" + key);<br /> try {<br /> TimeUnit._MICROSECONDS_.sleep(300);<br /> } catch (InterruptedException e) {<br /> e.printStackTrace();<br /> }
    4. Object obj = map.get(key);<br /> System._out_.println(Thread._currentThread_().getName() + "取完了" + key);<br /> return obj;<br /> } finally {<br /> // 释放读共享锁<br /> rwLock.readLock().unlock();<br /> }<br /> }<br />}

    7.2 读写锁的演变

    读写锁的优缺点:
    优点:
    1、相对于synchronized和可重入锁提高了并发性,对于读操作允许多个线程共享一把锁。对于写和之前一样。
    缺点:
    1、读操作时不能写,写操作时不能读,有可能导致一直读,没有写操作,造成写锁或读锁资源空闲。
    2、读的时候不能写,读完之后才可以写,写的时候可以读。

    锁降级: 一般认为写锁的操作优先级大于读锁的操作,将写锁降级读锁就是锁降价,这也是保证写操作时也可读操作的原因。

    在JDK8中说明:获取写锁操作之后,还可以获取读锁操作,然后释放写锁,释放读锁。

    ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    ReentrantReadWriteLock.WriteLock wLock = rwLock.writeLock();
    ReentrantReadWriteLock.ReadLock rLock = rwLock.readLock();

    八、阻塞队列

    8.1 概述
    阻塞队列就是之前的生产消费模型,生产线程+消费线程+线程安全的队列。
    同一时间只能有一个线程对队列进行操作(所以这个线程竞争同一把锁资源)。
    队列满了之后会唤醒消费线程。
    队列空之后会唤醒生产线程。
    image.png

    8.2 阻塞队列

    ArrayListBlockingQueue: (由数组组成有界的队列)底层通过数组实现,生产者和消费者共用一把锁,两者对于队列的使用就会竞争锁。
    image.png
    LinkedBlockingQueue(常用,由链表组成有界队列):底层通过链表实现,生产者和消费者独立使用各自的锁,允许生产者和消费者并发操作队列。
    image.png
    DelayQueue(优先级队列)
    image.png

    LinkedBlockingDeque(双向阻塞队列)
    image.png
    由链表组成的双向阻塞队列。
    image.png

    其中add和remove方法: add方法插入元素,当队列满的时候就会抛出异常,同理remove方法当队列为空的时候抛出异常。

    其中offer和poll方法, 插入成功返回true, 队列满插入失败返回false,不会抛出异常。

    九、线程池(ThreadPool)

    9.1 线程池的优势
    image.png

    1. 降低资源消耗:线程的创建和销毁会带来开销, 一开始创建大量的线程,当需要线程的时候能够快速分配线程。同时线程执行完毕之后放到线程池中管理,减少销毁带来的损耗。
    2. 提供相应速度:需要线程时直接从线程池中快速获取,减少了线程创建的损耗。
    3. 提高线程管理:线程资源是稀有资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统稳定性。使用线程池管理可以做到统一分配调优、调优、监控。

    Java中的线程池是通过Executors框架实现的。
    image.png

    9.2 线程池的分类

    Executors.newFixedThreadPool(int n):(一池n线程) 创建一个线程池里面有n个线程。

    1. 线程池中的线程处于一定的量,可以很好的控制线程的并发量。
    2. 线程可以重复被使用,在显示关闭之前,都将一致存在。
    3. 超出一定量的线程(超过任务的数量)需要在队列当中等待。

    ExecutorService pool = Executors.newFixedThreadPool(5);
    try {
    for (int i = 0; i < 10; i++) {
    pool.execute(() -> {
    System.out.println(Thread.currentThread().getName() + “办理业务”);
    });
    }
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    pool.shutdown();
    }

    Executors.newSingleThreadExecutor(): (一池一线程)创建一个线程池只有一个处理线程。

    ExecutorService pool = Executors.newSingleThreadExecutor();
    try {
    for (int i = 0; i < 10; i++) {
    pool.execute(() -> {
    System.out.println(Thread.currentThread().getName() + “办理业务”);
    });
    }
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    pool.shutdown();
    }

    Executors.newCachedThreadPool(): (可扩容线程池,遇强则强)创建一个线程池里面的线程数随着任务数量动态发生变化。

    ExecutorService pool = Executors.newCachedThreadPool();
    try {
    for (int i = 0; i < 10; i++) {
    pool.execute(() -> {
    System.out.println(Thread.currentThread().getName() + “办理业务”);
    });
    }
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    pool.shutdown();
    }

    9.3 线程池的底层实现

    这些线程池的底层实现都是使用ThreadPoolExecutor创建的。
    image.png

    • corePoolSize: 常驻线程数量(核心线程数量)
    • maximumPoolSize: 最大线程数量
    • keepAliveTime、unit: 当线程大于常驻线程时,多余的空间线程存活时间。
    • workQueue: 阻塞队列(当线程被用光之后,剩下的任务就先放到阻塞队列中
    • defaultThreadFactory: 用于创建线程
    • defaultHandler: 线程被用光,也达到了最大值,并且阻塞队列也满了,就会拒绝任务。

    9.4 线程池的工作流程和拒绝策略
    image.png

    1. 当主线程调用线程池中executor方法时才会创建线程。
    2. 当常驻线程正在忙,并且阻塞队列也满了,此时在来任务,就会创建新的线程去直接处理。(新的线程不会按照先来后到的原则哦,而是直接处理这个任务。阻塞队列还在里面)

    拒绝策略

    1. AbortPolicy(默认):如果常驻线程满了,阻塞队列也满了,也达到了最大线程数,直接抛出RejectedExecutionException异常。
    2. CalleRunsPolicy(调用者模式):如上情况,该策略不会抛弃任务,也不会抛出异常,而是将任务返回给调用者,从而降低新任务的流量。
    3. DiscardOldestPolicy():如上情况,抛弃队列中最久的任务,然后把任务放到阻塞队列中。
    4. DiscardPolicy():如上情况,默默抛弃任务,也不抛弃异常,如果允许任务丢失,这是最好的策略。

    9.5 自定义线程池
    image.png
    我们一般使用自定义线程池ThreadPoolExecutor使用。

    自定义线程数过程:
    image.png

    1. FixedThreadPool和SignalThreadPool允许的阻塞队列长度为Integer.MAX_VALUE, 有可能因为等待任务过多导致的OOM
    2. CachedThreadPool允许的最大线程数为Integer.MAX_VALUE, 有可能因为线程数过多导致的OOM。
    3. 所以阿里规范线程池使用ThreadPoolExecutor手动配置。

    十、Fork/Join框架介绍

    10.1 Fork/Join框简介

    Fork/Join他可以将一个大任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出。

    Fork: 将一个复杂任务进行拆分,大事化小
    Join: 把拆分的任务执行的结果进行合并。

    class MyTask extends RecursiveTask {
    // 拆分值不能超过10
    private static final Integer VALUE = 10;
    // 拆分开始值
    private int begin;
    // 拆分结束值
    private int end;
    private int result;

    1. public MyTask(int begin, int end) {<br /> this.begin = begin;<br /> this.end = end;<br /> }
    2. // 实现拆分和合并的过程<br /> @Override<br /> protected Integer compute() {<br /> // 判断相加的两个值是否大于0<br /> if ((end - begin) < _VALUE_) {<br /> // 如果小于,则代表执行完毕,相加操作<br /> for (int i = begin; i <= end ; i++) {<br /> result += i;<br /> }<br /> } else {<br /> // 继续进行拆分操作<br /> int middle = begin + (end - begin) / 2;<br /> // 拆分左边<br /> MyTask task_left = new MyTask(begin, middle);<br /> // 拆分右边<br /> MyTask task_right = new MyTask(middle + 1, end);<br /> task_left.fork();<br /> task_right.fork();
    3. result = task_left.join() + task_right.join();<br /> }<br /> return result;<br /> }<br />}

    public class ForkAndJoinTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    MyTask myTask = new MyTask(1, 100);
    // 创建分支合并池对象
    ForkJoinPool pool = new ForkJoinPool();
    ForkJoinTask submit = pool.submit(myTask);
    // 获取执行之后的结果
    Integer result = submit.get();
    System.out.println(result);
    // 关闭池对象
    pool.shutdown();
    }
    }

    十一、同步和异步

    同步: 主线程执行一个请求某些请求时,如果这个请求需要等待一段时间则这个线程就阻塞等待。 直接可以处理这个请求。(此时这个线程不会处理任何事,案例中有等待IO资源就绪,线程会一值等待IO就绪,直到就绪去执行。)
    异步,同上,如果这个请求需要一段时间就会执行主线程的任务,当这个请求就绪时然后通知再去执行这个请求。(同上,IO等待中,线程恢复到主业务中去执行。当IO资源就绪时会通知主线程去执行。)

    1. public static void main1(String[] args) throws ExecutionException, InterruptedException {<br /> // 异步调用没有返回值<br /> CompletableFuture<Void> completableFuture = CompletableFuture._runAsync_(() -> {<br /> // 异步执行这个方法<br /> System._out_.println(Thread._currentThread_().getName() + "completableFuture1");<br /> try {<br /> Thread._sleep_(10000);<br /> } catch (InterruptedException e) {<br /> e.printStackTrace();<br /> }<br /> });<br /> System._out_.println("我是主流程");<br /> System._out_.println(completableFuture.get());<br /> }
    2. public static void main(String[] args) throws ExecutionException, InterruptedException {<br /> // 异步调用没有返回值<br /> CompletableFuture<Integer> completableFuture = CompletableFuture._supplyAsync_(() -> {<br /> // 异步执行这个方法<br /> System._out_.println(Thread._currentThread_().getName() + "completableFuture1");<br /> int i = 10 / 0;<br /> try {<br /> Thread._sleep_(10000);<br /> } catch (InterruptedException e) {<br /> e.printStackTrace();<br /> }<br /> return 1024;<br /> });<br /> System._out_.println("我是主流程");<br /> completableFuture.whenComplete((t, u) -> {<br /> // 获取执行成功的返回值<br /> System._out_.println(t);<br /> // 获取执行中抛出的异常信息<br /> System._out_.println(u);<br /> }).get();<br /> }<br />}