[2023] 深入理解java多线程并发篇(1)

1、线程

线程的状态

java中六种线程状态-java划分方式

  • NEW:新建
  • RUNNABLE:可运行 —-会被CPU执行
  • BLOCKED:阻塞
  • WAITLNG:等待
  • TIMED_WAITING:等待(有时限)
  • TERMINATED:终结

[2023] 深入理解java多线程并发篇(1)_第1张图片

 

操作系统层面的五种状态

  • 新建
  • 分到CPU时间的:运行
  • 可以分到CPU时间的:就绪
  • 分不到CPU时间的:阻塞
  • 终结

[2023] 深入理解java多线程并发篇(1)_第2张图片

 

线程池对象—ThreadPoolExecutor()

  • corePoolSize:指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;
  • maximumPoolSize:指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量;
  • keepAliveTime:当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被毁**;**
  • unit:keepAliveTime的单位
  • workQueue:任务队列,被添加到线程池中,但尚未被执行的任务;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种;
  • threadFactory:线程工厂,用于创建线程,一般用默认即可;
  • handler:拒绝策略;当任务太多来不及处理时,如何拒绝任务;
    • AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作;
    • CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;
    • DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
    • DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;

[2023] 深入理解java多线程并发篇(1)_第3张图片

 

  • 执行过程:
    • 首先使用的是核心线程,满了后放入堵塞队列,然后再使用救急线程(该线程用完会释放掉),都用完后就会执行选择的拒绝策略;
private  static void testNewRunnableTerminated(){
//        线程工厂对象
        AtomicInteger c = new AtomicInteger(1);
//        工作队列对象
        ArrayBlockingQueue queue = new ArrayBlockingQueue<>(2);
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,  //核心线程
                3,   //最大线程
                0,   //救急线程生存时间
                TimeUnit.MINUTES,   //救急线程生存单位
                queue,   //堵塞队列
                r -> new Thread(r,"myThread" + c.getAndIncrement()),  //线程名字
//                    执行的拒绝策略:1.抛出异常
                    new ThreadPoolExecutor.DiscardPolicy());
        }
  • 输出过程:

[2023] 深入理解java多线程并发篇(1)_第4张图片

 

2、并发问题

并发问题:就是代码在多线程的环境下,多个线程共同去操作一个共享变量,产生的一些破坏了代码的原子性、可见性、有序性的问题!

  • sleep vs wail

    • 他们都可以被interruot()方法打断[2023] 深入理解java多线程并发篇(1)_第5张图片
    • lock vs synchronized​​​​        

     [2023] 深入理解java多线程并发篇(1)_第6张图片

  • lock的公平锁和非公平锁

    • 调用new MyReentrantLock(false) //false表示执行非公平锁,taue表示公平锁

    • 公平锁:会按进入阻塞队列的先后顺序,执行线程,等待进入获取锁

    • 非公平锁:不会按先后顺序执行

    • lock锁底层实现细节:[2023] 深入理解java多线程并发篇(1)_第7张图片

    • await():使线程放入等待队列,等待唤醒,
    • signalAll():使等待队列的线程重新返回阻塞队列,等待持有锁的线程释放锁
  • volatile(内存屏障)能否保证线程安全、 [2023] 深入理解java多线程并发篇(1)_第8张图片

    • 原子性:在多线程情况下,初始值为10,两个线程同时执行+5,-5操作,一条线程执行+5时,执行到一半时,切换到另外一条线程执行-5;这时执行完结果为5;然后重新返回第一条线程执行,剩下的+5操作(因为他前面执行到一半,根本不知道值已经被另外一个线程把值改了),执行完后10+5,他执行完的结果为15,把上一条执行完的结果覆盖掉;该结果就表示破坏了原子性的问题。

      • 解决办法:加锁,不让多条线程一起进入执行或者其他方式。
    • 可见性:当一个线程1执行一个代码时,循环条件是一个内存里的共享变量stop,会一直判断stop是false(大概一秒钟可以判断一千万次),达到一定的阈值后JIT(java的一个编译器),认为线程1的代码一直为false,为了提供效率,就会去修改线程1的代码,把stop直接就改为false;这样线程2去把stop修改为true之后,线程1的循环也不会退出,因为他已经被改了代码,所以他压根就看不到共享变量的值被修该为true

      • 解决办法:在共享变量(成员变量)上加volatile修饰
    • [2023] 深入理解java多线程并发篇(1)_第9张图片
    • 有序性:在编译的过程中出现指令重排序

      • 解决办法:在成员变量上加Volatile修饰
      • 在读的变量里需要放在最后一个变量上,在读的情况,该屏障是向下的,禁止下面的代码越过屏障到上面来执行
      • 在写的变量里需要放到第一个,和读先反

     [2023] 深入理解java多线程并发篇(1)_第10张图片

     

3、java中悲观锁和乐观锁

  • 悲观锁的代表是synchronized和Lock锁
  • 乐观锁的代表是Atomiclnteger, 使用cas来保证原子性,底层使用的是Unsafe这个类,

[2023] 深入理解java多线程并发篇(1)_第11张图片

 

在等待锁释放的过程中

悲观锁:会在重试争抢几次锁之后,就会进入阻塞状态,当获得锁的线程释放之后,在唤醒去继续争抢锁,所以会涉及上下文切换,

乐观锁:不会进入阻塞状态,会一直请求获取锁,直至前面的线程释放锁,前提条件是线程数不超过cpu核数

4、Hashtable vs ConcurrentHashMap

[2023] 深入理解java多线程并发篇(1)_第12张图片

 

  • **hashtable:**的扩容=原始容量*2+1,所以hashtable的容量都是质数,取余数分散性较好,所以不用二次哈希去计算数组位置
  1. 7版本的ConcurrentHashMap
  • ConcurrentHashMap:的结构是一个Segment下标数组中,套一个小的数组(clear),小的数组里下标冲突时,才会是一个链表。
    • ConcurrentHashMap是一个Segment一把锁,Segment长度多小决定了有多少把锁
    • ConcurrentHashMap在创建的时候指定了Segment的容量之后,后面不能扩容,只能扩容clear数组
    • [2023] 深入理解java多线程并发篇(1)_第13张图片 

  • 计算ConcurrentHashMap的下标

先算出clevel是2的几次方,如:16是2的4次方

所以Segment索引=二次hash的值转换二进制后的高5位,得出的结果是10100,转换为10进制是20,所以Segment索引就是20,clear的索引是看最低位的是多少

[2023] 深入理解java多线程并发篇(1)_第14张图片

 

  • ConcurrentHashMap扩容:

    • 之后扩容clear的容量,且扩容只扩容自己那个Segment数组
    • [2023] 深入理解java多线程并发篇(1)_第15张图片

    • clear初始扩容时会依据Segment[0]下标的元素进行扩容,Segment[0]的clear会有一个初始的容量,该容量初始长度为capacity容量/Segment的长度
    1. 8版本的ConcurrentHashMap

      • capacilty:往map放的元素的个数,
    2. ConcurrentHashMap扩容时怎么解决并发

[2023] 深入理解java多线程并发篇(1)_第16张图片

 

5、ThreadLocal

ThreadLoca(瑟为咯口)l叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

  • ThreadLoca创建方式,一般以静态成员变量方式创建,

private static ThreadLocal *localVar* = new ThreadLocal();

  • 一句话理解ThreadLocal,threadlocl是作为当前线程中属性ThreadLocalMap集合中的某一个Entry的key值Entry(threadlocl,value),虽然不同的线程之间threadlocal这个key值是一样,但是不同的线程所拥有的ThreadLocalMap是独一无二的,也就是不同的线程间同一个ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个value变量地址是一样的。

[2023] 深入理解java多线程并发篇(1)_第17张图片

  • ThreadLocal的理解含义:

    [2023] 深入理解java多线程并发篇(1)_第18张图片

  • ThreadLocal与synchronized比较

ThreadLocal其实是与线程绑定的一个变量。ThreadLocal和Synchonized都用于解决多线程并发访问。

但是ThreadLocal与synchronized有本质的区别:

1、Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

2、Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本

,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。

而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。

你可能感兴趣的:(java,开发语言,jvm)