1、线程
线程的状态
java中六种线程状态-java划分方式
- NEW:新建
- RUNNABLE:可运行 —-会被CPU执行
- BLOCKED:阻塞
- WAITLNG:等待
- TIMED_WAITING:等待(有时限)
- TERMINATED:终结

操作系统层面的五种状态
- 新建
- 分到CPU时间的:运行
- 可以分到CPU时间的:就绪
- 分不到CPU时间的:阻塞
- 终结

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

- 执行过程:
- 首先使用的是核心线程,满了后放入堵塞队列,然后再使用救急线程(该线程用完会释放掉),都用完后就会执行选择的拒绝策略;
private static void testNewRunnableTerminated(){ // 线程工厂对象 AtomicInteger c = new AtomicInteger(1); // 工作队列对象 ArrayBlockingQueue<Runnable> 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()); }
- 输出过程:
2、并发问题🦀
并发问题:就是代码在多线程的环境下,多个线程共同去操作一个共享变量,产生的一些破坏了代码的原子性、可见性、有序性的问题!
-
sleep vs wail
- 他们都可以被interruot()方法打断

- lock vs synchronized

- 他们都可以被interruot()方法打断
-
lock的公平锁和非公平锁
-
调用new MyReentrantLock(false) //false表示执行非公平锁,taue表示公平锁
-
公平锁:会按进入阻塞队列的先后顺序,执行线程,等待进入获取锁
-
非公平锁:不会按先后顺序执行
-
lock锁底层实现细节:

- await():使线程放入等待队列,等待唤醒,
- signalAll():使等待队列的线程重新返回阻塞队列,等待持有锁的线程释放锁
-
-
volatile(内存屏障)能否保证线程安全、

-
-
原子性:在多线程情况下,初始值为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修饰

-
有序性:在编译的过程中出现指令重排序
- 解决办法:在成员变量上加Volatile修饰
- 在读的变量里需要放在最后一个变量上,在读的情况,该屏障是向下的,禁止下面的代码越过屏障到上面来执行
- 在写的变量里需要放到第一个,和读先反

-
3、java中悲观锁和乐观锁
- 悲观锁的代表是synchronized和Lock锁
- 乐观锁的代表是Atomiclnteger, 使用cas来保证原子性,底层使用的是Unsafe这个类,
在等待锁释放的过程中
悲观锁:会在重试争抢几次锁之后,就会进入阻塞状态,当获得锁的线程释放之后,在唤醒去继续争抢锁,所以会涉及上下文切换,
乐观锁:不会进入阻塞状态,会一直请求获取锁,直至前面的线程释放锁,前提条件是线程数不超过cpu核数
4、Hashtable vs ConcurrentHashMap

- **hashtable:**的扩容=原始容量*2+1,所以hashtable的容量都是质数,取余数分散性较好,所以不用二次哈希去计算数组位置
- 7版本的ConcurrentHashMap
- ConcurrentHashMap:的结构是一个Segment下标数组中,套一个小的数组(clear),小的数组里下标冲突时,才会是一个链表。
- ConcurrentHashMap是一个Segment一把锁,Segment长度多小决定了有多少把锁
- ConcurrentHashMap在创建的时候指定了Segment的容量之后,后面不能扩容,只能扩容clear数组

- 计算ConcurrentHashMap的下标
先算出clevel是2的几次方,如:16是2的4次方
所以Segment索引=二次hash的值转换二进制后的高5位,得出的结果是10100,转换为10进制是20,所以Segment索引就是20,clear的索引是看最低位的是多少

-
ConcurrentHashMap扩容:
- 之后扩容clear的容量,且扩容只扩容自己那个Segment数组

- clear初始扩容时会依据Segment[0]下标的元素进行扩容,Segment[0]的clear会有一个初始的容量,该容量初始长度为capacity容量/Segment的长度
-
8版本的ConcurrentHashMap
- capacilty:往map放的元素的个数,
-
ConcurrentHashMap扩容时怎么解决并发

5、ThreadLocal
ThreadLoca(瑟为咯口)l叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
- ThreadLoca创建方式,一般以静态成员变量方式创建,
private static ThreadLocal<String> *localVar* = new ThreadLocal<String>();
- 一句话理解ThreadLocal,threadlocl是作为当前线程中属性ThreadLocalMap集合中的某一个Entry的key值Entry(threadlocl,value),虽然不同的线程之间threadlocal这个key值是一样,但是不同的线程所拥有的ThreadLocalMap是独一无二的,也就是不同的线程间同一个ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个value变量地址是一样的。

-
ThreadLocal的理解含义:

-
ThreadLocal与synchronized比较
ThreadLocal<T>其实是与线程绑定的一个变量。ThreadLocal和Synchonized都用于解决多线程并发访问。
但是ThreadLocal与synchronized有本质的区别:
1、Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
2、Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本
,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。
而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
文章详细介绍了Java中线程的六种状态以及操作系统层面的五种状态,探讨了线程池ThreadPoolExecutor的核心参数和执行过程。同时,文章讨论了并发问题,包括原子性、可见性和有序性,并对比了sleep和wait、lock与synchronized的区别。此外,还分析了悲观锁与乐观锁的实现,例如synchronized和AtomicInteger。最后,提到了Hashtable与ConcurrentHashMap的异同以及ThreadLocal在多线程环境中的作用,强调了ThreadLocal用于线程间的数据隔离。


1185





