java内存模型
上图就是我们的java内存模型
public class Demo10 {
private int x=0;
private int count(){
return x++;
}
}
上面是我们很一般的代码,在多线程中执行count()会有什么问题
发现x的值被覆盖了,为什么会出现这样的呢
上面三个步骤不是原子性的,当多个线程同时执行,有可能线程1在步骤1和步骤2之间另外一个线程2执行步骤1,此时线程2再次执行步骤2执行x+1,此时线程1也开始执行x+1,同时执行就会导致数据被覆盖的结果
我们再来看一个例子
public class test {
private int value = 0;
public void set(int value){
this.value = value;
}
public int get(){
return this.value;
}
}
上面代码在多线程中执行,有什么问题呢
发现当线程1已经更新的value的值,但是线程2获取的到值还是value=0.这就是可见性问题
我们再看看如下代码,有什么问题
public class Singleton {
private static Singleton instance = null;
public Singleton() {
}
private static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
上面代码是一个经典的懒汉+同步操作的单例模式,但是他是还存在那些问题呢,首先我们要知道创建一个实例对象并不是一步就可以创建的,而是按照下面步骤执行的
instance = new Singleton();
但是由于编译器进行指令重排序可能是1->3->2的顺序,就会导致实际上对象还没有初始化完成,而对象的属性是空,最终可能导致空指针,这个就是有序性问题
上面都是多线程中一起的并发问题,那我们如何解决呢
局部变量
局部变量由于仅存在线程中的内存中,不会出现共享变量的情况
ThradLocal
每个线程都有一个integet对象,虽然每个线程都会从主内存中把integer对象拷贝到工作内存中,但是线程1和线程2复制的对象并不是同一个对象,每个对象都会只会被一个线程操作,所以不存在共享变量,也就不会存在并发问题
不可变类
所谓的不可变对象是指已经常见,就对外的状态就不会改变的对象,如果一个对象的状态是恒古不变的,那么自然就不存在并发问题,因为对象是不可变的,所以无论多少个线程,对他做什么操作,他都是不变的,
CAS类
CAS意思就是Compare And Swap,比较并置换,CAS使用了3个基本操作数,内存地址V,旧的预期值A,要更新的新值B,只有当内存地址的对应的值和旧的预期值一样就会用新值B,才会将内存地址V对应的值更新为新的值B,否则循环操作,知道成功
我们知道JUC里面的AtomicInteger就是使用CAS思想,在AtomicInterger可以保证多线程下线程安全是依赖一Unsafe的实例,Unsage类提供硬件级别的原子操作,因为java是无法直接访问操作系统底层硬件,为此java使用native方法来扩展这部分功能,其中UnSafe类就是一个操作入口,UnSafe提供几种功能,其中包括分配和释放内存,挂起和恢复线程,定位对象字段内存地址,修改对象的字段值,CAS操作。
Synchronized和Lock
他们是进行加锁,是悲观锁的策略,在多线程中只有一个线程可以得到执行,
首先是两个线程抢占锁,但是线程1,抢到了锁,而线程2就会进入队列,当线程1执行完之后,通知线程2,你可以尝试抢占锁了,线程2就会尝试抢占锁,抢到之后才会执行代码