JUC高并发编程

1.JUC概述

1.1什么是juc

在 Java 中,线程部分是一个重点,本篇文章说的 JUC 也是关于线程的。JUC就是 java.util .concurrent 工具包的简称。这是一个处理线程的工具包,JDK 1.5 开始出现的。

1.2 线程和进程概念

1.进程和线程

进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。

线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

总结来说:
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位。
线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。

2.线程的状态

class Thread implements Runnable{   
    public enum State {
            /**
             * Thread state for a thread which has not yet started.
             */
        
            NEW,//1.新建

            /**
             * Thread state for a runnable thread.  A thread in the runnable
             * state is executing in the Java virtual machine but it may
             * be waiting for other resources from the operating system
             * such as processor.
             */
            RUNNABLE,//2.准备就绪

            /**
             * Thread state for a thread blocked waiting for a monitor lock.
             * A thread in the blocked state is waiting for a monitor lock
             * to enter a synchronized block/method or
             * reenter a synchronized block/method after calling
             * {@link Object#wait() Object.wait}.
             */
            BLOCKED,//3.阻塞

            /**
             * Thread state for a waiting thread.
             * A thread is in the waiting state due to calling one of the
             * following methods:
             * <ul>
             *   <li>{@link Object#wait() Object.wait} with no timeout</li>
             *   <li>{@link #join() Thread.join} with no timeout</li>
             *   <li>{@link LockSupport#park() LockSupport.park}</li>
             * </ul>
             *
             * <p>A thread in the waiting state is waiting for another thread to
             * perform a particular action.
             *
             * For example, a thread that has called <tt>Object.wait()</tt>
             * on an object is waiting for another thread to call
             * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
             * that object. A thread that has called <tt>Thread.join()</tt>
             * is waiting for a specified thread to terminate.
             */
            WAITING,//4.等待,不见不散

            /**
             * Thread state for a waiting thread with a specified waiting time.
             * A thread is in the timed waiting state due to calling one of
             * the following methods with a specified positive waiting time:
             * <ul>
             *   <li>{@link #sleep Thread.sleep}</li>
             *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
             *   <li>{@link #join(long) Thread.join} with timeout</li>
             *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
             *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
             * </ul>
             */
            TIMED_WAITING,//5.等待,过时不候

            /**
             * Thread state for a terminated thread.
             * The thread has completed execution.
             */
            TERMINATED;//6.终结
        }
    }

3.wati和sleep

(1)sleep 是 Thread 的静态方法,wait 是 Object 的方法,任何对象实例都能调用。
(2)sleep 不会释放锁,它也不需要占用锁。wait 会释放锁,但调用它的前提是当前线程占有锁(即代码要在 synchronized 中)。
(3)它们都可以被 interrupted 方法中断。

4.并发和并行

1.串行模式

串行表示所有任务都一一按先后顺序进行。串行意味着必须先装完一车柴才能运送这车柴,只有运送到了,才能卸下这车柴,并且只有完成了这整个三个步骤,才能进行下一个步骤。
串行是一次只能取得一个任务,并执行这个任务。

2.并行模式

并行意味着可以同时取得多个任务,并同时去执行所取得的这些任务。并行模式相当于将长长的一条队列,划分成了多条短队列,所以并行缩短了任务队列的长度。并行的效率从代码层次上强依赖于多进程/多线程代码,从硬件角度上则依赖于多核 CPU。

3. 并发

并发(concurrent)指的是多个程序可以同时运行的现象,更细化的是多进程可以同时运行或者多指令可以同时运行。但这不是重点,在描述并发的时候也不会去扣这种字眼是否精确,并发的重点在于它是一种现象, 并发描述的是多进程同时运行的现象。但实际上,对于单核心 CPU 来说,同一时刻只能运行一个线程。所以,这里的"同时运行"表示的不是真的同一时刻有多个线程运行的现象,这是并行的概念,而是提供一种功能让用户看来多个程序同时运行起来了,但实际上这些程序中的进程不是一直霸占 CPU 的,而是执行一会停一会。要解决大并发问题,通常是将大任务分解成多个小任务, 由于操作系统对程的调度是随机的,所以切分成多个小任务后,可能会从任一小任务处执行。这可能会出现一些现象:

  • 可能出现一个小任务执行了多次,还没开始下个任务的情况。这时一般会采用
    队列或类似的数据结构来存放各个小任务的成果

  • 可能出现还没准备好第一步就执行第二步的可能。这时,一般采用多路复用或
    异步的方式,比如只有准备好产生了事件通知才执行某个任务。

  • 可以多进程/多线程的方式并行执行这些小任务。也可以单进程/单线程执行这
    些小任务,这时很可能要配合多路复用才能达到较高的效率

4. 小结(重点)

并发:同一时刻多个线程在访问同一个资源,多个线程对一个点
例子:春运抢票 电商秒杀…
并行:多项工作一起执行,之后再汇总
例子:泡方便面,电水壶烧水,一边撕调料倒入桶中

5.管程

操作系统中叫监视器,java中叫锁

**管程(monitor)**是保证了同一时刻只有一个进程在管程内活动,即管程内定义的操作在同一时刻只被一个进程调用(由编译器实现).但是这样并不能保证进程以设计的顺序执行JVM 中同步是基于进入和退出管程(monitor)对象实现的,每个对象都会有一个管程(monitor)对象,管程(monitor)会随着 java 对象一同创建和销毁执行线程首先要持有管程对象,然后才能执行方法,当方法完成之后会释放管程,方法在执行时候会持有管程,其他线程无法再获取同一个管程

6.用户线程和守护线程

**用户线程:**平时用到的普通线程,自定义线程
**守护线程:**运行在后台,是一种特殊的线程,比如垃圾回收
当主线程结束后,用户线程还在运行,JVM 存活,如果没有用户线程,都是守护线程,JVM 结束

代码1:

public static void main(String[] args) {
    Thread aa = new Thread(() -> {
        System.out.println(Thread.currentThread().getName()+"用户线程");
        while(true){

        }
    }, "aa");
    aa.start();
    System.out.println(aa.isDaemon()+"==是否是守护线程");
    System.out.println(Thread.currentThread().getName()+"====over");
}

//输出如下,且服务未停止
false==是否是守护线程
main====over
aa用户线程

代码2:

public static void main(String[] args) {
    Thread aa = new Thread(() -> {
        System.out.println(Thread.currentThread().getName()+"用户线程");
        while(true){}
    }, "aa");
    aa.setDaemon(true);
    aa.start();
    System.out.println(aa.isDaemon()+"==是否是守护线程");
    System.out.println(Thread.currentThread().getName()+"====over");
}
//输出如下,且服务已停止
true==是否是守护线程
main====over
aa用户线程

2.LOCK接口

2.1 复习Synchronized

1.Synchronized作用范围

synchronized 是 Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
synchronized(this){
}
  1. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;o 虽然可以使用 synchronized 来定义方法,但 synchronized 并不属于方法定义的一部分,因此,synchronized 关键字不能被继承。如果在父类中的某个方法使用了synchronized 关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized 关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。

  2. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;

  3. 修改一个类,其作用的范围是 synchronized 后面括号括起来的部分,作用主的对象是这个类的所有对象。

2.Synchronized实现卖票例子

3个售票员 卖出30张票

//1.定义资源,设置资源的操作方式
class Ticket{
    private int num=30;
    public synchronized void sale(){
        if(num>0){
            System.out.println(Thread.currentThread().getName()+"卖出了"+num--+",剩余:"+num);
        }
    }
}
public class SaleTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        //2.创建多线程操作
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<30;i++){
                    ticket.sale();
                }
            }
        },"AA").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<30;i++){
                    ticket.sale();
                }
            }
        },"BB").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<30;i++){
                    ticket.sale();
                }
            }
        },"CC").start();
    }
}

3.多线程编程步骤(上)

1.第一 创建资源类,创建属性和操作方法
2.第二 创建多线程调用资源类的方法

2.2 什么是Lock接口

Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock 提供了比 synchronized 更多的功能。

Lock 与的 Synchronized 区别:

  • Lock 不是 Java 语言内置的,synchronized 是 Java 语言的关键字,因此是内置特性。Lock 是一个类,通过这个类可以实现同步访问;
  • Lock 和 synchronized 有一点非常大的不同,采用 synchronized 不需要用户去手动释放锁,当 synchronized 方法或者 synchronized 代码块执行完之后,系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

2.3 Lock实现可重入锁

ReentrantLock

2.4 创建线程的多种方式

  • 1.继承Thread类

  • 2.实现Runnable接口

  • 3.使用Callable接口

  • 4.使用线程池

2.5 使用Lock实现卖票

class LTicket{
    private Integer num = 30;
    //创建可重入锁
    ReentrantLock reentrantLock = new ReentrantLock();
    public void sale(){
        reentrantLock.lock();
        try{
            if(num>0){
                System.out.println(Thread.currentThread().getName()+"卖出了"+num--+"还剩下"+num);
            }
        }finally {
            reentrantLock.unlock();
        }
    }
}
public class SaleTicket {
    public static void main(String[] args) {
        LTicket lTicket = new LTicket();
        new Thread(()->{
            for (int i=0;i<30;i++){
                lTicket.sale();
            }
        },"AA").start();

        new Thread(()->{
            for (int i=0;i<30;i++){
                lTicket.sale();
            }
        },"BB").start();

        new Thread(()->{
            for (int i=0;i<30;i++){
                lTicket.sale();
            }
        },"CC").start();
    }
}

start()方法调用时候线程是否会立刻创建?不一定,start()方法源码如下,start0()中的native java代码无能为力,指令给操作系统,具体由操作系统决定。

class Thread implements Runnable {
	public synchronized void start() {
		/**
		 * * This method is not invoked for the main method thread or "system" *
		 * group threads created/set up by the VM. Any new functionality added *
		 * to this method in the future may have to also be added to the VM. * *
		 * A zero status value corresponds to state "NEW".
		 */
		if (threadStatus != 0)
			throw new IllegalThreadStateException();
		/*
		 * Notify the group that this thread is about to be started * so that it
		 * can be added to the group's list of threads * and the group's
		 * unstarted count can be decremented.
		 */ group.add(this);
		boolean started = false;
		try {
			start0();
			started = true;
		} finally {
			try {
				if (!started) {
					group.threadStartFailed(this);
				}
			} catch (Throwable ignore) {
				/*
				 * do nothing. If start0 threw a Throwable then it will be
				 * passed up the call stack
				 */ }
		}
	}

	private native void start0();
}

Lock 和 synchronized 有以下几点不同:

  1. Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现;
  2. synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很
    可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
  3. Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用synchronized 时,等待的线程会一直等待下去,不能够响应中断;4. 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
  4. Lock 可以提高多个线程进行读操作的效率。
    在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源
    非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于
    synchronized。

3.线程间通信

3.1多线程编程步骤(中)

第二步:在资源类操作方法:

1.判断

2.干活

3.通知

例:有两个线程

实现对一个初始值是0的变量

一个线程对值:+1

一个线程对值:-1

实现+1,-1,+1,-1的交替完成,这个过程就叫做线程间通信

public class Object {
	public final void wait() throws InterruptedException {
		wait(0);
	}

	public final native void notify();

	public final native void notifyAll();
}

线程间通信-synchronized实现:

class Share{
    //第1步:定义资源,资源的操作方法
    private int num = 0;
    //第2步:判断,干活,通知

    //生产
    public synchronized void incr() throws InterruptedException {
        //2.1 判断
        if(num!=0){
            this.wait();
        }
        //2.2 干活
        num++;
        System.out.println(Thread.currentThread().getName()+"==="+num);
        //2.3 通知
        this.notifyAll();
    }

    //消费
    public synchronized void decr() throws InterruptedException {
        //2.1 判断
        if(num!=1){
            this.wait();
        }
        //2.2 干活
        num--;
        System.out.println(Thread.currentThread().getName()+"==="+num);
        //2.3 通知
        this.notifyAll();
    }

}
public class ThreadDemo {
    public static void main(String[] args) {
        Share share = new Share();

        new Thread(() -> {
            try {
                for(int i=1;i<=10;i++){
                    share.incr();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"生产者").start();

        new Thread(() -> {
            try {
                for(int i=1;i<=10;i++){
                    share.decr();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"消费者").start();
    }
}

3.2 虚假唤醒问题

给上述线程间通信-synchronized实现各自多增加一个生产者和消费者,即会出现小于0或者大于1的情况出现,究其原因在于生产者1生产后,生产者2被唤醒并抢到资源,然后wait(),后生产者1又被唤醒,生产者2又被唤醒,因此从wait()地方醒来,继续执行后续逻辑,即产生超过1的数值,消费者小于0同理。

//生产
public synchronized void incr() throws InterruptedException {
    //2.1 判断
    while(num!=0){
        this.wait();
    }
    //2.2 干活
    num++;
    System.out.println(Thread.currentThread().getName()+"==="+num);
    //2.3 通知
    this.notifyAll();
}

//消费
public synchronized void decr() throws InterruptedException {
    //2.1 判断
    while(num!=1){
        this.wait();
    }
    //2.2 干活
    num--;
    System.out.println(Thread.currentThread().getName()+"==="+num);
    //2.3 通知
    this.notifyAll();
}

将上述判断中的if替换为while,即完成在唤醒后的再次判断。

3.3 多线程编程步骤(下)

第四步:防止虚假唤醒问题

3.4 多线程编程步骤总结:

  • 1.第一步: 创建资源类,创建属性和操作方法

  • 2.第二步:在资源类操作方法:

    • 1.判断
    • 2.干活
    • 3.通知
  • 3.第三步: 创建多线程调用资源类的方法

  • 4.第二步:防止虚假唤醒问题

线程间通信-lock实现:

//java.util.concurrent.locks包下Lock接口
public interface Lock {
    //获取锁
    void lock();
    //如果当前线程未被中断,则获取锁
    void lockInterruptibly() throws InterruptedException;
    //仅在调用时锁为空闲状态才获取该锁
    boolean tryLock();
    //如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    //释放锁
    void unlock();
    //返回绑定到此Lock实例的新Condition实例
    Condition newCondition();
}
public interface Condition {
    //造成当前线程在接到信号或被中断之前一直处于等待状态
    void await() throws InterruptedException;
    //造成当前线程在接到信号之前一直处于等待状态
    void awaitUninterruptibly();
    //造成当前线程在接到信号、被中断或到达等待时间之前一直处于等待状态
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    //造成当前线程在街道信号、被中断或到达指定等待事件之前一直处于等待状态
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    //造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态
    boolean awaitUntil(Date deadline) throws InterruptedException;
    //唤醒一个等待线程
    void signal();
    //唤醒所有等待线程
    void signalAll();
}
//1.定义资源,资源的操作
class Share{
    private int num=0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    //2.资源操作里,判断,干活,通知
    //+1
    public void incr() throws InterruptedException {
        lock.lock();
        try {
            //4.防止虚假唤醒
            while(num!=0){
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName()+"::"+num);
            condition.signalAll();
        } finally {
            lock.unlock();
        }

    }
    //-1
    public void decr() throws InterruptedException {
        lock.lock();
        try{
             //4.防止虚假唤醒
            while (num!=1){
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName()+"::"+num);
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }

}

public class ThreadDemo {
    public static void main(String[] args) {
        Share share = new Share();
		//创建多线程调用资源类的方法
        new Thread(() -> {
            try {
                for(int i=1;i<=10;i++){
                    share.incr();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"生产者1").start();

        new Thread(() -> {
            try {
                for(int i=1;i<=10;i++){
                    share.decr();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"消费者1").start();
        
    }
}

4.线程间定制化通信

在这里插入图片描述

/**
关键字synchronized与wait()/notify()这两个方法一起使用可以实现等待/通知模式, Lock锁的newContition()方法返回Condition对象,Condition类也可以实现等待/通知模式。

用notify()通知时,JVM会随机唤醒某个等待的线程, 使用Condition类可以进行选择性通知, Condition比较常用的两个方法:

● await()会使当前线程等待,同时会释放锁,当其他线程调用signal()时,线程会重新获得锁并继续执行。

● signal()用于唤醒一个等待的线程。

注意:在调用Condition的await()/signal()方法前,也需要线程持有相关的Lock锁,调用await()后线程会释放这个锁,在singal()调用后会从当前Condition对象的等待队列中,唤醒 一个线程,唤醒的线程尝试获得锁, 一旦获得锁成功就继续执行。
**/

/**
 * @author :leeliang
 * @date :Created in 2021/8/1 2:30
 * @description:定制化线程通信
 * @modified By:
 * @version: 1.0$
 */
//1.定义资源,创建属性操作方法
class ShareResource{
    private int flag =1;//1 AA;2 BB;3 CC;
    Lock lock = new ReentrantLock();
    Condition c1 = lock.newCondition();
    Condition c2 = lock.newCondition();
    Condition c3 = lock.newCondition();
    //2.在属性操作方法内:判断,干活,通知

    //打印5次方法
    public void print5(int loop) throws InterruptedException {
        lock.lock();
        //判断
        try{
            while(flag!=1){
                c1.await();
            }
            //干活
            for(int i=1;i<=5;i++){
                System.out.println(Thread.currentThread().getName()+"::第"+i+"次"+"=="+loop+"轮");
            }
            //通知BB线程
            flag=2;
            c2.signal();
        }finally {
            lock.unlock();
        }
    }

    //打印10次方法
    public void print10(int loop) throws InterruptedException {
        lock.lock();
        //判断
        try{
            while(flag!=2){
                c2.await();
            }
            //干活
            for(int i=1;i<=10;i++){
                System.out.println(Thread.currentThread().getName()+"::第"+i+"次"+"=="+loop+"轮");
            }
            //通知CC线程
            flag=3;
            c3.signal();
        }finally {
            lock.unlock();
        }
    }

    //打印15次方法
    public void print15(int loop) throws InterruptedException {
        lock.lock();
        //判断
        try{
            while(flag!=3){
                c3.await();
            }
            //干活
            for(int i=1;i<=15;i++){
                System.out.println(Thread.currentThread().getName()+"::第"+i+"次"+"=="+loop+"轮");
            }
            //通知AA线程
            flag=1;
            c1.signal();
        }finally {
            lock.unlock();
        }
    }
}

public class CustomerThread {

    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();
        new Thread(()->{
            for(int i=1;i<=10;i++){
                try {
                    shareResource.print5(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(()->{
            for(int i=1;i<=10;i++){
                try {
                    shareResource.print10(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();

        new Thread(()->{
            for(int i=1;i<=10;i++){
                try {
                    shareResource.print15(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();
    }
}

5.集合的线程安全

5.1ArrayList集合线程不安全演示

//list集合的add方法是线程不安全的
public interface List<E> extends Collection<E> {
    boolean add(E e);
}


public static void main(String[] args) {
    List list = new ArrayList();
    for(int i=0;i<10;i++){
        new Thread(()->{
            String substring = UUID.randomUUID().toString().substring(0, 8);
            list.add(substring);
            System.out.println(list);
        }).start();
    }
}

//报错:ConcurrentModificationException

解决方案1-Vector

//jdk1.0时方案
public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }
}

解决方案2-Collections

//返回指定列表支持的同步列表,比较久远
List list = Collections.synchronizedList(new ArrayList<>());

解决方案3-CopyOnWriteArrayList

package java.util.concurrent
//写时复制技术
List list = new CopyOnWriteArrayList();

//源码,添加为同步操作
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

5.2 HashSet线程不安全

public static void main(String[] args) {
    //Set set = new HashSet();
    //用CopyOnWriteArraySet替代
    Set set = new CopyOnWriteArraySet();
    for(int i=0;i<30;i++){
        new Thread(()->{
            String substring = UUID.randomUUID().toString().substring(0, 8);
            set.add(substring);
            System.out.println(set);
        }).start();
    }
}

5.3 HashMap线程不安全

public static void main(String[] args) {
    //Map map = new HashMap<String,String>();
    //用ConcurrentHashMap替代
    Map map = new ConcurrentHashMap();
    for(int i=0;i<30;i++){
        String key = String.valueOf(i);
        new Thread(()->{
            String substring = UUID.randomUUID().toString().substring(0, 8);
            map.put(key,substring);
            System.out.println(map);
        }).start();
    }
}

6. 多线程锁

6.1演示锁的八种情况

class Phone {

    public static synchronized void sendSMS() throws Exception {
        //停留4秒
        TimeUnit.SECONDS.sleep(4);
        System.out.println("------sendSMS");
    }

    public synchronized void sendEmail() throws Exception {
        System.out.println("------sendEmail");
    }

    public void getHello() {
        System.out.println("------getHello");
    }
}


public class Lock_8 {
    public static void main(String[] args) throws Exception {

        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "AA").start();

        Thread.sleep(100);

        new Thread(() -> {
            try {
               // phone.sendEmail();
               // phone.getHello();
                phone2.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "BB").start();
    }
}

1.标准访问,先打印短信还是邮件

-----sendSMS

-----sendEmail

锁当前对象

2.停4秒在短信方法内,先打印短信还是邮件

-----sendSMS

-----sendEmail

锁当前对象

3.新增普通的hello方法,是先打短信还是hello

-----getHello

-----sendSMS

锁当前对象的同步方法

4.现在有两部手机,先打印短信还是邮件

-----sendEmail

-----sendSMS

两部手机,则为不同锁,sendSms有沉睡一秒

5.两个静态同步方法,1部手机,先打印短信还是邮件

-----sendSMS

-----sendEmail

锁的范围不是当前对象,是字节码对象Class

6.两个静态同步方法,2部手机,先打印短信还是邮件

-----sendSMS

-----sendEmail

锁的范围不是当前对象,是字节码对象Class

7.1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件

-----sendEmail

-----sendSMS

锁的范围为字节码对象Class和this当前类对象

8.1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件

-----sendEmail

-----sendSMS

锁的范围为字节码对象Class和this当前类对象

总结:

synchronized实现同步的基础:JAVA中的每一个对象都可以作为锁,具体表现为以下3种形式:

普通同步方法: 锁是当前实例对象

静态同步方法:锁是当前类的Class对象

同步方法快: 锁是synchronized括号里配置的对象

6.2 公平锁和非公平锁

非公平锁:线程饿死,效率高

公平锁:阳光普照,效率相对低

Lock fairSync = new ReentrantLock(false);
Lock nonfairSync = new ReentrantLock(true);

6.3 可重入锁

synchronized(隐式)和Lock(显式)都是可重入锁

//Synchronized演示可重入锁
public static void main(String[] args) {
    Object o = new Object();
    new Thread(()->{
        System.out.println(Thread.currentThread().getName()+"外层");
        synchronized (o){
            System.out.println(Thread.currentThread().getName()+"中层");
            synchronized (o){
                System.out.println(Thread.currentThread().getName()+"内层");
            }
        }
    },"AA").start();
}
//Lock演示可重入锁
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        new Thread(()->{
            try{
                lock.lock();
                System.out.println(Thread.currentThread().getName()+"外层");
                try{
                    lock.lock();
                    System.out.println(Thread.currentThread().getName()+"内层");
                }finally {
                    //注销此行代码,将影响BB线程一直等待释放锁而不能执行
                    //lock.unlock();
                }
            }finally {
                lock.unlock();
            }
        },"AA").start();

        new Thread(()->{
            try{
                lock.lock();
                System.out.println(Thread.currentThread().getName()+"其他");
            }finally {
                    lock.unlock();
            }
        },"BB").start();
    }

6.4 死锁

1 死锁原因

​ 第一:系统资源不足

​ 第二:进程运行推进顺序不合适

​ 第三:资源分配不当

2 死锁模拟

public static void main(String[] args) {
    Object a = new Object();
    Object b = new Object();

    new Thread(()->{
        synchronized (a){
            System.out.println(Thread.currentThread().getName()+"持有a,等待b");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (b){
                System.out.println(Thread.currentThread().getName()+"拿到b");
            }
        }
    },"AA").start();


    new Thread(()->{
        synchronized (b){
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"持有b,等待a");
            synchronized (a){
                System.out.println(Thread.currentThread().getName()+"拿到a");
            }
        }
    },"BB").start();
    
}

3 验证是否是死锁

  • 1.jps 类似linux ps -ef

  • 2.jstack jvm里自带的堆栈跟踪工具

    jps -l结果:
    在这里插入图片描述

jstack 4264结果:

在这里插入图片描述

7.Callable接口

callable与runnable接口比较:

  • 1)是否有返回值
  • 2)是否抛出异常
  • 3)实现方法名称不同,一个是run方法,一个是call方法

Thread的类构造方法只有接收Runnable对象,没有接收Callable对象的。因此找一个类,既和Runnable有关系,又和Callable也有关系

  • Runnable接口有实现类FutureTask
  • FutureTask构造可以传递Callable

8.JUC强大的辅助类

8.1 减少计数CountDownLatch

CountDownLatch 类可以设置一个计数器,然后通过 countDown 方法来进行减 1 的操作,使用 await 方法等待计数器不大于 0,然后继续执行 await 方法
之后的语句。
• CountDownLatch 主要有两个方法,当一个或多个线程调用 await 方法时,这些线程会阻塞
• 其它线程调用 countDown 方法会将计数器减 1(调用 countDown 方法的线程不会阻塞)
• 当计数器的值变为 0 时,因 await 方法阻塞的线程会被唤醒,继续执行

//场景: 6 个同学陆续离开教室后值班同学才可以关门。
//普通版本
public static void main(String[] args) {
    for(int i=1;i<=6;i++){
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+" 号走了");
        },String.valueOf(i)).start();
    }
    System.out.println("班长锁门了");
}
/*
输出如下:
1 号走了
3 号走了
班长锁门了
2 号走了
4 号走了
5 号走了
6 号走了
*/
//使用countDownLatch
public static void main(String[] args) throws InterruptedException {
    CountDownLatch countDownLatch = new CountDownLatch(6);
    for(int i=1;i<=6;i++){
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+" 号走了");
            countDownLatch.countDown();
        },String.valueOf(i)).start();
    }
    countDownLatch.await();
    System.out.println("班长锁门了");
}
/*
3 号走了
4 号走了
1 号走了
2 号走了
5 号走了
6 号走了
班长锁门了
*/

8.2 循环栅栏CyclicBarrier

CyclicBarrier 看英文单词可以看出大概就是循环阻塞的意思,在使用中CyclicBarrier 的构造方法第一个参数是目标障碍数,每次执行 CyclicBarrier 一
次障碍数会加一,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后的语句。可以将 CyclicBarrier 理解为加 1 操作

//场景: 集齐 7 颗龙珠就可以召唤神龙
    private final static int NUMBER=7;
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER,()->{
            System.out.println("集齐龙珠,召唤神龙。");
        });
        for(int i=1;i<=7;i++){
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 号龙珠已经收集");
                try {
                    cyclicBarrier.await();
                System.out.println(Thread.currentThread().getName()+" 号龙珠已经消失");

                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }

/**
输出如下:
2 号龙珠已经收集
3 号龙珠已经收集
4 号龙珠已经收集
1 号龙珠已经收集
5 号龙珠已经收集
6 号龙珠已经收集
7 号龙珠已经收集
集齐龙珠,召唤神龙。
*/

8.3 信号灯Semaphore

Semaphore 的构造方法中传入的第一个参数是最大信号量(可以看成最大线程池),每个信号量初始化为一个最多只能分发一个许可证。使用 acquire 方
法获得许可证,release 方法释放许可。

//场景: 抢车位, 6 部汽车 3 个停车位
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for(int i=1;i<=6;i++){
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"停车");
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));
                    semaphore.release();
                    System.out.println(Thread.currentThread().getName()+"开走");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
/*
输出如下:
2停车
3停车
1停车
1开走
5停车
3开走
4停车
2开走
5开走
6停车
4开走
6开走
*/

9.ReentrantReadWriteLock读写锁

9.1 读写锁概述

在这里插入图片描述

悲观锁效率很低,不支持并发操作

乐观锁,支持并发并且加入version版本号进行控制

表锁:不会发生死锁

行锁:会发生死锁

读锁:共享锁,会产生死锁,因为读的时候可以修改

写锁:独占锁,会产生死锁,因为写的时候可以写多条

在这里插入图片描述

9.2 读写锁案例

//不加锁读写
class MyCache{
    private volatile Map<String,Object> map = new HashMap();

    public void put(String key,Object value){
        try{
            System.out.println(Thread.currentThread().getName()+"号线程正在放数");
            TimeUnit.MICROSECONDS.sleep(300);
           map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"号线程已经放完数");
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public Object get(String key){
        Object resultValue = null;
        try{
            System.out.println(Thread.currentThread().getName()+"号线程取数");
            TimeUnit.MICROSECONDS.sleep(300);
            resultValue = map.get(key);
            System.out.println(Thread.currentThread().getName()+"号线程已经取数="+resultValue);
        }catch (Exception e){
            e.printStackTrace();
        }
        return resultValue;
    }

}

public class ThreadDemo6 {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        for(int i=1;i<=3;i++){
            final int num = i;
            new Thread(()->{
                myCache.put(num+"",num+"");
            },String.valueOf(i)).start();
        }

        for(int i=1;i<=3;i++){
            final int num = i;
            new Thread(()->{
                myCache.get(num+"");
            },String.valueOf(i)).start();
        }



    }
}
/*
输出结果如下:
1号线程正在放数
3号线程正在放数
2号线程正在放数

1号线程取数
2号线程取数
3号线程取数

2号线程已经放完数
1号线程已经放完数
3号线程已经取数=null
1号线程已经取数=1
3号线程已经放完数
2号线程已经取数=null

Process finished with exit code 0

*/
//读写锁
class MyCache{
    private volatile Map<String,Object> map = new HashMap();

    //创建读写锁
    ReadWriteLock rwLock = new ReentrantReadWriteLock();
    public void put(String key,Object value){
        //加写锁
        rwLock.writeLock().lock();
        try{
            System.out.println(Thread.currentThread().getName()+"号线程正在放数");
            TimeUnit.MICROSECONDS.sleep(300);
           map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"号线程已经放完数");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //释放写锁
            rwLock.writeLock().unlock();
        }
    }

    public Object get(String key){
        Object resultValue = null;
        rwLock.readLock().lock();
        try{
            System.out.println(Thread.currentThread().getName()+"号线程取数");
            TimeUnit.MICROSECONDS.sleep(300);
            resultValue = map.get(key);
            System.out.println(Thread.currentThread().getName()+"号线程已经取数="+resultValue);
        }catch (Exception e){
            e.printStackTrace();
        }finally{
            rwLock.readLock().unlock();
        }
        return resultValue;
    }
}

public class ThreadDemo6 {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        for(int i=1;i<=3;i++){
            final int num = i;
            new Thread(()->{
                myCache.put(num+"",num+"");
            },String.valueOf(i)).start();
        }

        for(int i=1;i<=3;i++){
            final int num = i;
            new Thread(()->{
                myCache.get(num+"");
            },String.valueOf(i)).start();
        }
    }
}
/*
输出管理:
2号线程正在放数
2号线程已经放完数
3号线程正在放数
3号线程已经放完数
1号线程正在放数
1号线程已经放完数
1号线程取数
2号线程取数
3号线程取数
1号线程已经取数=1
2号线程已经取数=2
3号线程已经取数=3
*/

9.3 AQS概述和应用

9.4 读写锁深入

1.读写锁的演变

在这里插入图片描述

2.读写锁的降级:

将写入锁降级为读锁

获取写锁—>获取读锁—>释放写锁—>释放读锁

读锁 不能升级为 写锁

        ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
        ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
        //获取写锁
        writeLock.lock();
        System.out.println("写锁已获取");
        //获取读锁
        readLock.lock();
        System.out.println("读锁已获取");
        //释放写锁
        writeLock.unlock();
        //释放读锁
        readLock.lock();
/*
输出结果:
写锁已获取
读锁已获取
*/
//读锁无法升级为写锁,(读的时候不能写,写的时候可以读???)
		//获取读锁
        readLock.lock();
        System.out.println("读锁已获取");
        //获取写锁
        writeLock.lock();
        System.out.println("写锁已获取");
/*
读锁已获取
*/

10.BlockingQueue阻塞队列

1.阻塞队列概述

Concurrent 包中,BlockingQueue 很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建
高质量的多线程程序带来极大的便利。本文详细介绍了 BlockingQueue 家庭中的所有成员,包括他们各自的功能以及常见使用场景。阻塞队列,顾名思义,首先它是一个队列, 通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出;

在这里插入图片描述

当队列是空的,从队列中获取元素的操作将会被阻塞
当队列是满的,从队列中添加元素的操作将会被阻塞
试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素
试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增

在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起为什么需要 BlockingQueue
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue 都给你一手包办了在 concurrent 包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者”和“消费者”模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他们之间的数据共享问题。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然。

• 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列
• 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒

2.阻塞队列的架构

3.阻塞队列分类方法

1.ArrayBlockingQueue 由数组结构组成的有界阻塞队列

2.LinkedBlockingQueue 由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列

3.DelayQueue 使用优先级队列实现的延迟无界阻塞队列

4.PriorityBlockingQueue 支持优先级排序的无界阻塞队列

5.SynchronousQueue 不存储元素的阻塞队列,也即单个元素的队列

6.LinkedTransferQueue 由链表组成的无界阻塞队列

7.LinkedBlockingDeque 由链表组成的双向阻塞队列

4.阻塞队列核心方法

在这里插入图片描述

       //核心方法演示
		BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        //第一组 add remove
        System.out.println(queue.add("a")); //true
        System.out.println(queue.add("b")); //true
        System.out.println(queue.add("c")); //true
//        System.out.println(queue.add("c"));  java.lang.IllegalStateException: Queue full

        System.out.println(queue.remove()); //a
        System.out.println(queue.remove()); //b
        System.out.println(queue.remove()); //c
//        System.out.println(queue.remove()); java.util.NoSuchElementException
        //第二组 offer poll
        System.out.println(queue.offer("a"));   //true
        System.out.println(queue.offer("b"));   //true
        System.out.println(queue.offer("c"));   //true
        System.out.println(queue.offer("d"));   //false

        System.out.println(queue.poll());   //a
        System.out.println(queue.poll());   //b
        System.out.println(queue.poll());   //c
        System.out.println(queue.poll());   //null

        //第三组 put take
        queue.put("a");
        queue.put("b");
        queue.put("c");
        //queue.put("d"); 线程阻塞

        System.out.println(queue.take());
        System.out.println(queue.take());
        System.out.println(queue.take());
//        System.out.println(queue.take());     //线程阻塞

        System.out.println("=================");
        //第四组 offer poll
        queue.offer("a");
        queue.offer("b");
        queue.offer("c");
//        queue.offer("d",3L,TimeUnit.SECONDS); //线程阻塞3秒后退出

        System.out.println(queue.poll());   //a
        System.out.println(queue.poll());   //b
        System.out.println(queue.poll());   //c
//        System.out.println(queue.poll(3L,TimeUnit.SECONDS));     //null 线程阻塞3秒后退出

11. ThreadPool线程池

11.1 线程池概述

线程池(Thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理
者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
例子: 10 年前单核 CPU 电脑,假的多线程,像马戏团小丑玩多个球,CPU 需要来回切换。 现在是多核电脑,多个线程各自跑在独立的 CPU 上,不用切换
效率高。

线程池的优势: 线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,
超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。

它的主要特点为:

  • 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
  • 提高响应速度: 当任务到达时,任务可以不需要等待线程创建就能立即执行。
  • 提高线程的可管理性: 线程是稀缺资源,如果无限制的创建,不仅会销耗系 统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

11.2 线程池架构

Java 中的线程池是通过 Executor 框架实现的,该框架中用到了

Executor

Executors

ExecutorService

ThreadPoolExecutor

这几个类

在这里插入图片描述

11.3 线程池使用方式

1.一次N线程:Executors.newFixedThreadPool(int)

**作用:**创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。

特征:

  • 线程池中的线程处于一定的量,可以很好的控制线程的并发量
  • 线程可以重复被使用,在显示关闭之前,都将一直存在
  • 超出一定量的线程被提交时候需在队列中等待

创建方式:

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

例:银行窗口对外提供服务,窗口数不变。

2.一个任务一个任务执行,一池一线程:Executors.newSingleThreadExcutor()

**作用:**创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的newFixedThreadPool 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。

特征:

创建方式:

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

3.线程池根据需求创建线程,可扩容,遇强则强:Executors.newCachedThreadPool()

**作用:**创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.
创建方式:

  • 线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
  • 线程池中的线程可进行缓存重复利用和回收(回收默认时间为 1 分钟)
  • 当线程池中,没有可用线程,会重新创建一个线程

创建方式:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

三种线程池案例:

//        ExecutorService threadPool1 = Executors.newFixedThreadPool(3);  一次N线程
//        ExecutorService threadPool2 = Executors.newSingleThreadExecutor(); //一个任务一个任务执行
        ExecutorService threadPool3 = Executors.newCachedThreadPool();	//线程池根据需求创建线程
        try{
            for(int i=1;i<=10;i++){
                threadPool3.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" 办理");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally{
//            threadPool1.shutdown();
//            threadPool2.shutdown();
            threadPool3.shutdown();
        }

11.4 线程池底层原理

//无论哪一种线程池,都是new ThreadPoolExecutor(七个参数); 
return new ThreadPoolExecutor(null,null,null,null,null,null,null);

11.5 线程池的七个参数

int corePoolSize			  			核心/常驻线程数量

int maximumPoolsize			 			最大线程数量

long keepAliveTime		      			线程存活时间

TimeUnit unit				  			线程存活时间单位

BlockingQueue<Runnable> workQueue	    阻塞队列
    
ThreadFactory threadFactory				线程工厂

RejectedExecutionHandler handler 		拒绝策略

11.6 线程池底层工作流程

在这里插入图片描述

工作流程:

  1. 在创建了线程池后,线程池中的线程数为零

  2. 当调用 execute()方法添加一个请求任务时,线程池会做出如下判断:

2.1 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
2.2 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;

2.3 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;

2.4 如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。

  1. 当一个线程完成任务时,它会从队列中取下一个任务来执行

  2. 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
    4.1 如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。

4.2 所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

拒绝策略:

在这里插入图片描述

注意事项:

  1. 项目中创建多线程时,使用常见的三种线程池创建方式,单一、可变、定长都有一定问题,原因是 FixedThreadPool 和 SingleThreadExecutor 底层都是用
    LinkedBlockingQueue 实现的,这个队列最大长度为 Integer.MAX_VALUE,容易导致 OOM。所以实际生产一般自己通过 ThreadPoolExecutor 的 7 个参
    数自定义线程池。
  2. 创建线程池推荐适用 ThreadPoolExecutor 及其 7 个参数手动创建
  • corePoolSize 线程池的核心线程数
  • maximumPoolSize 能容纳的最大线程数
  • keepAliveTime 空闲线程存活时间
  • unit 存活的时间单位
  • workQueue 存放提交但未执行任务的队列
  • threadFactory 创建线程的工厂类
  • handler 等待队列满后的拒绝策略
  1. 为什么不允许适用不允许 Executors.的方式手动创建线程池,如下图

在这里插入图片描述

11.7 自定义线程池

        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                2L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(2),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        try{
            for(int i=1;i<=8;i++){
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" 办理");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally{
            threadPool.shutdown();
        }

12.Fork/Join分支合并框架

12.1Fork/Join 框架简介

Fork/Join 它可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出。Fork/Join 框架要完成两件事情:

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JknqgoLC-1631514386783)(img/4.juc_img/分支合并示意图.png)]

  1. 任务分割:首先 Fork/Join 框架需要把大的任务分割成足够小的子任务,如果子任务比较大的话还要对子任务进行继续分割
  2. 执行任务并合并结果:分割的子任务分别放到双端队列里,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都放在另外一个队列里,启动一个线程从队列里取数据,然后合并这些数据。

在 Java 的 Fork/Join 框架中,使用下面类完成上述操作

  • **ForkJoinTask: **我们要使用 Fork/Join 框架,首先需要创建一个 ForkJoin 任务。该类提供了在任务中执行 fork 和 join 的机制。通常情况下我们不需要直接集
    成 ForkJoinTask 类,只需要继承它的子类,Fork/Join 框架提供了两个子类:
    • a. RecursiveAction:用于没有返回结果的任务
    • b.RecursiveTask: 用于有返回结果的任务
  • ForkJoinPool: ForkJoinTask 需要通过 ForkJoinPool 来执行
  • RecursiveTask: 继承后可以实现递归(自己调自己)调用的任务
    Fork/Join 框架的实现原理
    ForkJoinPool 由 ForkJoinTask 数组和 ForkJoinWorkerThread 数组组成
    ForkJoinTask 数组负责将存放以及将程序提交给 ForkJoinPool
    ForkJoinWorkerThread 负责执行这些任务。

12.2 Fork/Join 架构

在这里插入图片描述

在这里插入图片描述

12.3 fork/join实现原理

1.fork方法实现原理:

当我们调用 ForkJoinTask 的 fork 方法时,程序会把任务放在 ForkJoinWorkerThread 的 pushTask 的 workQueue 中,异步地执行这个任务,然后立即返回结果。

    public final ForkJoinTask<V> fork() {
        Thread t;
        if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
            ((ForkJoinWorkerThread)t).workQueue.push(this);
        else
            ForkJoinPool.common.externalPush(this);
        return this;
    }

pushTask 方法把当前任务存放在 ForkJoinTask 数组队列里。然后再调用ForkJoinPool 的 signalWork()方法唤醒或创建一个工作线程来执行任务。代码如下:

        final void push(ForkJoinTask<?> task) {
            ForkJoinTask<?>[] a; ForkJoinPool p;
            int b = base, s = top, n;
            if ((a = array) != null) {    // ignore if queue removed
                int m = a.length - 1;     // fenced write for task visibility
                U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
                U.putOrderedInt(this, QTOP, s + 1);
                if ((n = s - b) <= 1) {
                    if ((p = pool) != null)
                        p.signalWork(p.workQueues, this);
                }
                else if (n >= m)
                    growArray();
            }
        }

2.join方法实现原理

Join 方法的主要作用是阻塞当前线程并等待获取结果。让我们一起看看ForkJoinTask 的 join 方法的实现,代码如下:

    public final V join() {
        int s;
        if ((s = doJoin() & DONE_MASK) != NORMAL)
            reportException(s);
        return getRawResult();
    }

它首先调用 doJoin 方法,通过 doJoin()方法得到当前任务的状态来判断返回什么结果,任务状态有 4 种:
已完成(NORMAL)

被取消(CANCELLED)

信号(SIGNAL)

异常(EXCEPTIONAL)

  • 如果任务状态是已完成,则直接返回任务结果。
  • 如果任务状态是被取消,则直接抛出 CancellationException
  • 如果任务状态是抛出异常,则直接抛出对应的异常

让我们分析一下dojoin方法:

    private int doJoin() {
        int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
        return (s = status) < 0 ? s :
            ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
            (w = (wt = (ForkJoinWorkerThread)t).workQueue).
            tryUnpush(this) && (s = doExec()) < 0 ? s :
            wt.pool.awaitJoin(w, this, 0L) :
            externalAwaitDone();
    }


    final int doExec() {
        int s; boolean completed;
        if ((s = status) >= 0) {
            try {
                completed = exec();
            } catch (Throwable rex) {
                return setExceptionalCompletion(rex);
            }
            if (completed)
                s = setCompletion(NORMAL);
        }
        return s;
    }

在 doJoin()方法流程如下:

  • 1.首先通过查看任务的状态,看任务是否已经执行完成,如果执行完成,则直接返回任务状态;

  • 2.如果没有执行完,则从任务数组里取出任务并执行。

  • 3.如果任务顺利执行完成,则设置任务状态为 NORMAL,如果出现异常,则记录异常,并将任务状态设置为 EXCEPTIONAL。

3.Fork/Join 框架的异常处理

ForkJoinTask 在执行的时候可能会抛出异常,但是我们没办法在主线程里直接捕获异常,所以 ForkJoinTask 提供了 isCompletedAbnormally()方法来检查
任务是否已经抛出异常或已经被取消了,并且可以通过 ForkJoinTask 的getException 方法获取异常。getException 方法返回 Throwable 对象,如果任务被取消了则返回CancellationException。如果任务没有完成或者没有抛出异常则返回 null。

12.4入门案例

场景: 生成一个计算任务,计算 1+2+3…+100,每 10 个数切分一个子任务


/**
 * 分支合并案例
 */
class MyTask extends RecursiveTask<Integer> {
    //拆分差值不能超过10
    private static final Integer VALUE=10;
    private  int begin;//拆分开始值
    private  int end;//拆分结束值
    private  int result;//返回结果

    public MyTask(int begin,int end){
        this.begin = begin;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        //判断两个数的差值是否大于10
        if(end-begin<=VALUE){
            //累加
            for(int i=begin;i<=end;i++){
                result+=i;
            }
        }else{
            //进一步拆分
            int middle = (begin+end)/2;
            //拆分左边
            MyTask leftTask = new MyTask(begin, middle);
            //拆分右边
            MyTask rightTask = new MyTask(middle+1, end);
            //调用拆分方法
            leftTask.fork();
            rightTask.fork();
            //合并结果
           result = leftTask.join() + rightTask.join();
        }
        return result;
    }
}

public class TaskExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1.创建MyTask对象
        MyTask myTask = new MyTask(1, 100);
        //2.创建分支合并池对象
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Integer> submit = forkJoinPool.submit(myTask);
        //3.获取最终得到的结果
        Integer result = submit.get();
        System.out.println(result);
    }
}

13. CompletableFuture异步回调

13.1CompletableFuture 简介

CompletableFuture 在 Java 里面被用于异步编程,异步通常意味着非阻塞,可以使得我们的任务单独运行在与主线程分离的其他线程中,并且通过回调可以在主线程中得到异步任务的执行状态,是否完成,和是否异常等信息。

CompletableFuture 实现了 Future, CompletionStage 接口,实现了 Future接口就可以兼容现在有线程池框架,而 CompletionStage 接口才是异步编程的接口抽象,里面定义多种异步方法,通过这两者集合,从而打造出了强大的CompletableFuture 类。

13.2 Future 与 CompletableFuture

Futrue 在 Java 里面,通常用来表示一个异步任务的引用,比如我们将任务提交到线程池里面,然后我们会得到一个 Futrue,在 Future 里面有 isDone 方法来 判断任务是否处理结束,还有 get 方法可以一直阻塞直到任务结束然后获取结果,但整体来说这种方式,还是同步的,因为需要客户端不断阻塞等待或者不断轮询才能知道任务是否完成。

Future 的主要缺点如下:
(1)不支持手动完成
我提交了一个任务,但是执行太慢了,我通过其他路径已经获取到了任务结果,现在没法把这个任务结果通知到正在执行的线程,所以必须主动取消或者一直等待它执行完成
(2)不支持进一步的非阻塞调用通过 Future 的 get 方法会一直阻塞到任务完成,但是想在获取任务之后执行额外的任务,因为 Future 不支持回调函数,所以无法实现这个功能
(3)不支持链式调用对于 Future 的执行结果,我们想继续传到下一个 Future 处理使用,从而形成一个链式的 pipline 调用,这在 Future 中是没法实现的。
(4)不支持多个 Future 合并比如我们有 10 个 Future 并行执行,我们想在所有的 Future 运行完毕之后,执行某些函数,是没法通过 Future 实现的。
(5)不支持异常处理Future 的 API 没有任何的异常处理的 api,所以在异步运行时,如果出了问题是不好定位的。

13.3 CompletableFuture 入门

1)使用 CompletableFuture

**场景:**主线程里面创建一个 CompletableFuture,然后主线程调用 get 方法会阻塞,最后我们在一个子线程中使其终止。

  /**
     * 主线程里面创建一个 CompletableFuture,然后主线程调用 get 方法会阻塞,最后我们
     在一个子线程中使其终止
     * @param args
     */
    public static void main(String[] args) throws Exception{
        CompletableFuture<String> future = new CompletableFuture<>();
        new Thread(() -> {
            try{
                System.out.println(Thread.currentThread().getName() + "子线程开始干活");
                //子线程睡 5 秒
                Thread.sleep(5000);
                //在子线程中完成主线程
                future.complete("success");
            }catch (Exception e){
                e.printStackTrace();
            }
        }, "A").start();
        //主线程调用 get 方法阻塞
        System.out.println("主线程调用 get 方法获取结果为: " + future.get());
        System.out.println("主线程完成,阻塞结束!!!!!!");
    }

2)没有返回值的异步任务

    /**
     * 没有返回值的异步任务
     * @param args
     */
    public static void main(String[] args) throws Exception{
        System.out.println("主线程开始");
        //运行一个没有返回值的异步任务
        CompletableFuture<Void> future = CompletableFuture.runAsync(()-> {
            try {
                System.out.println("子线程启动干活");
                Thread.sleep(5000);
                System.out.println("子线程完成");
            } catch (Exception e) {
                e.printStackTrace();
            }
         });
        //主线程阻塞
        future.get();
        System.out.println("主线程结束");
    }

3)有返回值的异步任务

   /**
     * 没有返回值的异步任务
     * @param args
     */
    public static void main(String[] args) throws Exception{
        System.out.println("主线程开始");
        //运行一个有返回值的异步任务
        CompletableFuture<String> future =
                CompletableFuture.supplyAsync(() -> {
                    try {
                        System.out.println("子线程开始任务");
                        Thread.sleep(5000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return "子线程完成了!";
                });
        //主线程阻塞
        String s = future.get();
        System.out.println("主线程结束, 子线程的结果为:" + s);
    }

4)线程依赖

当一个线程依赖另一个线程时,可以使用 thenApply 方法来把这两个线程串行化。

   private static Integer num = 10;
    /**
     * 先对一个数加 10,然后取平方
     * @param args
     */
    public static void main(String[] args) throws Exception{
        System.out.println("主线程开始");
        CompletableFuture<Integer> future =
                CompletableFuture.supplyAsync(() -> {
                    try {
                        System.out.println("加 10 任务开始");
                        num += 10;
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return num;
                }).thenApply(integer -> {
                    return num * num;
                });
        Integer integer = future.get();
        System.out.println("主线程结束, 子线程的结果为:" + integer);
    }

5)消费处理结果

thenAccept 消费处理结果, 接收任务的处理结果,并消费处理,无返回结果。

 public static void main(String[] args) throws Exception{
        System.out.println("主线程开始");
        CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println("加 10 任务开始");
                num += 10;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return num;
        }).thenApply(integer -> {
            return num * num;
        }).thenAccept(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println("子线程全部处理完成,最后调用了 accept,结果为:" +
                        integer);
            }
        });
    }

6)异常处理

exceptionally 异常处理,出现异常时触发

public static void main(String[] args) throws Exception{
    System.out.println("主线程开始");
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        int i= 1/0;
        System.out.println("加 10 任务开始");
        num += 10;
        return num;
    }).exceptionally(ex -> {
        System.out.println(ex.getMessage());
        return -1;
    });
    System.out.println(future.get());
}

handle 类似于 thenAccept/thenRun 方法,是最后一步的处理调用,但是同时可以处理异常

    public static void main(String[] args) throws Exception{
        System.out.println("主线程开始");
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("加 10 任务开始");
            num += 10;
            return num;
        }).handle((i,ex) ->{
            System.out.println("进入 handle 方法");
            if(ex != null){
                System.out.println("发生了异常,内容为:" + ex.getMessage());
                return -1;
            }else{
                System.out.println("正常完成,内容为: " + i);
                return i;
            }});
        System.out.println(future.get());
    }

7)结果合并

thenCompose 合并两个有依赖关系的 CompletableFutures 的执行结果

public static void main(String[] args) throws Exception{
    System.out.println("主线程开始");
    //第一步加 10
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
        System.out.println("加 10 任务开始");
        num += 10;
        return num;
    });
    //合并
    CompletableFuture<Integer> future1 = future.thenCompose(i->
    //再来一个 CompletableFuture
            CompletableFuture.supplyAsync(() -> {
                return i + 1;
            }));
    System.out.println(future.get());
    System.out.println(future1.get());
}

thenCombine 合并两个没有依赖关系的 CompletableFutures 任务

public static void main(String[] args) throws Exception{
    System.out.println("主线程开始");
    CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() -> {
        System.out.println("加 10 任务开始");
        num += 10;
        return num;
    });
    CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() -> {System.out.println("乘以 10 任务开始");
        num = num * 10;
        return num;
    });
    //合并两个结果
    CompletableFuture<Object> future = job1.thenCombine(job2, new
            BiFunction<Integer, Integer, List<Integer>>() {
                @Override
                public List<Integer> apply(Integer a, Integer b) {
                    List<Integer> list = new ArrayList<>();
                    list.add(a);
                    list.add(b);
                    return list;
                }
            });
    System.out.println("合并结果为:" + future.get());
}

合并多个任务的结果 allOf 与 anyOf
allOf: 一系列独立的 future 任务,等其所有的任务执行完后做一些事情

/**
 * 先对一个数加 10,然后取平方
 * @param args
 */
public static void main(String[] args) throws Exception{
    System.out.println("主线程开始");
    List<CompletableFuture> list = new ArrayList<>();
    CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() -> {
        System.out.println("加 10 任务开始");
        num += 10;
        return num;
    });
    list.add(job1);
    CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() -> {
        System.out.println("乘以 10 任务开始");num = num * 10;
        return num;
    });
    list.add(job2);
    CompletableFuture<Integer> job3 = CompletableFuture.supplyAsync(() -> {
        System.out.println("减以 10 任务开始");
        num = num * 10;
        return num;
    });
    list.add(job3);
    CompletableFuture<Integer> job4 = CompletableFuture.supplyAsync(() -> {
        System.out.println("除以 10 任务开始");
        num = num * 10;
        return num;
    });
    list.add(job4);
    //多任务合并
    List<Integer> collect =
            list.stream().map(CompletableFuture<Integer>::join).collect(Collectors.toList());
    System.out.println(collect);
}

anyOf: 只要在多个 future 里面有一个返回,整个任务就可以结束,而不需要等到每一个future 结束

/**
 * 先对一个数加 10,然后取平方
 * @param args
 */
public static void main(String[] args) throws Exception{
    System.out.println("主线程开始");
    CompletableFuture<Integer>[] futures = new CompletableFuture[4];
    CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() -> {
        try{
            Thread.sleep(5000);
            System.out.println("加 10 任务开始");num += 10;
            return num;
        }catch (Exception e){
            return 0;
        }
    });
    futures[0] = job1;
    CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() -> {
        try{
            Thread.sleep(2000);
            System.out.println("乘以 10 任务开始");
            num = num * 10;
            return num;
        }catch (Exception e){
            return 1;
        }
    });
    futures[1] = job2;
    CompletableFuture<Integer> job3 = CompletableFuture.supplyAsync(() -> {
        try{
            Thread.sleep(3000);
            System.out.println("减以 10 任务开始");
            num = num * 10;
            return num;
        }catch (Exception e){
            return 2;
        }
    });
    futures[2] = job3;
    CompletableFuture<Integer> job4 = CompletableFuture.supplyAsync(() -> {
        try{
            Thread.sleep(4000);
            System.out.println("除以 10 任务开始");num = num * 10;
            return num;
        }catch (Exception e){
            return 3;
        }
    });
    futures[3] = job4;
    CompletableFuture<Object> future = CompletableFuture.anyOf(futures);
    System.out.println(future.get());
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值