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查看所有进程

 

 

posted @ 2022-01-10 12:40  黑白灰java  阅读(158)  评论(0)    收藏  举报