【多线程与高并发 2】volatile 篇

多线程与高并发 2




volatile 篇


偏向锁 -> 循环锁 -> 重量级锁

synchronized(只能使用Object)

当线程≥2时,自旋锁;当自旋次数>10时,重量级锁。


Lock( )CAS使用自旋

synchronized( )使用锁升级


volatile // 可变的,易变的

保证线程可见性,禁止指令重排序。

保证线程可见性:一个类的值给两个类同时调用,里面的变量改变后无法轻易发现(线程之间不可见)。

volatile可以让一个线程发生改变之后,另一个线程可以马上知道。

// 原理:CPU的缓存一致性协议。

禁止指令重排序:CPU迸发执行指令,所以会对指令重新排序,加了volatile来保证重排序。

单例:保证在JVM内存永远只有某一个类的一个实例(比如:权限管理者)。


  • 饿汉式:(定义类的时候就实例化方法)
public class Mgr01{
  private static final Mgr01 INSTANCE = new Mgr01();
  private Mgr01(){};
  public static Mgr01 getInstance(){return INSTANCE;}
  public void m() {System.out.println("m");}
  public static void main(String[] args){
    Mgr01 m1 = Mgr01.getInstance();
    Mgr01 m2 = Mgr01.getInstance();
    System.out.println(m1 == m2);
  }
}
  • 懒汉式:什么时候调用方法什么时候初始化
public class Lazy{
    private Lazy(){}
    //默认不会实例化,什么时候用什么时候new
        private static Lazy lazy=null;
        public static synchronized Lazy getInstance(){
            if(lazy==null){
                lazy=new Lazy();
        	}
        	return lazy;
    	}
}
饿汉式懒汉式
安全
节省内存
  • 懒汉饿汉合并:

类的定义:

public class Mgr01{
  private /*volatile*/ static Mgr0x INSTANCE;
  private Mgr0x(){};
  public static Mgr01 getInstance(){
  	//以下所有代码写的都是这一个方法
  }
}

以下所有方法写的都是上面的 getInstance()方法。

以上方法没有加volatile,最后会写上。


  • 直接判断null
// 先判断是否为空 然后再那啥:
public static Mgr03 getInstance(){
    if(INSTANCE == null){
        try{
            Thread.sleep(1);
        }catch(InterruotedException e){
            e.printStackTrace();
        }
        INSTANCE = new Mgr03();
    }
    return INSTANCE;
}

↑ ↑ ↑ ↑ ↑ 这是一种错误的书写方式,自己抿;

  • 先锁再null
public static synchronized Mgr04 getInstance(){
    if(INSTANCE == null){
        try{
            Thread.sleep(1);
        }catch(InterruotedException e){
            e.printStackTrace();
        }
        INSTANCE = new Mgr04();
    }
    return INSTANCE;
}

↑ ↑ ↑ ↑ ↑ 修改正确,但是违背了 能不加锁就不加锁 原则。

  • 锁细化:
public static Mgr05 getInstance(){
    if(INSTANCE == null){
        synchronized (Mgr05.class){
            try{
                Thread.sleep(1);
            }catch(InterruotedException e){
                e.printStackTrace();
            }
            INSTANCE = new Mgr05();
        }
    }
    return INSTANCE;
}

↑ ↑ ↑ ↑ ↑ 这也是一种错误的书写方式(重复初始化);

  • 双重检查:
public static Mgr05 getInstance(){
    if(INSTANCE == null){
        synchronized (Mgr05.class){
            if(INSTANCE == null){
                try{
                    Thread.sleep(1);
                }catch(InterruotedException e){
                    e.printStackTrace();
                }
                INSTANCE = new Mgr05();
            }
        }
    }
    return INSTANCE;
}

↑ ↑ ↑ ↑ ↑ 修改正确…而且锁不加载外面,效率增高~~

  • 关于volatile(主要是 指令重排序 )超高超高迸发的情况可能会发生:
// new对象的三步
INSTANCE = new Mgr06();

1. 申请内存并给初始值(int = 0,String = null;)
2. 修改值
3. 将值给对象

volatile 防止第二步第三步会颠倒;

  • 一个求结果是 100000 的小程序
public class T{
    volatile int count = 0; // 加上vilatile
    synchronized void m(){  // 加上 synchronized
        for(int i=0;i<10000;i++){count++;}
    }


    public static void main(String[] args){
        T t = new T();
        List<Thread> threads = new ArraysList<~>();

        for(int i=0;i<10;i++){
            threads.add(new Thread(t::m,"threads-"+i));
        }

        threads.forEach((o)->o.start());

        threads.forEach((o)->{
          try{
              o.join();
          } catch(InterruptedException e){
              e.printStackTrace();
          }
        });
            System.out.println(t.count);
    }
}

只有加上了 synchronized & volatile 才能运行出正确结果,其中 synchronized 用来保证原子性





锁优化

  • 锁力度变小(争用不是很激烈的话)

如果有一群要争用的代码,那么可以将方法上的 synchronized 写到 count++ 上;

  • 锁力度变大(争用很激烈很频繁的话)

假如一个方法里面 总共 20 行代码,加了19行,那不如直接用一个大的锁。





锁的对象被调用

public class = T{
    Object o = new Object();// 错误修改点
    
    synchronized(0){
        sout("123");
    }

    public void zbc(){
        T t = new T();
        t.o = "a";
    }

↑ ↑ ↑ 以上代码错误!以下为修改 ↓ ↓ ↓

final Object o = new Object();




有些类在创建的时候直接加了锁

Atomic 开头的 ( AtomicInteger count = new AtomicInteger( ); // 让count进行原子性加减)





CAS ( Compare And Set ) 无锁优化 乐观锁

在请求的时候就乐观的认为 代码里的值就是我的期望值


cas (V ,Expected,NewValue){
    if (V == Expected){
        V = NewValue;
	}else{
        tryAgain or fail;
    }
}

↑ ↑ ↑ 以上是在CPU 原语上的支持,不能被打断。





ABA 问题(与前女友复合之后,其实她已经经历了n个男人;)

有个对象 object == 1;想使用cas把它变成2:

cas(object,1,2);//没有线程进行操作,可以进行更改

如果在更改的时候有一个线程给 object 改成了2,然后又改成了 1 ;在基础类型(如:int)没有影响,但是 Object 对象有影响;

解决方法:做 cas 的时候加个版本号:version

解决方法:使用 AutomicStampedReference ( unsafe 什么时候调用什么时候返回这个值 )


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值