多线程与高并发 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 什么时候调用什么时候返回这个值 )