JUC相关

JUC

简介

JUC是指java.util.concurrent 工具包的简写,这是一个处理线程的工具包,JDK1.5 开始出现的

进程

是在系统中正在运行的一个应用程序,程序一旦运行就是进程,进程是资源分配的最小单位

线程

系统分配处理器时间资源的最小单位,或者说进程之内独立执行的一个单元执行流,是程序执行的最小单位

用户线程

自定义线程

主线程结束了,用户线程还在运行,Jvm存活

守护线程

比如垃圾回收

没有用户线程了,都是守护线程,jvm结束

创建线程的方式

1、继承Thread类

2、实现Runaable接口

3、使用Callable接口

4、使用线程池

wait()

wait()使得当前线程阻塞,前提是必须先获得锁,一般配合synchronized关键字使用,即,一般在synchronized 同步代码块中使用

当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态

wait()需要被try catch包围,以便发生异常中断也可以使wait等待的线程唤醒

wait 和 sleep的区别

1、sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用

2、sleep不会释放锁,它也不需要占用锁,wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized中)

3、它们都可以被interripted 方法中断

wait对于某一个参数的版本,实现中断和虚假唤醒是有可能的,而且次方法应始终在循环中使用。

notify()

notify方法是唤醒一个等待对象的线程并使得该线程开始运行,所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。

notifyAll()会唤醒所有等待的线程

并行和并发的区别

并发:同一时刻多个线程在访问同一个资源,多个线程多一个点

并行:多项工作一起执行,之后再汇总

管程

是一种同步机制,保证同一个时间,只有一个线程访问被保护数据或者代码

JVM同步基于进入和退出,使用管程对象实现的

Synchronized关键字

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

1、修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象

2、修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象

虽然可以使用synchronized 来定义方法,但 synchronized 并不属于方法定义的一部分,因此,synchronized 关键字不能被继承,如果在父类中的某个方法使用了 synchronized 关键字,而在子类中覆盖了这个方法,在子类中这个方法默认情况并不是同步的,而必须显式地在子类的这个方法中加上 Synchronized 关键字才可以

多线程编程步骤

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

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

1、判断

2、干活

3、通知

第三步:创建多个线程,调用资源类的操作方法

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

Lock接口

Lock接口介绍

为锁和等待条件提高一个框架的接口和类,它不同于内置同步和监视器

Lock实现提高了比使用synchronized 方法和语句获得的更广泛的锁定操作

Lock与 synchronized 的区别

  • Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问
  • Lock和synchronized有一点非常大的不同:采用synchronized不需要用户去手动释放锁,当Synchronized 方法或者 synchronized 代码块执行完之后,系统会自动让线程释放对锁的占用,而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就可能导致出现死锁现象。
  • lock可以等待锁的线程响应中断,而Synchronized不行,使用Synchronized时,等待的线程会一直等待下去,不能响应中断
  • 通过Lock可以知道有没有成功获取锁,而Synchronized却无法办到
  • Lock可以提高多个线程进行读操作的效率

在性能上来说,如果竞争资源不激烈,两者的性能差不多的,而当竞争资源非常激烈时,此时Lock的性能要远远优于synchronized

线程间通信

实现四个线程对number +1 之后 -1的操作

class Share{
    private int number = 0;

    //创建lock
    private Lock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    public void incr() throws InterruptedException{
        //上锁
        lock.lock();
        try{
            //判断
            while(number != 0){
                condition.await();
            }
            //操作
            number++;
            System.out.println(Thread.currentThread().getName() + " :: " + number);

            //通知
            condition.signalAll();
        }finally {
            //解锁
            lock.unlock();
        }
    }

    public void decr() throws InterruptedException{
        //上锁
        lock.lock();
        try{
            //判断
            while(number != 1){
                condition.await();
            }
            //操作
            number--;
            System.out.println(Thread.currentThread().getName() + " :: " + number);

            //通知
            condition.signalAll();
        }finally {
            //解锁
            lock.unlock();
        }
    }
}


public class LSaleTicket {

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

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

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

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

线程定制化通信

AA输出5次,BB输出10次,CC输出15次,进行十轮并且按顺序

//第一步 创建资源类
class ShareResource{
    //定义标志位
    private int flag = 1; //1 AA 2 BB 3 CC
    //创建Lock锁
    private Lock lock = new ReentrantLock();

    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    //打印五次
    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);

            }
            //通知
            flag = 2; //修改标注位
            c2.signal();
        }finally {
            lock.unlock();
        }
    }

    //打印十次
    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);

            }
            //通知
            flag = 3; //修改标注位
            c3.signal();
        }finally {
            lock.unlock();
        }
    }

    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);

            }
            //通知
            flag = 1; //修改标注位
            c1.signal();
        }finally {
            lock.unlock();
        }
    }
}
public class ThreadDemo {

    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();
    }
}

集合的线程安全

ArrayList

异常演示
public class ThreadDemo2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        for(int i = 0;i < 30;i++){
            new Thread(() ->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

出现java.util.ConcurrentModificationException错误

解决方案

1、Vector

List<String> list = new Vector<>();

2、Collections

List<String> list = new Collections.synchronizedList(new ArrayList<>());

3、CopyOnWriteArrayList

并发读,独立写,写时复制技术

HashSet和Hashmap线程不安全

解决方案

HashSet的解决方案是使用CopyOnWriteArraySet

HashMap的解决方案是使用ConcurrentHashMap

多线程锁

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

1、对于普通同步方式,锁是当前实例对象

2、对于静态同步方法,锁是当前类的Class对象

3、对于同步方法块,锁是Synchronized括号里配置的对象

公平锁

效率相对低

非公平锁

线程饿死,效率高

可重入锁

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

死锁

什么是死锁

两个或两个以上进程在执行过程中,因为争夺资源而造成一种互相等待的现象,如果没有外力干涉,他们无法再执行下去

死锁产生的原因

1、系统资源不足

2、进程运行推进顺序不合理

3、资源分配不当

死锁案例
public class ThreadDemo3 {

    static Object a = new Object();
    static Object b = new Object();
    public static void main(String[] args) throws InterruptedException {
        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");
                }
            }
        }, "A").start();

        new Thread(()->{
            synchronized (b){
                System.out.println(Thread.currentThread().getName() + "持有锁B,试图获取锁A");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (a){
                    System.out.println(Thread.currentThread().getName() + "获取锁A");
                }
            }
        }, "A").start();
    }
}
验证是否是死锁

1、jps 类似linux ps -ef

2、jstack jvm自带堆栈跟踪工具

Callable接口

Runnable创建线程缺少的功能是:当线程终止时,我们无法使线程返回结果,为了支持此功能,Java中提供了Callable接口

Runnable接口和Callable接口不同点:

1、是否有返回值

2、是否测出异常

3、实现方法名称不同,一个是run方法,一个是call方法

特点

  • 为了实现Runnable,需要实现不返回任何内容的run()方法,而对于Callable,需要实现在完成时返回结果的call()方法
  • call()方法可以引发异常,而run()而不能
  • 为实现Callable而必须重写call方法
  • 不能直接替换runnable,因为Thread类的构造方法根本没有Callable

发现Runnable接口有实现类FutureTask(中间对象)

FutureTask的构造函数有Callable参数,通过FutureTask创建对象

FutureTask

FutureTask(Callable<> callable) 创建一个FutureTask,一旦运行就执行给定的Callable

FutureTask(Runnable runnable,V result)创建一个FutureTask,一旦运行就执行给定的Ru你那边了,并安排成功完成时get返回给定的结果

所谓的FutureTask是在不影响主任务的同时,开启单线程完成某个特别的任务,之后主线程续上单线程的结果即可(该单线程汇总给主线程只需要一次即可)
如果之后主线程在开启该单线程,可以直接获得结果,因为之前已经执行过一次了

//比较两个接口
//实现Runnable接口
class MyThread1 implements Runnable {
    @Override
    public void run() {

    }
}

//实现Callable接口
class MyThread2 implements Callable {

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+" come in callable");
        return 200;
    }
}

public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //Runnable接口创建线程
        new Thread(new MyThread1(),"AA").start();

        //Callable接口,报错
       // new Thread(new MyThread2(),"BB").start();

        //FutureTask
        FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());

        //lam表达式
        FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
            System.out.println(Thread.currentThread().getName()+" come in callable");
            return 1024;
        });

        //创建一个线程
        new Thread(futureTask2,"lucy").start();
        new Thread(futureTask1,"mary").start();

//        while(!futureTask2.isDone()) {
//            System.out.println("wait.....");
//        }
        //调用FutureTask的get方法
        System.out.println(futureTask2.get());

        System.out.println(futureTask1.get());

        System.out.println(Thread.currentThread().getName()+" come over");
       }
}

JUC辅助类

减少计数 CountDownLatch

CountDownLatch类可以设置一个计数器,然后通过countDown方法来进行减1的操作,使用await()方法等待计数器不大于0,然后继续执行await方法之后的语句

  • CountDownLatch 主要有两个方法,当一个或多个线程调用await()方法时,这些线程会阻塞
  • 其他线程调用countDown 方法会将计数器减1,调用countDown方法的线程不会阻塞
  • 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行
具体案例

6个同学陆续离开教室之后,班长才能锁门

如果不加CountDownLatch类,会出现线程混乱执行,同学还未离开教室班长就锁门

public class CountDownLatchDemo {
    //6个同学陆续离开教室之后,班长锁门
    public static void main(String[] args) throws InterruptedException {
        
        //6个同学陆续离开教室之后
        for (int i = 1; i <=6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");

            },String.valueOf(i)).start();
        }

        System.out.println(Thread.currentThread().getName()+" 班长锁门走人了");
    }
}

正确案例

//演示 CountDownLatch
public class CountDownLatchDemo {
    //6个同学陆续离开教室之后,班长锁门
    public static void main(String[] args) throws InterruptedException {

        //创建CountDownLatch对象,设置初始值
        CountDownLatch countDownLatch = new CountDownLatch(6);

        //6个同学陆续离开教室之后
        for (int i = 1; i <=6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");

                //计数  -1
                countDownLatch.countDown();

            },String.valueOf(i)).start();
        }

        //等待
        countDownLatch.await();

        System.out.println(Thread.currentThread().getName()+" 班长锁门走人了");
    }
}

循环栅栏 CyclicBarrier

CyclicBarrier 的构造方法的第一个参数是目标障碍数,每次执行CyclicBarrier 一次障碍数会加1,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后的语句,可以将CycliBarrier理解为加1操作。

具体案例
//集齐7颗龙珠就可以召唤神龙
public class CyclicBarrierDemo {

    //创建固定值
    private static final int NUMBER = 7;

    public static void main(String[] args) {
        //创建CyclicBarrier
        CyclicBarrier cyclicBarrier =
                new CyclicBarrier(NUMBER,()->{
                    System.out.println("*****集齐7颗龙珠就可以召唤神龙");
                });

        //集齐七颗龙珠过程
        for (int i = 1; i <=7; i++) {
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+" 星龙被收集到了");
                    //等待
                    cyclicBarrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

Semaphore 信号量

一个计数信号量,从概念上将,信号量维护了一个许可集,如有必要,在许可可用前会阻塞每一个acquire(),然后在获取该许可。每个release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采取相应的行动

具体常用的构造方法有:
Semaphore(int permits)创建具有给定的许可数和非公平的公平设置的Semapore

具体常用的方法有:
acquire()从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断
release()释放一个许可,将其返回给信号量

设置许可数量Semaphore semaphore = new Semaphore(3);
一般acquire()都会抛出异常,release在finally中执行

具体案例

6辆汽车,停3个车位

//6辆汽车,停3个车位
public class SemaphoreDemo {
    public static void main(String[] args) {
        //创建Semaphore,设置许可数量
        Semaphore semaphore = new Semaphore(3);

        //模拟6辆汽车
        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));

                    System.out.println(Thread.currentThread().getName()+" ------离开了车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //释放
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

读写锁

悲观锁

单独每个人完成事情的时候,执行上锁解锁。解决并发中的问题,不支持并发操作,只能一个一个操作,效率低

乐观锁

单独每个人完成事情的时候,执行上锁解锁。解决并发中的问题,不支持并发操作,只能一个一个操作,效率低

表锁

整个表操作,不会发生死锁

行锁

每个表中的单独一行进行加锁,会发生死锁

读锁

共享锁(可以有多个人读),会发生死锁

写锁

独占锁(只能有一个人写),会发生死锁

读写锁

一个资源可以被多个读线程访问,也可以被一个写线程访问,但不能同时存在读写线程,读写互斥,读读共享

具体案例
//资源类
class MyCache {
    //创建map集合
    private volatile Map<String,Object> map = new HashMap<>();

    //创建读写锁对象
    private ReadWriteLock rwLock = new ReentrantReadWriteLock();

    //放数据
    public void put(String key,Object value) {
        //添加写锁
        rwLock.writeLock().lock();

        try {
            System.out.println(Thread.currentThread().getName()+" 正在写操作"+key);
            //暂停一会
            TimeUnit.MICROSECONDS.sleep(300);
            //放数据
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+" 写完了"+key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //释放写锁
            rwLock.writeLock().unlock();
        }
    }

    //取数据
    public Object get(String key) {
        //添加读锁
        rwLock.readLock().lock();
        Object result = null;
        try {
            System.out.println(Thread.currentThread().getName()+" 正在读取操作"+key);
            //暂停一会
            TimeUnit.MICROSECONDS.sleep(300);
            result = map.get(key);
            System.out.println(Thread.currentThread().getName()+" 取完了"+key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //释放读锁
            rwLock.readLock().unlock();
        }
        return result;
    }
}

public class ReadWriteLockDemo {
    public static void main(String[] args) throws InterruptedException {
        MyCache myCache = new MyCache();
        //创建线程放数据
        for (int i = 1; i <=5; i++) {
            final int num = i;
            new Thread(()->{
                myCache.put(num+"",num+"");
            },String.valueOf(i)).start();
        }

        TimeUnit.MICROSECONDS.sleep(300);

        //创建线程取数据
        for (int i = 1; i <=5; i++) {
            final int num = i;
            new Thread(()->{
                myCache.get(num+"");
            },String.valueOf(i)).start();
        }
    }
}

锁的演变

  1. 无锁:多线程抢夺资源
  2. synchronized和ReentrantLock,都是独占,每次只可以一个操作,不能共享
  3. ReentrantReadWriteLock,读读可以共享,提升性能,但是不能多人写。缺点:造成死锁(一直读,不能写),读进程不能写,写进程可以读。
  4. 写锁降级为读锁(一般等级写锁高于读锁)
//演示读写锁降级
public class Demo1 {

    public static void main(String[] args) {
        //可重入读写锁对象
        ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();//读锁
        ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();//写锁

        //锁降级
        //1 获取写锁
        writeLock.lock();
        System.out.println("manongyanjiuseng");
        
        //2 获取读锁
        readLock.lock();
        System.out.println("---read");
        
        //3 释放写锁
        writeLock.unlock();

        //4 释放读锁
        readLock.unlock();
    }
}

如果是读之后再写,执行不了
因为读锁权限小于写锁
需要读完之后释放读锁,再进行写锁

阻塞队列

是一个队列,通过一个共享的队列,可以使得数据由队列的i一端输入,从另一端输出

种类

ArrayBlockingQueue

基于数组的阻塞队列实现(常用)

在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue 内部还保存着两个整型变量,分别标识着队列的头部和尾部在数组中的位置

ArrayBlockingQueue 在生产者放入数据和消费者获取数据,都是共用同一个锁对象,无法并行

LinkedBlockingQueue

基于链表的阻塞队列(常用)

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

之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能

DelayQueue

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

DelayQueue 中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue 是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞

PriorityBlockingQueue

基于优先级的阻塞队列
支持优先级排序的无界阻塞队列
不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者

SynchronousQueue

一种无缓冲的等待队列

相对于有缓冲的 BlockingQueue 来说,少了一个中间经销商的环节(缓冲区)
不存储元素的阻塞队列,也即单个元素的队列

声明一个 SynchronousQueue 有两种不同的方式,它们之间有着不太一样的行为。
公平模式和非公平模式的区别:

  • 公平模式:SynchronousQueue 会采用公平锁,并配合一个 FIFO 队列来阻塞
    多余的生产者和消费者,从而体系整体的公平策略;
  • 非公平模式(SynchronousQueue 默认):SynchronousQueue 采用非公平锁,同时配合一个 LIFO 队列来管理多余的生产者和消费者

而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理

LinkedTransferQueue

由链表结构组成的无界阻塞 TransferQueue 队列

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

预占模式。意思就是消费者线程取元素时,如果队列不为空,则直接取走数据,若队列为空,生成一个节点(节点元素为 null)入队,消费者线程被等待在这个节点上,生产者线程入队时发现有一个元素为 null 的节点,生产者线程就不入队了,直接就将元素填充到该节点,并唤醒该节点等待的线程,被唤醒的消费者线程取走元素,从调用的方法返回

LinkedBlockingDeque

由链表结构组成的双向阻塞队列

阻塞有两种情况

  • 插入元素时: 如果当前队列已满将会进入阻塞状态,一直等到队列有空的位置时再该元素插入,该操作可以通过设置超时参数,超时后返回 false 表示操作失败,也可以不设置超时参数一直阻塞,中断后抛出 InterruptedException异常
  • 读取元素时: 如果当前队列为空会阻塞住直到队列不为空然后返回元素,同样可以通过设置超时参数

线程池

线程池是创建和管理一个连接的缓冲池的计数,这些连接准备好被任何需要它们的线程使用。

线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度

特点:

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

具体架构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f5HtTe5w-1639810986414)(G:\photos\mkd截图\cc01ccd591ac421eb6d150728bb1584a.png)]

Executors为工具类,I为接口类,C为实现类

线程使用方式

Executors.newFixedThreadPool(int)

一池N线程

ExecutorService threadPool1 = Executors.newFixedThreadPool(5); //5个窗口
Executors.newSingleThreadExecutor()

一池一线程

 ExecutorService threadPool2 = Executors.newSingleThreadExecutor(); //一个窗口
Executors.newCachedThreadPool()

一池可扩容根据需求创建线程

 ExecutorService threadPool3 = Executors.newCachedThreadPool();

执行线程execute()
关闭线程shutdown()

线程池的七个参数

corePoolSize:常驻线程数量(核心)

maximumPoolSize:最大线程数量

keepAliveTimeunit:线程存活时间,单位

BlockingQueue:阻塞队列

threadFactory:线程工厂

RejectedExecutionHandler:拒绝策略

public ThreadPoolExecutor(int corePoolSize,
                         int maximumPoolSize,
                         long keepAliveTime,
                         TimeUnit unit,
                         BlockingQueue<Runnable> workQueue,
                         ThreadFactory threadFactory,
                         RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
      maximumPoolSize <= 0 ||
      maximumPoolSize < corePoolSize ||
     keepAliveTime < 0)
      throw new IllegalArgumentException();
      if (workQueue == null || threadFactory == null || handler == null)
         throw new NullPointerException();
       this.corePoolSize = corePoolSize;
       this.maximumPoolSize = maximumPoolSize;
       this.workQueue = workQueue;
       this.keepAliveTime = unit.toNanos(keepAliveTime);
       this.threadFactory = threadFactory;
       this.handler = handler;
}

拒绝策略

1、抛异常

2、谁调用找谁

3、抛弃最久执行当前

4、不理不问

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LPCBUG4y-1639810986415)(G:\photos\mkd截图\07a1429981584ef986c0852071d3bd78.png)]

自定义线程池

在实际开发中不允许使用Executors创建,而是通过ThreadPoolExecutor方式,规避资源耗尽风险。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BnVJjlro-1639810986417)(G:\photos\mkd截图\5f5fba97080a4ffb8d853a70872bff6a.png)]

分支合并框架

Fork和Join分支

将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果
该算法相当于递归,且是二分查找思路

异步回调

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

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

异步调用没有返回值方法runAsync
异步调用有返回值方法supplyAsync

Future 与 CompletableFuture

对比这两种方法,一个为同步一个为异步

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

(1)不支持手动完成

我提交了一个任务,但是执行太慢了,我通过其他路径已经获取到了任务结果,现在没法把这个任务结果通知到正在执行的线程,所以必须主动取消或者一直等待它执行完成

(2)不支持进一步的非阻塞调用

通过 Future 的 get 方法会一直阻塞到任务完成,但是想在获取任务之后执行额外的任务,因为 Future 不支持回调函数,所以无法实现这个功能

(3)不支持链式调用

对于 Future 的执行结果,我们想继续传到下一个 Future 处理使用,从而形成一个链式的 pipline 调用,这在 Future 中是没法实现的。

(4)不支持多个 Future 合并

比如我们有 10 个 Future 并行执行,我们想在所有的 Future 运行完毕之后,执行某些函数,是没法通过 Future 实现的。

(5)不支持异常处理

Future 的 API 没有任何的异常处理的 api,所以在异步运行时,如果出了问题是不好定位的
子任务进行并行处理,最后将子任务结果合并成最后的计算结果
该算法相当于递归,且是二分查找思路

异步回调

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

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

异步调用没有返回值方法runAsync
异步调用有返回值方法supplyAsync

Future 与 CompletableFuture

对比这两种方法,一个为同步一个为异步

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

(1)不支持手动完成

我提交了一个任务,但是执行太慢了,我通过其他路径已经获取到了任务结果,现在没法把这个任务结果通知到正在执行的线程,所以必须主动取消或者一直等待它执行完成

(2)不支持进一步的非阻塞调用

通过 Future 的 get 方法会一直阻塞到任务完成,但是想在获取任务之后执行额外的任务,因为 Future 不支持回调函数,所以无法实现这个功能

(3)不支持链式调用

对于 Future 的执行结果,我们想继续传到下一个 Future 处理使用,从而形成一个链式的 pipline 调用,这在 Future 中是没法实现的。

(4)不支持多个 Future 合并

比如我们有 10 个 Future 并行执行,我们想在所有的 Future 运行完毕之后,执行某些函数,是没法通过 Future 实现的。

(5)不支持异常处理

Future 的 API 没有任何的异常处理的 api,所以在异步运行时,如果出了问题是不好定位的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qtayu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值