对于非原子变量(count++),多个线程同时操作它的值的时候,如果不想每次结果不一致,可以使用加锁例如:synchronized(obj){ //被操作变量 };
对于原子变量(例如int a = 0),多个线程同时操作它的值的时候,如果不想每次线程使用时值不一致,可以使用volatile修饰,这样当一个线程修改了值存进了主内存,另一个线程在使用期间会将正在使用的值进行失效处理,再重新在主内存中拿新的值使用,就是有一个总线嗅探机制。
任意的对象都能成为锁:锁有两种状态
1、有锁状态:
2、无锁状态:
锁演变流程及不同应对方案:
方案一:一个线程碰到synchronized之后对象变成偏向锁,直接标记在对象的markword上;
锁的三种方式:
public class Person{
//充当一把锁
private Object lock = new Object();
//也可以这样
private byte[] b = new byte[0];//表示在内存中开辟一个单位的字节+128位的头数据
public void a(){
synchronized(lock){
//do something
}
//或者
synchronized(b){
//do something
}
}
}
2.锁住当前this(this也是同下面——不要直接这样做,这样做是将整个class作为锁对象,会存在内存泄露的风险)
public class Person{
public void a(){
synchronized(this){
//do something
}
}
}
3.直接锁非静态方法(不要直接这样做,这样做是将整个class作为锁对象,会存在内存泄露的风险)
public synchronized void a(){
}
注:使用对象锁的情况,只有使用同一实例的线程才会受锁的影响,多个实例调用同一方法也不会受影响。
方案二:二个线程碰到synchronized之后作为对象变成轻量级锁,数据记录在栈帧内;
方案三:三个线程来到就使用monitor,里面三个线程排队,每个线程在使用的时候就到owner区域;
公平锁是monitor有的说法,在排队的过程中线程依次进入Owner中运行,不会有插队的情况就是公平锁;
非公平锁就是线程在entrylist列表中都可以抢占Owner,进行运行;
无锁方案
方案四:CAS方案(也用于多线程共享变量)
CAS必须和volatile配合使用,保证多线程安全性同时保证可见性;
可以使用JUC中AQS来实现CAS(CompareAndSet)方案:
使用案例:
//CAS无锁状态
ReentrantLock lock = new ReentranLock();//源码中lock的使用就是 值.CompareAndSet(当前值,预期值,到达预期值就跳出结束,否则一直循环)
lock.lock();
//要用的变量
lock.unlock();
//使用原子操作Atomic
static int i = 0;
static AtomicInteger inte = new AtomicInteger();
public static void main(String[] args) {
final Object object = new Object();
Thread t1 = new Thread(new Runnable() {
@Override public void run() {
for (int j = 0; j < 10; j++) {
/** * 两者等同效果 */
synchronized (object) { i++; }
//-----------------------
inte.incrementAndGet();
}
}
});
注:这里涉及到锁膨胀的问题,锁膨胀就是根据线程开辟数量升级不同的应对方法;
综上所述:
java锁不是唯一处理线程互斥的方案;
也可以使用CAS方案解决;
CAS和Synchronized的区别:
无锁状态下,CAS会一直高速旋转没有停歇,而Synchronized会在线程没有抢到锁的时候进入阻塞状态,发生上下文切换;
由此可以知道CAS中线程一直在跑就需要CPU的支持,所以在使用CAS时线程的数量和CPU对应的话,效率最高。假设有10000个线程,会出现线程上下文切换(换CPU运行线程),切换的话会导致消耗,效率很低。
CAS原子变量:Atomic包
对于单个数据的多线程调用情况下,提供了一套Atomic包——原子操作
* 所有的原子操作,实际是在顶层进行了一个CAS的循环比较,只有达成目的才会退出;
* 在并发场景中大部分是某个属性的数据安全处理,所以搞了一组Atomic包,利用CAS不加锁的方式处理,在当前线程不多,核数合适的情况下,这种方案优于synchronized;
原子变量分为:
基本数据类型
引用类型:AtomicRefrence处理的是引用类型的值-->地址
原子数组
对象
compareAndSet:比较并且设值
* 当前数值比较如果相等就设入新的值
* 如果不等就一直自旋
CAS的ABA问题
* ABA指的是:线程1更改值将A修改成B, 线程2再次将值B修改为A ,使用AtomicString就无法知道是否有修改过值,这就是ABA问题;
* CAS引起的ABA问题,可以用AtomicStampedReference和AtomicMarkableReference去解决,可以知道是否有修改值
CAS(Compare And Set 比较并且替换)机制中使用了三个基本操作数:
- 内存地址V
- 旧的预期值A
- 要修改的值B
只有当内存地址V中实际的值与旧的预期值A相等的时候,才能将内存地址对应的值修改为B;
下面Atomic相关使用示例:
/**
* Created by:zx
* on 11/2/21
* 对于单个数据的多线程调用情况下,提供了一套Atomic包——原子操作
* 所有的原子操作,实际是在顶层进行了一个CAS的循环比较,只有达成目的才会退出;
* 在并发场景中大部分是某个属性的数据安全处理,所以搞了一组Atomic包,利用CAS不加锁的方式处理,在当前线程不多,核数合适的情况下,这种方案优于synchronized
* <p>
* <p>
* compareAndSet:比较并且设值
* 当前数值比较如果相等就设入新的值
* 如果不等就一直自旋
* <p>
* CAS引起的ABA问题,可以用AtomicStampedReference和AtomicMarkableReference去解决,可以知道是否有修改值
* ABA指的是:线程1更改值为A->B 线程2再次更改值为B->A 使用AtomicString就无法知道是否有修改过值,这就是ABA问题
**/
public class Atomic {
static int i = 0;
static AtomicInteger inte = new AtomicInteger(0);
static AtomicReference<String> atomicReference = new AtomicReference<>("A");
/**
* 如果数据在使用过程中从A变成了B,然后另外一个线程将值从B又变回了A,这时在业务角度来看是没有变动的,
* 但是内部运行是有所变动的,进行了两次赋值,这个时候如果你想知道这个值是不是有变动的情况,就提供了下面这个版本记录,
* 就可以知道A变成B,B在变成A是否是经过这个步骤才回到A还是说一直没有变化
*/
//版本记录参数1:当前值,参数2:版本号,改一次就会加一 能追溯轨迹,当前值更新了第几个版本了
static AtomicStampedReference<String> atomicStampedReference = new AtomicStampedReference<>("A", 0);
//这个表示我只知道是否更改过这个值,如果改过就是true,没有改过就是false
static AtomicMarkableReference<String> atomicMarkableReference = new AtomicMarkableReference<>("A", false);
public static void main(String[] args) {
//对象中某个属性将它保持原子操作
/**
* 参数一:某个model类
* 参数二:当前这个属性的所属类型
* 参数三:要修改的这个值的key
*/
PersonBean bean = new PersonBean();
AtomicReferenceFieldUpdater atomicReferenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(PersonBean.class, String.class, "name");
/**
* 参数一:bean类
* 参数二:对它预期的值
* 参数三:要将它的值修改成这个结果
*/
atomicReferenceFieldUpdater.compareAndSet(bean, null, "赵欣");
System.out.println("AtomicReferenceFieldUpdater:"+ bean.toString());
//打印:
// AtomicReferenceFieldUpdater:PersonBean{name='赵欣', age='null'}
final Object object = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
/**
* 两者等同效果
*/
synchronized (object) {
i++;
}
//AtomicInteger
inte.incrementAndGet();
//atomicStampedReference
String ast = atomicStampedReference.getReference();
System.out.println("atomicStampedReference1 : " + atomicStampedReference.compareAndSet(ast, "B" + j, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("atomicStampedReference新值1:" + atomicStampedReference.getStamp() + " " + atomicStampedReference.getReference());
}
//AtomicRefrence
String aR = atomicReference.get();
System.out.println("atomicReference1 : " + atomicReference.compareAndSet(aR, "D"));
System.out.println("atomicReference新值1:" + atomicReference.get());
//atomicMarkableReference
String am = atomicMarkableReference.getReference();
System.out.println("atomicMarkableReference1:" + atomicMarkableReference.compareAndSet(am, "E", atomicMarkableReference.isMarked(), true));
System.out.println("atomicMarkableReference1值:" + atomicMarkableReference.isMarked() + " " + atomicMarkableReference.getReference());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
/**
* 两者等同效果
*/
synchronized (object) {
i++;
}
//AtomicInteger
inte.incrementAndGet();
}
//AtomicRefrence
String aR = atomicReference.get();
System.out.println("atomicReference2 : " + atomicReference.compareAndSet(aR, "C"));
System.out.println("atomicReference新值2:" + atomicReference.get());
// //atomicStampedReference
String ast = atomicStampedReference.getReference();
System.out.println("atomicStampedReference2 : " + atomicStampedReference.compareAndSet(ast, "A", atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("atomicStampedReference新值2:" + atomicStampedReference.getStamp() + " " + atomicStampedReference.getReference());
//atomicMarkableReference
String am = atomicMarkableReference.getReference();
System.out.println("atomicMarkableReference2:" + atomicMarkableReference.compareAndSet(am, "A", atomicMarkableReference.isMarked(), true));
System.out.println("atomicMarkableReference2值:" + atomicMarkableReference.isMarked() + " " + atomicMarkableReference.getReference());
}
});
/**
* 打印:每次运行两个线程互相修改,值一致
* atomicReference2 : false
* atomicReference新值2:D
* atomicReference1 : true
* atomicReference新值1:D
*
* atomicReference2 : false
* atomicReference1 : true
* atomicReference新值2:D
* atomicReference新值1:D
*
* atomicReference1 : true
* atomicReference2 : true
* atomicReference新值1:C
* atomicReference新值2:C
*
* atomicReference1 : true
* atomicReference2 : false
* atomicReference新值2:D
* atomicReference新值1:D
*/
/**
* 打印:atomicStampedReference 获取当前的版本,然后自加1,最终获得的修改的值就是最终修改过的多少次
*
* atomicStampedReference1 : true
* atomicStampedReference2 : true
* atomicStampedReference新值1:2 A
* atomicStampedReference新值2:2 A
* atomicStampedReference1 : true
* atomicStampedReference新值1:3 B1
* atomicStampedReference1 : true
* atomicStampedReference新值1:4 B2
* atomicStampedReference1 : true
* atomicStampedReference新值1:5 B3
* atomicStampedReference1 : true
* atomicStampedReference新值1:6 B4
* atomicStampedReference1 : true
* atomicStampedReference新值1:7 B5
* atomicStampedReference1 : true
* atomicStampedReference新值1:8 B6
* atomicStampedReference1 : true
* atomicStampedReference新值1:9 B7
* atomicStampedReference1 : true
* atomicStampedReference新值1:10 B8
* atomicStampedReference1 : true
* atomicStampedReference新值1:11 B9
* atomicStampedReference新值00000:11 B9
*/
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//atomicStampedReference
System.out.println("atomicStampedReference新值00000:" + atomicStampedReference.getStamp() + " " + atomicStampedReference.getReference());
//atomicMarkableReference
System.out.println("atomicMarkableReference00000值:" + atomicMarkableReference.isMarked() + " " + atomicMarkableReference.getReference());
//AtomicInteger
//compareAndSet使用,基准是inte的get原本的值,只有get获得当前的值,update需要修改的值,如果两者相同就退出if
int ai = inte.get();
System.out.println("ai: " + ai);
if (inte.compareAndSet(ai, 3)) {
System.out.println(3);
}
System.out.println(i + " " + inte);
}
}
总结:
并发的手段两种:
- synchronized(悲观体现:表示我上锁了,别的线程无法修改,只有我释放锁了其他线程才能修改)
- 无锁CAS(乐观体现:乐观锁就是没加锁,只考虑方案的实现,其他线程修改了也没事,使用了CAS会一直重试获取结果;CAS是无锁并发,无阻塞)
网友评论