线程
一、基础知识
1 进程:是系统执行资源分配和调度的独立单位,有属于自己的存储空间和系统资源。
ps:进程间内存独立不共享
2 线程:进程中的顺序控制流
例如:单线程--一个进程包含一个顺序控制流·
多线程--一个进程包含多个顺序控制流·
ps:java中,线程间堆空间和方法区内存共享,栈内存不共享
3.实现方法
① 继承Thread
② 实现Runnable
③ 实现Callable(可以获取线程的执行 结果,但此时调用方线程阻塞)
4.ThreadLocal
每一个Thread内部,有一个ThreadLocalMap对象,即每个线程独有的变量。
5. 线程池状态
①RUNNING:处于正常运行状态,可接收/处理任务。
②SHUTDOWN:调用线程池的shutdown()方法。不接受任务,执行完队列中的任务。
③STOP:调用shutdownNow()方法。不接受任务,队列中的任务不执行,运行中的任务中断。
④TIDYING:SHUTDOWN/STOP无线程运行时,转为该状态。
⑤TERMINED:线程池已关闭。

二、对象
1. 一个空的Object对象,内存分布为
①MW(mark word:8个字节)
②klass(类元指针:4/8个字节)如果开启了指针压缩,那么类元指针占8个字节
③padding(填充:4个字节)由于存在伪共享问题,所以会有4个字节的填充,即未开启指针压缩
2. 强软弱虚
①强(强引用):当不在使用对象时,会被gc回收。
②软(软引用):当不使用对象时,会在堆空间不足以分配新的资源时回收。
③弱(弱引用):不管有没有使用该对象,只要gc就会被回收。
④虚(虚引用):获取不到信息,用来监测内存外数据被删除,通知gc

三、锁
1.轻量级锁(自旋锁:CAS)
无等待队列,忙等待。
场景:线程执行时间短,等待线程少。
2.重量级锁
有等待队列,通过操作系统调度。
场景:线程执行时间长,等待线程多。
3.锁升级过程:
①只有一个线程A访问资源时,为偏向锁;
②当有第二个线程B来访问资源时,若锁对象头指向线程B的ID,则直接获取锁;否则CAS尝试替换锁对象头指向自己,替换成功则仍然为偏向锁,失败改为自旋锁(轻量级锁);
③n个线程竞争同一个资源,升级为synchronize锁(重量级锁)
4.什么是synchronize锁
是基于计算机内核态的monitorenter和monitorexit,其中底层通过c语言编写的lock comxchg 来实现上锁。
由于从用户态->内核态->用户态非常耗性能,所以称之为重量级锁。
5.cache line
CPU在内存中取数据是以块的形式读取数据(也就是以cache line为单位进行读取),并加入到自己的高速缓存中,每个cache line是64个字节(Long = Double = 8 字节,Char = Short = 2 字节, Int = Float = 4 字节)
CPU伪共享问题:两个线程AB同时访问不在同一内存单元的x和y,但由于CPU从的将其加载到同一个cache line中,所以当线程A修改x后,将会给线程B发RFO消息并把它的缓存失效掉。
缓存行填充:可解决CPU伪共享问题。让每一份数据占据一个缓存行(将数据填充到64byte)。
例如:预读取对象Object obj 的 Long x 成员变量,则在x前填充6个Long b1 ~ Long b6(对象头也占8字节),使其达到64个字节,因此会被读取到单独的cache line中。
拓展:java 8中可以用@Contended在类/字段上,将类/字段加载到独立的缓存行上,需加上虚拟机参数-XX:-RestrictContended。
关键字
valatile:
线程可见性。因为线程是运行在不同的CPU内,所以线程间可见性=CPU间可见性。
如果vlatile标注的变量x被线程A修改时,会通知其他使用x的线程让其重新去缓存中取一遍。
可解决指令重排序问题。
面试题:
一、CAS类
1.ABA问题:(针对变量m)
描述:线程A修改m的期望值为0,但这个0经过线程B修改为8,再由线程C修改为0,此时的0不同于真正期望的0。
解决:添加版本号(integer、boolean等等)
2.CAS修改值的原子性问题
描述:在线程A内部判断m期望值正确之后,在修改之前,线程B进行修改。
解决: 通过使用原子性的类,例如AtomicInteger。
底层是用汇编语言写的一个汇编操作 lock cmpxchg指令,实现CAS修改值的原子性(ps:多核CPU执行 lock cmpxchg,单核CPU执行 cmpxchg)
二、其他
1.指令重排序问题
描述:原本先执行a(),在执行b(),CPU为了提高效率,在执行a()的时候,把b()执行了。
场景:Object o = new Object()有三个步骤,
1. new 一个对象,并为其分配空间, 其成员变量为默认值 (半初始化状态)
2. invokespecial (运行构造方法),此时成员变量为设定值
3. 将 o 指向到 new 出来的空间
重排序发生,执行顺序为1->3->2,线程A创建对象o,线程B获取到了半初始化的o,此时其
成员变量为0,出问题了。
2.哲学家就餐问题
描述:五个哲学家,坐在圆桌上,每个哲学家左右手都有一根筷子,如何才能使就餐最快完成。
解决:避免死锁,混入一个左撇子或右撇子即可。最高效:奇数左撇子,偶数右撇子。
3.生产者消费者问题
描述:生产者生产,消费者消费,生产最大量有限制。
解决:生产者消费者抢夺仓库一把锁,抢到后判断是否超出仓库最大量/仓库是否无商品。
4.异步回调回滚问题
描述:多个事务,其中一个事务失败,要求其他事务回滚。
解决:CompletableFuture的getAll方法可以获取到所有事务的回调结果。
5. 为什么不推荐使用Executors来创建线程?
原因:Executors底层是通过调用new ThreadPoolExecutor(,,,new LinkedBlockQueue<Runnable>())方法,而LinkedBlockQueue()方法是一个无界阻塞队列,当开的线程多了,任务不断被塞入队列中,容易导致OOM(out of memory)。
举例:FixedThreadPool、SingleThreadExecutor。
建议:用ThreadPoolExecutor来定义线程池。
6.Sychronized和ReentrantLock的区别?
①用法不同:Sychronized可用于修饰普通方法、静态方法和代码块。ReentrantLock只能修饰代码块。
②获取锁和释放锁方式不同:Sychronized自动上锁、释放锁。ReentrantLock需手动lock()、unlock()。
③锁类型不同:Sychronized是非公平锁,ReentrantLock可在创建时传入boolean值来控制是否为公平锁。
④响应中断不同:Sychronized不可响应中断,ReentrantLock可以用lockInterruptibly()来获取锁并响应中断指令。

⑤底层实现不同:Sychronized在JVM层通过监视器(Monitor)实现,ReenrtrantLock通过AQS程序级别的API实现。

⑥锁标识不同:Sychronized锁信息在对象头MarkWord上,ReentrantLock是通过state变量判断。
7.

浙公网安备 33010602011771号