java多线程提高篇
synchronize
可以确保被它修饰的代码块或方法在同一时刻只有一个线程执行,确保线程安全性。
修饰成员方法:对象锁
修饰静态方法:类锁
修饰代码块synchronize(this|a.class):对象锁或类锁
tips:构造方法是线程安全的,不可以使用synchronize关键字修饰(单例模式中只有无参构造方法)
底层原理:
synchronized采用“锁升级+对象头的Mark Word”实现。在并发环境中,synchronized会随着多线程竞争的加剧,按照如下步骤逐步升级:无锁、偏向锁(撤销偏向锁重新竞争)、轻量级锁(自旋、锁膨胀)、重量级锁。
markword存储锁状态和锁标志,对象的hashcode信息
偏向锁:markword存储获得锁的线程ID,对象偏向某个线程
轻量级锁:加锁时,jvm先在竞争线程的堆栈中创建存储锁记录的空间,将markword复制到锁记录中,再使用cas操作将markword的值替换为指向锁记录的指针
详细:https://www.nowcoder.com/interview/ai/report?roomId=466658
Lock则采用“CAS+volatile”实现,其实现的核心是AQS。AQS是线程同步器,是一个线程同步的基础框架,它基于模板方法模式。在具体的Lock实例中,锁的实现是通过继承AQS来实现的,并且可以根据锁的使用场景,派生出公平锁、不公平锁、读锁、写锁等具体的实现。
volatile
防止指令重排问题
cpu高速缓存——解决主存与cpu运行速度匹配问题。从cpu cache读取数据,运输完毕后写入主存——但是,比如i++操作,存在缓存不一致问题。
类似的,JMM中,线程可以访问本地内存而不是直接访问主存,对共享变量会造成数据不一致。
使用volatile关键字确保变量的可见性,确保在主存中读取变量
synchronize修饰代码块或方法,volatile修饰变量
synchronize确保代码或方法在多线程下的同步性。volatile确保变量在多线程的可见性,还可以防止指令重排。
ThreadLocal(线程的变量副本)
ThreadLocal声明的变量,访问它的每个线程都会有这个变量的本地副本,成为专属线程的本地变量
原理:Thread的内部类ThreadLocalMap存储变量,存储的是《ThreadLocal,value》的键值对
内存泄露问题:key是弱引用,value是强引用,在垃圾回收时,key会被删除而value不会,此时key为null,永远不会被回收
CAS原理
compare and swap(比较交换原理)
拿期望的值和原本的一个值作比较,如果相同则更新成新的值。在JUC的原子类的compareandset方法中大量使用
无锁操作是使用CAS(compare and swap)又叫做比较交换来鉴别线程是否出现冲突,出现冲突就重试当前操作直到没有冲突为止。
Atomic原子类
基本类型AtomicInteger
数组类型AtomicIntegerArray
引用类型AtomicReference
修改类型AtomicIntegerFieldUpdater
主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升
多线程问题定位
内存泄露与内存溢出
jps查看所有的java进程
jstat命令查看垃圾回收情况,jstat -gc pid
jstack pid 详细dump信息
图形化界面jconsole可以查看内存、线程、cpu等多种信息
内存泄漏是指程序在申请内存后,无法释放已申请的内存空间。
内存溢出是指程序申请内存时,没有足够的内存供申请者使用,报错OOM。内存泄漏的堆积最终会导致内存溢出。
解决方案;
引起内存溢出的原因有很多种,常见的有以下几种:
1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3.代码中存在死循环或循环产生过多重复的对象实体;
4.使用的第三方软件中的BUG;
5.启动参数内存值设定的过小;
内存溢出的解决方案:
第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)
第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。
第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。
重点排查以下几点:
2.检查代码中是否有死循环或递归调用。
3.检查是否有大循环重复产生新对象实体。
4.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
5.检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。
windows下命令
tasklist | findstr java查找所有相关的进程
taskkill
linux下命令
top -Hp pid(线程id) 查看进程的所有线程情况(cpu利用率,内存使用率等)
ps -fe查看所有进程

浙公网安备 33010602011771号