• 原子操作和volatile关键字


    原子操作:不可被中断的操作。要么全执行,要么全不执行。

    现代CPU读取内存,通过读取缓存再写入主存。先去主存读--->写入缓存---->运行线程--->写入缓存---->写入主存

    多cpu时会出现缓存一致性和总线锁的问题。

    只有简单的读取,赋值操作,即一步完成的操作才是原子操作。

    volatile,synchronized,lock 能保证可见性,

    volatile保证修改的值立即更新到主存,synchronized和lock保证同一时刻只有一个线程操作变量,在锁被释放前会将新值写入内存。

    线程的有序性:

    java内存模型具备一些先天的有序性,即happensbefore(先行发生)原则

    1,程序次序规则,一个线程内,按照代码书写顺序执行

    2,锁定规则,一个解锁操作ounlock先行发生于后面对同一个锁的lock操作

    3,volatile变量规则,对一个变量的写操作先行发生于后面对这个变量的读操作

    4,传递规则,A先发生于B,B先发生于C,则A先发生于C

    5,线程启动规则,Thread对象的start()方法先行发生于此线程的每一个操作

    6,线程中断规则,对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,即中断的发生先于中断被检测到

    7,线程终结规则,线程中所有的操作都先行发生于线程的中止检测,可以通过Thread.join()方法结束,Thread.isAlive()的返回值手段检测到线程已经终止

    8,对象终结规则,一个对象的初始化完成先行发生于他的finalize()方法的开始

    1·8·来源于《深入理解java虚拟机》

    在单个线程中,虚拟机有可能对指令进行重排序。虽然进行重排序,但是最终执行的结果与程序顺序执行的结果是一致的。它只会对不存在数据依赖性的指令进行重排序。

    在多线程中则能保证执行顺序。

    一旦一个共享变量,比如类的成员变量,类的静态成员变量,被volatile修饰之后,那么它就具备了两层语义:

    1,保证了不同线程对这个变量进行操作时是可见的,即一个线程修改了某个变量的值,这个新值对其他线程来说是立即可见的。

    2,禁止进行指令重排序。

    但是volatile不保证操作的原子性,比如对自增操作等不能保证原子性。

    对自增操作,可以用synchronized和lock保证操作的原子性。也可以用AtomicInteger操作来进行自增。原因是atomic是利用CAS的原理实现原子操作。CAS利用处理器提供的CMPXCHG指令实现,处理器执行CMPXCHG指令是一个原子操作。

    long或double的赋值操作不是原子操作,比如在32位机上对long和double的读写将会分成两步进行。用volatile修饰时,long和double的读写将会变成原子的。

    线程局部(本地)变量是指局限于线程内部的变量,不与其他线程共享。

    java提供ThreadLocal类来支持线程局部变量,它好似一种实现线程安全的方式。

    在web服务器上使用线程局部变量的时候,它的生命周期比任何应用程序的变量的生命周期都要长,所以如果使用完毕后而不释放,救会有内存泄露的风险。

    sleep()只是短暂休眠线程,并不会释放锁。

    wait()指等待某条件。只有释放掉锁,其他等待的线程才能在满足条件时获取到该锁,所以wait()又叫条件等待。

    单例的目的是为了保证运行时Singleton类只有一个实例,因为instance = new Singleton()的性能开销较大。最常用的地方比如获取数据库连接,spring中创建beanFactory等。

    单例模式的七种写法:

    1,懒汉式,线程不安全,在多线程下不能正常工作:

    public class Singleton{

    private static Singleton instance;

    private Singleton(){};

    public static Singleton getInstance(){

    if(instance == null){

    instance = new Singleton ();

    }

    return instance;

    }

    }

    2,懒汉式,线程安全,在多线程下也正常工作,但是效率低下,99%的情况不需要同步

    /***lazy **thread safety*/
    public class Singleton{
    private static Singleton instance;
    private Singleton(){};
    public static synchronized Singleton getInstance(){
    if(instance == null){
    instance = new Singleton();
    }
    return instance;
    }
    }

    3,饿汉式,基于classloader机制避免了线程同步,但是没有lazy loading的效果,在类装载时就实例化

    public class Singleton{
    private static Singleton instance = new Singleton();
    private Singleton(){};
    public static Singleton getInstance(){
    return instance;
    }
    }

    4,饿汉式的变种,也是在类加载时即实例化,没有实现懒加载

    public class Singleton{
    private static Singleton instance = null;
    static{
    instance = new Singleton();
    }
    private Singleton(){};
    public static Singleton getInstance(){
    return instance;
    }
    }

    5,静态内部类,这是一种推荐的写法,懒加载且单线程,只有在显式调用getInstance()的时候SingletonHolder类才被装载

    public class Singleton{
    private static class SingletonHolder{
    private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton(){};
    public static Singleton getInstance(){
    return SingletonHolder.INSTANCE;
    }
    }

    6,枚举,effective java推荐的写法,线程安全的单例模式最推荐的写法

    避免了多线程同步,防止反序列化重新创建新的对象

    因为JVM类初始化是线程安全的,所以可以采用枚举类实现一个线程安全的单例模式

    public enum Singleton{

    INSTANCE;

    public void whateverMethod(){

    }

    }

    7,双重校验锁

    public class Singleton{
    private volatile static Singleton instance;
    private Singleton(){};
    public static Singleton getInstance(){
    if(instance == null){
    synchronized(Singleton.class){
    if(instance == null){
    instance = new Singleton();
    }
    }
    }
    return instance;
    }
    }

    双重校验锁是不安全的,java内存模型允许"无序写入",无序写入将会导致二次检查失败。JDK5以后可以使用双重校验锁,但是不推荐。

    任何时候都不应该使用双重校验锁,因为无法保证它能在任何JVM上都能顺利运行。

    之所以会有双重校验锁,是因为以下代码:

    if(instance == null ){//两个线程在初始化判断的时候同时进入

    synchronized(Singleton.class){//线程2在此时被锁定

    instace = new Singleton();

    }//线程1 执行实例化完毕唤醒线程2,线程2 将不会经过检查,而是直接再次执行实例化动作,造成出错。因为检查这一步已经过了

    retunr instance;

    }

    如果一个单例类由多个不同的类装载器装载,比如有连个servlet访问同一个单例类,则这两个servlet会生成各自的实例。

    如果单例类Singleton实现了serializedble接口,那么这个类的实例就可能被序列化和复原。不管怎么样如果你序列化一个单例类的对象,接下来复原那个对象,你就会有多个单例类的实例。

  • 相关阅读:
    HttpServletRequest和HttpServletResponse实例
    ioc autofac简单示例
    sql left join 字符串
    sqlserver split函数
    类型同时存在于A.dll和B.dll中的解决办法
    ef使用dbfirst方式连接mysql
    html5 图片上传 预览
    webservice使用EF生成的model序列化问题
    sqlserver2008数据库文件降级为sqlserver2005文件
    [转]webapi部署在IIS7.5报404的解决方案
  • 原文地址:https://www.cnblogs.com/tulpen/p/8669017.html
Copyright © 2020-2023  润新知