线程生命周期:
正常执行一次就结束的:new->runnable->runing->terminated;
执行多次结束的:new->runnable->runing->调用wait()变成wating->调用notify()变成runnable->runing->terminated
并行
指多个事情在同一时间点上同时发生;
并行的多个线程之间不会相互抢占资源;
只有才多个CPU或1个CPU多核的情况下,才会发生并行;
并发
指多个事情在同一时间段内同时发生;
并发的多个任务直接是会相互抢占资源的;
锁类型:
保存在对象的64位的对象头的前32位中:
- 无锁
- 偏向锁
- 轻量锁
- 重量锁 :monitor介入,必定有一个线程会获得锁
懒汉式单例的问题:
这个懒汉式是线程不安全的,DCL单例:用到的时候再去创建(比较懒):先判空,再加锁,再判空,只加了一层锁;
class Singleton{
private String str;
private static Singleton singleton;
public String getStr() {
return str;
}
public static Singleton getInstance(){
if(singleton==null){
synchronized (Singleton.class){//第一层锁,保证只有一个线程进入
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
在dex指令优化时,会指令重排序;
指令重排序:
导致代码执行结果不一样时,不会进行指令重排序;
代码执行结果一样时,会指令重排序;
在java中new对象并赋值是一句代码 singleton=new Singleton(),在dex中指令是分为3个指令:
- new instence:堆区只有对象头和对齐,还没有对象实例(还没有对象的方法和变量,如上面的getStr()和str)
- invoke:初始化对象实例数据(此时才会实例化getStr()和str)
- 方法区变量singleton和堆区的对象绑定
指令重排序时,指令1顺序是不变的,可能会将2和3顺序调换,调换后1->3->2;
这时调用对象的方法getStr()就会报空指针,因为第2步还没有初始化对象实例数据;
解决:
1、推荐:添加volatile关键字:
class Singleton{
private String str;
private static volatile Singleton singleton;//第二层锁,volatile关键字禁止指令重排
public String getStr() {
return str;
}
public static Singleton getInstance(){
if(singleton==null){
synchronized (Singleton.class){//第一层锁,保证只有一个线程进入
//双重检查,防止多个线程同时进入第一层检查(因单例模式只允许存在一个对象,故在创建对象之前无引用指向对象,所有线程均可进入第一层检查)
//当某一线程获得锁创建一个Singleton对象时,即已有引用指向对象,singleton不为空,从而保证只会创建一个对象
//假设没有第二层检查,那么第一个线程创建完对象释放锁后,后面进入对象也会创建对象,会产生多个对象
if(singleton==null){//第二层检查
//volatile关键字作用为禁止指令重排,保证返回Singleton对象一定在创建对象后
singleton=new Singleton();
//singleton=new Singleton语句为非原子性,实际上会执行以下内容:
//(1)在堆上开辟空间;(2)属性初始化;(3)引用指向对象
//假设以上三个内容为三条单独指令,因指令重排可能会导致执行顺序为1->3->2(正常为1->2->3),当单例模式中存在普通变量需要在构造方法中进行初始化操作时,单线程情况下,顺序重排没有影响;但在多线程情况下,假如线程1执行singleton=new Singleton()语句时先1再3,由于系统调度线程2的原因没来得及执行步骤2,但此时已有引用指向对象也就是singleton!=null,故线程2在第一次检查时不满足条件直接返回singleton,此时singleton为null(即str值为null)
//volatile关键字可保证singleton=new Singleton()语句执行顺序为123,因其为非原子性依旧可能存在系统调度问题(即执行步骤时被打断),但能确保的是只要singleton!=0,就表明一定执行了属性初始化操作;而若在步骤3之前被打断,此时singleton依旧为null,其他线程可进入第一层检查向下执行创建对象
}
}
}
return singleton;
}
}
2、双重synchronized加锁:
synchronized锁代码块,保证只有一个线程进入:
class Singleton{
private String str;
private static Singleton singleton;
public String getStr() {
return str;
}
//第一层锁
public synchronized static Singleton getInstance(){
if(singleton==null){
synchronized (Singleton.class){//第二层锁
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
volatile:
- 修饰的变量在创建时,不会指令重排序
- 对象是放在主内存的内存屏障中,可以被其他线程观察
- 若对象被其他线程改变,则其他线程高速缓冲区内持有的对象将作废,重新去内存屏障中获取;
CAS:
1、需要使用到volatile自动刷新被修改的对象机制;(对象被其他线程修改时自动去内存屏障中重新获取新的对象,再放到当期线程的高速缓冲区中)
2、提交前先判断;
i++操作:如初始i=1; 要多个线程并发进行 i++操作
- 多个线程取到i,放在自己的高速缓冲区中;
- 线程A取到i的数据地址、i值1、预期值是2;
- 线程A+1操作后
- 先不提交;
- 先去判断高速缓冲区里的i值预期值与自己取的i值是否相等
- 相等则提交;
- 不相等,则重新去高速缓冲区读i值,再进行i+1,走4、5;
ABA:
线程A监听到的对象i,可能被其他线程进行了+1+1+1-1-1-1...的操作,虽然值相同,但是进行了好多操作;
读取时,记录修改的版本号和次数,提交时对比就可以解决;
locks原理:
用到了volatile原子操作、线程队列休眠、CAS机制;
有线程执行时则休眠:LookSupprt.pack(),
执行线程执行完后唤醒队列里的休眠的线程:LookSupprt.unpack()