Volatile :Java虚拟机提供的轻量级的同步机制,基本遵守JMM规范
- 保证可见性: volatile int number=0;此时的number的值被一个线程修改后,其他线程也可以知道修改后的值
- 不保证原子性:线程太快会出现写覆盖,造成数据丢失
怎么解决:1. 加synchronized
2.直接使用juc下AtomicInteger(AtomicInteger能保持原子性是由于底层的Unsafe类)
- 禁止指令重排(难点): 避免多线程环境下出现乱序执行的现象
JMM三大特性:1.可见性:某一线程修改值后写回主内存后,要及时通知其他线程的机制
2.原子性:某个线程正在做某个业务时,中间不可以被加塞或者分割,需要整体完整,要么都成功要么都失败
3.有序性:计算机执行程序时为提高性能,编译器和处理器会对指令做重排,重排经过编译器优化,指令并行,内存系统三次重排(处理器在重排序时必须考虑指令之间的数据依赖)。多线程环境中,由于编译器优化的存在,两个线程中的变量能否保证一致性是无法确定的,结果无预测
Number++在多线程下是非线程安全的,为何不加synchronized解决
加synchronized太重,大材小用
JMM:(Java内存模型):抽象概念,并不真实存在,描述的是一种规范,定义了各个字段的访问方式
JMM关于同步的规定:
- 线程解锁之前,必须把共享变量的值刷新会主内存
- 线程加锁之前,必须读取主页内存的最新值到自己的工作内存
- 加锁解锁是同一把锁
自增变量:
++i先自增后把结果压入栈,i++先把i压入栈在自增
赋值最后计算
自增自减都是直接改变变量的值,不经过操作数栈
最后的赋值之前,临时结果也是存在操作数栈中
JVM垃圾回收机制,GC(分代收集算法)发生在JVM那部分,有几种GC,他们的算法是什么
GC发生在堆里,Minor GC(发生在年轻代GC):次数上频繁收集Young区 Full GC(发生在老年代GC):次数上较少收集Old区;
四种算法:
引用计数法:较难处理循环引用,且计数器本身也有一定的消耗
复制算法:发生在年轻代中Tracing从From找到存活的对象拷贝到To,二者交换身份,下次从TO开始分配内存,没有标记和清除过程,效率高,没有碎片,但是需要双倍的空间
标记清楚: 发生在老年代;标记存活对象,清楚未标记对象;两次扫描耗时严重,会产生内存碎片,但不需要额外空间
标记压缩:发生在老年代; 标记存活对象,再次扫描将存活对象滑动到一端,没有碎片,但是需要移动对象的成本
单例模式:(Singleton)
例如JVM运行环境的Runtime类
要点:
整个系统中只能有一个实例对象被获取和使用:构造器私有化
必须自行创建这个实例:静态变量来保存这个唯一的实例
像整个系统提供该实例:直接暴露(静态变量public) 用静态变量的get方法获取
饿汉式:类初始化时直接创建对象,不存在线程安全问题;枚举形式最简单
懒汉式:存在线程安全问题,静态内部类最简单
DCL版(俗称双端检索机制)的单例模式:线程不一定安全,因为有指令重排的存在,加入volatile可以禁止指令重排
多线程环境下单例的写法:
public class Singleton5 {
private static volatile Singleton5 instance;
private Singleton5(){
}
public static Singleton5 getInstance(){
if (instance==null) {
synchronized (Singleton5.class) {
if (instance==null) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
instance=new Singleton5();
}
}
}
return instance;
}
CAS是什么也就是比较并交换(compareAndSet)CAS全称为Compare-And-Swap,它是一条CPU并发原语。调用Unsafe类中的CAS方法,JVM发出CAS汇编指令,这是偏硬件的功能。而原语的执行必须是连续的,在执行过程中不允许被终端。也就说CAS是一条CPU的原子指令。
CAS底层原理:
AtomicInteger类下:
private static final Unsafe unsafe=Unsafe.getUnsafe();
private static final llong valueOffset;
static{
try{
valueOffset=unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField(“value”));
}catch(Exception ex){throw new Error(ex);}
}
private volatile int value;
- Unsafe是CAS的核心类,存在于sun.misc包中 注:Unsafe类中的所有方法都是native修饰的,其中的方法都是直接调用操作系统底层的资源执行相应的任务
- 变量valueOffset表示变量值在内存总的偏移地址,Unsafe就是根据内存偏移地址获取数据的
public final int getAndIncrement(){
return unsafe.getAndAddInt(this,valueOffset,1);
}
- 变量value用volatile修饰,保证了多线程之间的内存可见性
图为2中底层代码的详细讲解
CAS小总结
CAS的缺点:
- 循环时间长,开销比较大
- 只能保证一个共享变量的原子操作
- 引出来的ABA问题:CAS算法实现的一个重要前提需要去除内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差内会导致数据的变化
原子类AtomicInteger的ABA问题,原子更新引用
原子引用:
时间戳的原子引用;
原子引用+一种新机制à修改版本号(类似于时间戳)
-------------------------------------------------------------------------------
集合类不安全之并发修改异常:java.util.ConcurrentModificationException,高并发多线程访问下的常见异常
ArrayList线程不安全的原因是其中的add方法没有加锁
- 故障现象:java.util.ConcurrentModificationException
- 导致原因:并发争抢修改导致,参考花名册签名情况,一个人正在写,另一个同学过来抢夺,导致数据不一致异常。并发修改异常。
- 解决方案:
- 用Vector
- Collections.synchronizedList(new ArrayList<>();
- new CopyOnWriteArrayList<>();
- 优化建议(不犯同样的错误):
Set线程不安全问题
解决方案:
-
- Collections.synchronizedSet(new ArraySet<>();
- new CopyOnWriteArraySet<>();
HashSet底层数据结构Hash Map,但是Set中填一个值Map中填两个值原因是HashSet的add方法调用HashMap的put方法,但是add方法中添加的值是put中的K,V是一个PRECENT常量
Map线程不安全问题:
解决方案:
5.1 Collections.synchronizedSet(new HashMap<>();
5.2 new CopyOnWriteHashMap<>();
-------------------------------------------------------------------------------
JVM方法作用域,内存地址指针注意String类型的特殊性,常量池中有则复用没有新建
-------------------------------------------------------------------------------
JAVA中的锁
公平锁和非公平锁:公平锁按申请锁的顺序来获取锁,非公平锁:不安申请锁的顺序获取锁,高并发情况下可能会发生优先级反转或者饥饿现象
区别:
非公平锁的优点在于吞吐量大于公平锁;对于Synchronized而言也是一种非公平锁。
可重入锁(又叫递归锁):在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁 作用就是防止死锁
ReentrantLock/Synchronized就是一个典型的可重入锁
注:同步方法可以进入另一个同步方法,但是锁要加几次释放几次
自旋锁理论知识:CAS思想(自旋)
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁;这样的好处是减少线程上下文的切换,缺点是循环会消耗CPU
读写锁(共享锁/独占锁),互斥锁:
小总结:读读能共存,读写不能共存,写写不能共存
代码验证:
===========================================================
类初始化和实例初始化
类初始化过程:
创建对象需要先加载和初始化类
Main方法所在的类先加载和初始化
子类初始化时需先初始化父类
类初始化就是执行<clinit>()方法
<clinit>()方法由静态类变量显示赋值代码和静态代码块组成
类变量显示赋值代码和静态代码块从上到下顺序执行
<clinit>()方法只执行一次
实例初始化过程:
实例初始化就是执行<init>()方法
<init>()方法由非静态实例变量显示赋值代码和非静态代码块,对应的构造器代码组成
三者从上到下顺序执行
<init>()方法只执行一次
方法的重写:
SpringMVC中如何解决POST请求中文乱码问题
在web.xml 文件中配置过滤器:
<filter>
<filter-name>CharacterEcodingFilter<filter-name/>
<filter-class>org.springframework.web.filter.CharacterEcodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<filter>
<filter-mapping>
<filter-name> CharacterEcodingFilter </filter-name>
<url-parten>/*</url-parten>
</filter-mapping>
GET请求呢
在服务器配置文件中的第一个<connecter></connecter>中添加属性URIEncoding=”UTF-8”