单例模式懒汉式和饿汉式、多线程、volatile、synchronized笔记

in 日常随笔 with 0 comment 访问: 83 次

单例模式

属于创建型模式的一种,应用于保证一个类仅有一个实例的场景下,并且提供了一个访问它的全局访问点。

单例模式三要素

  1. 私有的静态属性,这主要是为了存储类唯一的实例。
  2. 公共的静态方法,这主要是为了提供给外键生成获取单例的方法。
  3. 用于限制类再次实例话的措施。一般会私有化类的构造方法。

单例模式-懒汉式

默认不会实例化,什么时候用什么时候new。

public class Lazy {
    //默认不会实例化,什么时候用什么时候new
    private static Lazy lazy = null;

    private Lazy() {
    }

    public static synchronized Lazy getInstance() {
        if (lazy == null) {
            lazy = new Lazy();
        }
        return lazy;
    }
}

单例模式-饿汉式

类加载的时候就实例化,并且创建单例对象。

public class Hungry {
    // 类加载的时候就实例化,并且创建单例对象
    private static final Hungry hungry = new Hungry();

    private Hungry() {
    }

    public static Hungry getInstance() {
        return hungry;
    }
}

懒汉式优缺点

多线程

内存模型与CPU缓存

本来CPU计算的数字都是从主从main memory中读取的,但是CPU运行的速度比计算机读取内存的速度快,为了补齐这个短板,所以出现了CPU缓从这种东西。

在多CPU系统(或多核处理器——一个芯片上有多个CPU),每个CPU有自己的缓存。两个线程A,B在不同的CPU上同时跑,A对主存的某个共享变量修改后
会暂时存在CPU a的缓存中。线程B在CPU b上跑,B仍旧是从主存中读取该共享变量,此时B读到的就是旧值了。就出现了数据的不一致性。

这里出现不一致的条件:必须是多个线程并且访问共享变量,而不是普通变量。

为了解决这个问题,有两种方式:

在总线上加LOCK#锁;
使用缓从一致性协议,比如MESI协议。

并发环境下的可见性、原子性、有序性

JAVA原子性适用于除了long和double的原始数据类型的“简单操作”。从内存中读写除了long和double的原始数据类型是原子操作的。


简单操作指的是,简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)

i++            # java中不是原子的
i = i+1        # java中不是原子的
i = j          # java中不是原子的
i = 1          # java中是原子的

上面四个语句只有最后一个是原子的。这里注意:i=j,虽然不是原子的(有两步:先从内从读取 j 的值,再将 j 的值写入内从),但是这里的每一步是原
子的(这两步都是简单的读写操作)。i++在C++里可能就是原子的,这个与C++本身的内存模型有关。

在32位上,对64bit的long和double变量的读写是分成两个32bit读写的,因此上下文切换可能发生在读(或写)进行到一半时,这叫做word tearing。

用violate定义long或者double变量时, 对 “简单的” 负值和return操作能保证原子性。

不同的JVM提供了对原子性不同程度的保证。。。
Java中原子性保证:Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。
【由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块】,那么自然就不存在原子性问题了,从而保证了原子性。

Java中可见性保证:synchronized和Lock、volatile三种。推荐synchronized方式,volatile有局限性,适合某个特定场合。

volatile关键字 (易变的,不稳定的,易挥发的)

https://www.cnblogs.com/blackmlik/p/12911252.html

volatile 局限性:

可见性和原子性是不同的两个概念。对一个非volatile变量的原子操作的结果不会立即刷新到主从。多线程环境下对某个共享变量访问时,要么把这个变量
定义成volatile的,要么使用synchronization,这样就保证了该变量的可见性。但如果该变量的操作是在synchronized方法或代码块中的话,就不用
将该变量定义为volatile了,因为synchronization同步机制会强迫刷新到内存,强迫一个线程对共享变量做的改变对整个应用可见。


当一个字段的值依赖于它之前的值时(比如计数器的递增),volatile不起作用,它也不起作用于那些值受其他字段值约束的字段,比如Range类的下
界和上界,它必须遵守lower <= upper的约束。

通常,只有当类只有一个可变字段时,使用volatile而不是synchronized才是安全的。同样,您的首选应该是使用synchronized关键字,这是最安全
的方法,尝试其他任何操作都是有风险的。

如果将变量定义为volatile,它会告诉编译器不要进行任何优化,以免删除那些使字段与线程中的本地数据保持精确同步的读写操作。实际上,读取和
写入直接进入内存,而不被缓存,volatile还限制了优化期间编译器对访问的重新排序。但是,volatile并不影响增量不是原子操作这一事实。

基本上,如果一个字段可以被多个任务同时访问,并且至少有一次访问是写操作,那么就应该将该字段设置为volatile。例如,用作停止任务标志的
字段必须声明为volatile。
赞赏支持
Responses