多线程juc


demo在 springboot-demo/demo

服务器核

单核:某一个时间点上只能做一件事
双核: 可以做两件

并行与并发

并发是一个处理器在一个时间段同时处理多个任务。其实并不是同时进行,是cpu在切换执行。会抢占资源
	一台咖啡机排排两列队。
并行是多个处理器或者多核的处理器各自同时在执行自己的任务。不会抢占资源
	两台咖啡机各自排一列队

线程、进程、管程

进程:资源分配和调度的基本单位,java程序运行后就是一个进程,有一个pid
线程:进程中的实际运作单位,是操作系统运算调度的最小单位。一般一个进程有多个线程在运行
管程:monitor监视器,也就是锁,每个对象都有

线程状态图

在这里插入图片描述

创建线程

  • 继承Thread类
  • 实现Runnable接口

都是重写run方法,start 方法用于启动线程,调用的是native start0()。

  • new Thread传入Runnable匿名内部类。

线程执行流程:
取决于操作系统的CPU,run方法是由操作系统底层调用。由CPU给线程分配时间片,分到了再执行。分配的时间片能把线程执行完就执行完,不能执行完就执行多少算多少,时间片用完就会让出执行权重新竞争时间片。
时间片用完了,保存执行状态以及恢复叫做上下问切换。

线程池

更多的我们不是创建线程,而是线程池。ThreadTest类

线程池(ThreadPoolExecutor)

  • 池的作用
    • 限定线程个数,防止线程过多
    • 节省开销
  • 创建过程
    • 创建出来时,线程池的线程数是0。
    • 执行请求(submit()),如果正在运行的线程数量小于核心则创建新线程执行。即使核心有空闲的也是
    • 大于等于核心,任务放入队列
    • 队列满了,但是还没达到最大,继续创建新线程。达到了就执行拒绝策略
    • 线程空闲超过存活时间,大于核心的线程数会停掉。核心的默认一直存活。设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭。
  • 参数
    • corePoolSize:核心线程数
    • maxPoolSize:最大线程数
    • Keep-alive times:线程存活时间
    • unit:时间单位
    • workQueue:队列
    • ThreadFactory:线程创建工厂,非必填
    • RejectedExecutionHandler:拒绝策略,出现原因:1. 线程池已经被关闭;2. 达到最大线程时还需要创建线程。
      • java提供:
        终止:默认直接报错
        抛弃:不做任何处理 直接抛弃任务
        抛弃最旧的:队列的第一个抛掉,再尝试提交,用PriorityBlockingQueue会把优先级最高的抛掉
        调用者运行:既不抛弃任务也不抛出异常,线程池饱和后将由调用线程池的主线程自己来执行任务,因此在执行任务的这段时间里主线程无法再提交新任务。

线程池(Executors)弃用

其实它很多api最终也是用的ThreadPoolExecutor,只是默认了一些参数

  • newSingleThreadExecutor:创建一个执行有序 并且失败重启的单一线程
  • newCachedThreadPool:创建缓存线程池(根据当前需要进行创建)适用于短时异步任务
  • newFixedThreadPool:创建固定大小的线程池
  • newScheduledThreadPool:定时任务的线程池,支持定时及周期性的执行任务,也是quarz的底层
    弃用原因:阿里巴巴开发手册说:线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式
    1、例如newFixedThreadPool(N),固定了N个线程,队列使用的是LinkedBlockingQueue,源码显示是Integer.MAX_VALUE。如果出现阻塞,队列里面的任务会越来越多,最终oom。
    2 使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。

Queue

  • 无界队列:队列大小无限制,LinkedBlockingQueue,
    问题:当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM,Executors.newFixedThreadPool 就是这个。

  • 有界队列:先进先出:ArrayBlockingQueue

  • 优先级队列:PriorityBlockingQueue,对象实现conparable接口,优先级由任务的Comparator决定

  • 同步移交队列:SynchronousQueue,要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列

      大队列、小线程池:降低CPU使用率、操作系统资源和上下文切换开销,但是可能降低系统吞吐量。
      小队列、大线程池:CPU使用率较高,理论上可以提升系统吞吐量。太大可能增加上下文切换时间,也会降低效率。
      视服务器资源可调优。
    

future接口

当我们需要线程异步执行的结果时,需要提交Callable接口,返回future。

  • 阻塞式获取:主线程会一直阻塞,直到子线程执行完毕。
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        System.out.println("主线程执行中");
        Future<Object> future = executor.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                Thread.sleep(5000);
                System.out.println("子线程执行完毕");
                return "子线程执行完毕~";
            }
        });

      //  System.out.println(future.get()); //阻塞
        System.out.println("主线程执行完");
    }
  • 使用CompletableFuture实现类,非阻塞式获取
    public static void main(String[] args) {
        System.out.println("主线程执行中");
        CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "子线程执行完毕~";
        }, executor).whenComplete((v, e) -> {
            if (e == null){
                System.out.println("子线程: " + v);
            }
        }).exceptionally(e -> {
            e.printStackTrace();
            System.out.println("异常情况:" + e.getCause() + "\t" + e.getMessage());
            return null;
        });
        System.out.println("主线程执行完");

    }

AQS

  • abstractQueuedSynchronizer抽象队列同步器。一个先进先出的抽象队列 状态值state表示锁是否占用。将线程都封装进一个一个的node节点排队。是juc体系的基石,主要用于解决锁分配给“谁”的问题。
  • 基石:提供多线程的基础api,线程抢占锁、排队的实现。比如ReentrantLock有一个内部类Sync,它继承了AbstractQueuedSynchronizer。ReentrantLock的公平锁 非公平锁的实现都在aqs里面

三个辅助类

CountDownLatchDemo.java

  • CountDownLatch:减少计数,减少至0执行。可以实现等线程结束了,主线程才执行。
  • CyclicBarrier:循环栅栏,有指定线程数等待再执行
  • Semaphore:信号灯,抢到了semaphore就执行。可以实现指定线程数目访问指定代码,例如redis缓存击穿时,所有请求都会打到数据库,可以通过这种方式限制指定数量的线程去访问数据库。PublicController “get/data”

问:怎么等所有的子线程结束再结束主线程

1、CountDownLatch
2、future.get()阻塞
3、t.join()
4、t.isAlive() == true){//t.getState() != State.TERMINATED这两种判断方式都可以
https://www.cnblogs.com/lixin-link/p/10998058.html

threadlocal

ThreadLocalDemo.java

作用

为线程提供局部变量。在每个线程中都有独立拷贝,不会被其他线程干扰。

api

  • withInitial
  • set get
  • remove:释放数据,否则即使ThreadLocal = null数据也不会自动回收造成内存泄露,解释见下。

Thread threadLocal ThreadLocalMap

  • 创建一个thread线程,使用threadLocal,它实际使用的是threadLocal里面的静态内部类ThreadLocalMap,这个map就是真正存放数据的地方。get/set都是在当前线程的ThreadLocalMap里面操作。
  • map的entry是弱引用(不论内存是否充足,gc回收都会回收掉它)。如果是强引用,外界是通过ThreadLocal来对ThreadLocalMap操作,ThreadLocal回收后因为ThreadLocalMap下的key-value是强引用,它不会被回收,造成内存泄露。
  • 如果是弱引用,可以保证key指向的value能被回收。ThreadLocal被回收时,可能会在map里产生key为null的情况,对于这种就无法回收,所以还需要手动掉remove。
  • 使用弱引用+remove来达到不产生内存泄露

关键字

  • sleep
    • Thread类的静态方法,使线程暂停一会,自动恢复
    • 交出CPU,让CPU去执行其他的任务,不会释放锁。
    • interrupt中断会报错,需要在try里面再中断一次
  • wait
    • Object类的方法,让当前线程由“运行状态”进入“等待(阻塞)状态”,直到其他线程调用notify(只随机唤醒一个 wait 线程)或notifyAll(唤醒所有 wait 线程)或者达到指定的等待时间。
    • 释放同步锁
    • interrupt可以中断

二者的区别:是否释放锁,方法来源、入参不同

  • 中断:不是真正的停掉一个线程,线程只能自己停止,只是设置一个状态,是否停止由本线程决定InterruptDemo.java

    • interrupt()
      设置线程的中断状态是true。中断的线程运行过程中先判断中断标识,为true就不执行。
      打断后状态是true,线程执行完了又变回false(对于空闲的线程这个方法没有意义)
    • isInterrupted
      返回中断状态
    • static interruptes
      返回中断状态,并重新设置为false
  • yield():

    • “运行状态”进入到“就绪状态”,重新竞争cpu执行权但是并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权
    • 不会释放锁。
  • join()

    • 等待该线程终止。加在主线程的话,主线程会等待其他线程结束再结束。加在其他线程,会等待join的线程先执行。
      在这里插入图片描述
  • setDaemon/isDaemon
    设置为守护线程,都是的话,jvm退出。
    是个服务线程,准确地来说就是服务其他的线程。Java 中垃圾回收线程就是特殊的守护线程。

线程通信

1、ThreadDemo1 2 3
线程执行顺序不定,可以通过线程间通信实现按顺序执行。通过wait/notifyAll、await/signalAll。
LockSupport也可以实现并且不用考虑先等待再唤醒。
2、Interrupt
一个线程中断其他线程

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值