⾼并发问题与解决⽅案
乐观锁
( Optimistic Locking ) 相对悲观锁⽽⾔,乐观锁机制采取了更加宽松的加锁机制。悲观锁⼤多数情况下依靠数据库的锁机制实现,以保证操作最⼤程度的独占性。但随之⽽来的就是数据库性能的⼤量开销,特别是对长事务⽽⾔,这样的开销往往⽆法承受。⽽乐观锁机制在⼀定程度上解决了这个问题。乐观锁,⼤多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加⼀个版本标识,在基于数据库表的版本解决⽅案中,⼀般是通过为数据库表增加⼀个 “version” 字段来实现。读取出数据时,将此版本号⼀同读出,之后更新时,对此版本号加⼀。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进⾏⽐对,如果提交的数据版本号⼤于数据库表当前版本号,则予以更新,否则认为是过期数据。
version⽅式:⼀般是在数据表中加上⼀个数据版本号version字段,表⽰数据被修改的次数,当数据被修改时,version值会加⼀。
当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
CAS操作⽅式:即compare and swap 或者 compare and set,涉及到三个操作数,数据所在的内存值,预期值,新值。当需要更新时,判断当前内存值与之前取到的值是否相等,若相等,则⽤新值更新,若失败则重试,⼀般情况下是⼀个⾃旋操作,即不断的重试
悲观锁
(Pessimistic Lock),正如其名,具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来⾃外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也⽆法保证外部系统不会修改数据)。
基本名词解释
1.原⼦性
原⼦性是指⼀个操作是不可中断的,要么全部执⾏成功要么全部执⾏失败,有着“同⽣共死”的感觉。
在多个线程⼀起执⾏的时候,⼀个操作⼀旦开始,就不会被其他线程所⼲扰。
java内存模型中定义了8中操作都是原⼦的,不可再分的。
lock(锁定):作⽤于主内存中的变量,它把⼀个变量标识为⼀个线程独占的状态;
unlock(解锁):作⽤于主内存中的变量,它把⼀个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
read(读取):作⽤于主内存的变量,它把⼀个变量的值从主内存传输到线程的⼯作内存中,以便后⾯的load动作使⽤;
load(载⼊):作⽤于⼯作内存中的变量,它把read操作从主内存中得到的变量值放⼊⼯作内存中的变量副本
use(使⽤):作⽤于⼯作内存中的变量,它把⼯作内存中⼀个变量的值传递给执⾏引擎,每当虚拟机遇到⼀个需要使⽤到变量的值的字节码指令时将会执⾏这个操作;
assign(赋值):作⽤于⼯作内存中的变量,它把⼀个从执⾏引擎接收到的值赋给⼯作内存的变量,每当虚拟机遇到⼀个给变量赋值的字节码指令时执⾏这个操作;
store(存储):作⽤于⼯作内存的变量,它把⼯作内存中⼀个变量的值传送给主内存中以便随后的write操作使⽤;
write(操作):作⽤于主内存的变量,它把store操作从⼯作内存中得到的变量的值放⼊主内存的变量中。
2.有序性
如果在本线程内观察,所有的操作都是有序的;如果在⼀个线程观察另⼀个线程,所有的操作都是⽆序的
(即在多线程环境中由于线程竞争抢占可能导致原本单线程的顺序多线程出现顺序错乱的问题)
如下⾯的单例模式的实现上有⼀种双重检验锁定的⽅式
public class Singleton {
private Singleton() { }
private volatile static Singleton instance;
public Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class){
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
如果2和3进⾏了重排序的话,线程B进⾏判断if(instance==null)时就会为true,⽽实际上这个instance并没有初始化成功,显⽽易见对线程B来说之后的操作就会是错得。⽽⽤volatile修饰的话就可以禁⽌2和3操作重排序,从⽽避免这种情况
3.可见性
可见性是指当⼀个线程修改了共享变量后,其他线程能够⽴即得知这个修改。通过之前对synchronzed
内存语义进⾏了分析,当线程获取锁时会从主内存中获取共享变量的最新值,释放锁的时候会将共享变量同步到主内存中。
4.synchronized: 具有原⼦性,有序性和可见性; synchronized对应的锁是互斥锁(悲观锁)
volatile:具有有序性和可见性 volatile包含禁⽌指令重排序的语义,其具有有序性。
JAVA内存模型
1.线程共享内存模型
2.JVM下的JAVA内存模型提交更改是内存条吗
JVM中运⾏的每个线程都拥有⾃⼰的线程栈,线程栈包含了当前线程执⾏的⽅法调⽤相关信息,我们也把它称作调⽤栈。随着代码的不断执⾏,调⽤栈会不断变化。
线程栈还包含了当前⽅法的所有本地变量信息。⼀个线程只能读取⾃⼰的线程栈,也就是说,线程中的本地变量对其它线程是不可见的。即使两个线程执⾏的是同⼀段代码,它们也会各⾃在⾃⼰的线程栈中创建本地变量,因此,每个线程中的本地变量都会有⾃⼰的版本。
所有原始类型(boolean,byte,short,char,int,long,float,double)的本地变量都直接保存在线程栈当中,对
于它们的值各个线程之间都是独⽴的。对于原始类型的本地变量,⼀个线程可以传递⼀个副本给另⼀个线程,当它们之间是⽆法共享的。
堆区包含了Java应⽤创建的所有对象信息,不管对象是哪个线程创建的,其中的对象包括原始类型的封装类(如Byte、Integer、Long等等)。不管对象是属于⼀个成员变量还是⽅法中的本地变量,它都会被存储在堆区。
⼀个本地变量如果是原始类型,那么它会被完全存储到栈区。
⼀个本地变量也有可能是⼀个对象的引⽤,这种情况下,这个本地引⽤会被存储到栈中,但是对象本⾝仍然存储在堆区。
对于⼀个对象的成员⽅法,这些⽅法中包含本地变量,仍需要存储在栈区,即使它们所属的对象在堆区。
对于⼀个对象的成员变量,不管它是原始类型还是包装类型,都会被存储到堆区。
Static类型的变量以及类本⾝相关信息都会随着类本⾝存储在堆区。
堆中的对象可以被多线程共享。如果⼀个线程获得⼀个对象的应⽤,它便可访问这个对象的成员变量。如果两个线程同时调⽤了同⼀个对象的同⼀个⽅法,那么这两个线程便可同时访问这个对象的成员变量,但是对于本地变量,每个线程都会拷贝⼀份到⾃⼰的线程栈中。
下图展⽰了上⾯描述的过程:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论