Android 锁、线程

线程生命周期:

正常执行一次就结束的:new->runnable->runing->terminated;

执行多次结束的:new->runnable->runing->调用wait()变成wating->调用notify()变成runnable->runing->terminated

并行

指多个事情在同一时间点上同时发生;

并行的多个线程之间不会相互抢占资源;

只有才多个CPU或1个CPU多核的情况下,才会发生并行;

并发

指多个事情在同一时间段内同时发生;

并发的多个任务直接是会相互抢占资源的;

锁类型:

保存在对象的64位的对象头的前32位中:

  1. 无锁
  2. 偏向锁
  3. 轻量锁 
  4. 重量锁 :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个指令:

  1. new instence:堆区只有对象头和对齐,还没有对象实例(还没有对象的方法和变量,如上面的getStr()和str)
  2. invoke:初始化对象实例数据(此时才会实例化getStr()和str)
  3. 方法区变量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:

  1. 修饰的变量在创建时,不会指令重排序
  2. 对象是放在主内存的内存屏障中,可以被其他线程观察
  3. 若对象被其他线程改变,则其他线程高速缓冲区内持有的对象将作废,重新去内存屏障中获取;

CAS:

1、需要使用到volatile自动刷新被修改的对象机制;(对象被其他线程修改时自动去内存屏障中重新获取新的对象,再放到当期线程的高速缓冲区中)

2、提交前先判断;

i++操作:如初始i=1; 要多个线程并发进行 i++操作

  1. 多个线程取到i,放在自己的高速缓冲区中;
  2. 线程A取到i的数据地址、i值1、预期值是2;
  3. 线程A+1操作后
  4. 先不提交;
  5. 先去判断高速缓冲区里的i值预期值与自己取的i值是否相等
  6. 相等则提交;
  7. 不相等,则重新去高速缓冲区读i值,再进行i+1,走4、5;

ABA:

线程A监听到的对象i,可能被其他线程进行了+1+1+1-1-1-1...的操作,虽然值相同,但是进行了好多操作;

读取时,记录修改的版本号和次数,提交时对比就可以解决;

locks原理:

用到了volatile原子操作、线程队列休眠、CAS机制;

有线程执行时则休眠:LookSupprt.pack(),

执行线程执行完后唤醒队列里的休眠的线程:LookSupprt.unpack()

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值