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 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
-
lock的公平锁和非公平锁
-
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数组
-
先算出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
-
ConcurrentHashMap扩容时怎么解决并发
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变量地址是一样的。
ThreadLocal其实是与线程绑定的一个变量。ThreadLocal和Synchonized都用于解决多线程并发访问。
但是ThreadLocal与synchronized有本质的区别:
1、Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
2、Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本
,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。
而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。