《并发实战》笔记1.0.第二章安全性,原子性,加锁机制

《并发实战》1.0.安全性,原子性

俺开始学并发了!

安全性

一言以蔽之就是,做正确的事,但是由于并发的场景下,执行顺序不确定所以就容易出现问题。当然如果没有状态性,那咋整都莫得问题,不过如果有,而且需要修改状态,那这个问题就要格外重视。
如果有需要重视安全性的问题,那么我们就需要实现它的同步。
** 同步机制:**
1.synchronized:它提供一种独占锁
2.volatitle:可见性
3.显式锁:
4.原子变量:保证其操作具有原子性。

原子性

所谓原子性,与数据库事务特性的原子性一个意思,就是要么干就干完,不干就一点不干,概括的说就是一个动作无法再分割。
这里 非原子性 或者说 由于非原子性又因为不恰当的执行时序 会引发两个问题。

  • 检查-执行问题:`
 getInstance(){
  		if(instance==null){
   			instance==new Instance();
   		}
   		return instance;
   }
  • 读取-修改-写入问题:
a++;

这俩问题都可以通过使用原子型操作解决,比如第一个问题,如果判断与执行操作是原子性的那么就不会出现重复建立对象的情况了,第二个问题也同理。
用原子操作实现个类似单例模式演示下吧(其实是CAS啦)

AtomicReference<Main> m = new AtomicReference<>();
    int id_ = id;
    static int id;
    {
        id++;
    }
    public  Main getInstance(){
        for(;;) {
            if(m.get()!=null){
                return m.get();
            }
            if(m.compareAndSet(null, new Main())){
                return m.get();
            }
        }
    }

虽然得到的都是那创建的第一个实例。不过…总共创建的实例可不止1个(具体几个不可控),所以写单例还是老实的用锁吧。
刚才看到一篇关于单例模式有趣的文章。
不一样的单例模式——作者:陈丰尧

加锁机制

如果有多个相关变量的情况下,使用原子性(CAS)进行线程同步是相当困难的。这时候我们就该加锁啦。

内置锁

每一个引用类型的实例都可以被当成锁,且只能被一个线程独占 (互斥锁),所以该线程内的所有操作都可以当作是原子的。

//两次检查机制(为了效率)和volatile(为了安全性,每次
//都从主内存求读)保证了懒汉式单例模式的效率和安全性
volatile Main m ;
    public  Main getInstance(){
        if(m==null){
            synchronized (this) {
                if (m == null) {
                    m = new Main();
                }
            }
        }
        return m;
    }

重入

简单的说就是:持有了某个锁的线程能否再进入被该锁保护的代码块。可重入锁的粒度为线程级别而不是调用级别。
重入的实现方法为,将锁与获取计数器与一个所有者线程相关联,若计数器为0时,则它可以被任何线程获取,被获取后,计数器递增,若再一次持有该锁,则再递增,只有退出同步代码块时才相应递减。数值为0时,锁被释放。(比如父类某方法有锁,子类某方法也有锁还需要调用父类方法时,这时可重入锁的作用就体现出来了,否则就是死锁)

性能、锁状态

这两个是互相制衡的,如果要保障性能,锁应该尽量的少,或者说锁的范围尽量的小,而锁状态意味着,不是说锁是操作是原子性的,它就是线程安全的了,得保证修改某状态时,整个一系列的操作都得是同步的。

发布了18 篇原创文章 · 获赞 1 · 访问量 282

猜你喜欢

转载自blog.csdn.net/qq_38732834/article/details/105164808