JAVA-线程-CAS

35 篇文章 0 订阅
15 篇文章 0 订阅

线程扩展

常用专业术语:CAS(乐观锁的实现),ABA,

乐观锁:

认为一般情况下不会发生并发冲突,只有在进行数据更新的时候,才会检测并发冲突,若无冲突则直接修改,有冲突返回失败

java的乐观锁采用CAS机制实现。

CAS机制

java的CAS利用unsafe实现的,依赖的是jvm针对不同操作系统的Atomic::cmpxchg原子性指令,使用了汇编的CAS操作,并使用cpu硬件提供的lock机制保证其原子性。

CAS机制-Compare and swap,比较并交换,
操作:假设内存中原数据为v,旧的预期值A,需要修改的新值B,比较A和v是否相等, 如果相等,将B写入v–交换,返回是否成功,

CAS的缺点:ABA问题,新值和预期旧值的问题,
ABA问题:当前数据v为100,经过线程A的操作之后编程了0;此时又有线程C进行操作变成了100,而线程B需要对线程A操作之前的100进行操作,但其执行排在线程C之后了,对C之后的100进行了操作,此时出现了数据出现了错误的操作。
代码操作如下:

import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;


//ABA问题
public class Demo_0527_2 {
    private static AtomicReference money = new AtomicReference(100);
//    private  static AtomicStampedReference money = new AtomicStampedReference(100,0);
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                    boolean result = money.compareAndSet(100,0);
                System.out.println("第一次转账"+result);
            }
        });
        t1.start();
        t1.join();
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
               boolean result =  money.compareAndSet(0,100);
                System.out.println("入账100"+result);
            }
        });
        t3.start();


        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {


                boolean result = money.compareAndSet(100,0);
                System.out.println("第二次转账"+result);
            }
        });
        t2.start();
    }
}

上述代码模拟转账的情况,第一次线程1转账太慢,发起了线程2进行转账,而中间又因为线程3转入了100,在线程1和3执行完成之后,线程2本不应该继续执行,但CAS机制只识别当前的值与要操作的值是否相等,实际上已经不是之前的值,因此出现了重复转账。

执行结果:
第一次转账true
入账100true
第二次转账true

ABA解决方法

ABA主要由于对比值相等进行改变,不能区分数据状态,只需要引入版本号即可,每次操作完成后,版本号+1,这时就可以根据版本号判别当前数据是否被修改过。

AtomicStampedReference(初始值,版本号);解决ABA问题;

import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;


//ABA问题
public class Demo_0527_2 {
//    private static AtomicReference money = new AtomicReference(100);
    private  static AtomicStampedReference money = new AtomicStampedReference(100,0);
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                    boolean result = money.compareAndSet(100,0,0,1);
                System.out.println("第一次转账"+result);
            }
        });
        t1.start();
        t1.join();
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
               boolean result =  money.compareAndSet(0,100,1,2);
                System.out.println("入账100"+result);
            }
        });
        t3.start();


        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {


                boolean result = money.compareAndSet(100,0,1,2);
                System.out.println("第二次转账"+result);
            }
        });
        t2.start();
    }
}

执行结果
第一次转账true
入账100true
第二次转账false

如果将初始值设为1000,执行结果为
第一次转账false
入账100false
第二次转账false

** 原因** Integer的高速缓存-128-127;进行compare的时候,对比的是引用,而不是值,只有一个缓存对象,超过范围就会自动创建新的变量,导致对像改变。
调整Integer高速缓存的边界值,将vm option 修改为Djava.lang.high=1000;

悲观锁

认为一定会出现并发冲突,因此在进入代码后就进行加锁,
synchronized属于悲观锁,

共享锁-非共享锁

共享锁指的是一个锁可以被多个程序拥有,非共享锁只能被一个线程拥有;
synchronized是非共享的

读写锁

属于共享锁,就是将锁分为两部分,读锁和写锁,读锁可以被多个线程拥有,而写锁只能被一个线程拥有,ReenttrantReaderLocker;
读写锁的优点是锁的粒度更高,性能更高。
读写锁中的读锁和写锁是互斥的;防止在读写同时进行造成脏数据的问题,

import java.util.Date;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;


//读写锁
public class Demo_0527_3 {
    public static void main(String[] args) {
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);//true表示公平和非公平;默认非公平
        ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
        ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10,10, 10,TimeUnit.SECONDS,new LinkedBlockingDeque<>(1000));
        executor.execute(new Runnable() {
            @Override
            public void run() {
                readLock.lock();
                try{
                    System.out.println(Thread.currentThread().getName()+"读锁"+new Date());
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    readLock.unlock();
                }
            }
        });
        executor.execute(new Runnable() {
            @Override
            public void run() {
                readLock.lock();
                try{
                    System.out.println(Thread.currentThread().getName()+"读锁"+new Date());
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    readLock.unlock();
                }
            }
        });
        executor.execute(new Runnable() {
            @Override
            public void run() {
                writeLock.lock();
                try{
                    System.out.println("线程名:"+Thread.currentThread().getName()+"写锁"+new Date());
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    writeLock.unlock();
                }
            }
        });
        executor.execute(new Runnable() {
            @Override
            public void run() {
                writeLock.lock();
                try{
                    System.out.println("线程名:"+Thread.currentThread().getName()+"写锁"+new Date());
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    writeLock.unlock();
                }
            }
        });
    }
}

公平锁

锁的获取顺序必须和线程方法的先后顺序保持一致,
优点:执行有序,结果可以预期
非公平:锁的获取顺序和线程的先后顺序无关,–默认锁策略
优点:性能高,
公平锁的创建:new ReentrantLock(true);
非公平:
synchronized new ReentrantLock(false);

自旋锁

通过死循环一直尝试获取锁;
缺点:线程在枪锁失败后进入阻塞,只要没抢到锁,就进行死等,如果锁没有很快被释放,线程就会一直消耗CPU的资源

while(true){
    if(获得锁)return}

可重入锁

允许同一个线程多次获得同一把锁;

public class Demo_0527_4 {
    private static Object lock = new Object();
    public static void main(String[] args) {
        synchronized (lock){
            System.out.println("第一次进入锁");
            synchronized (lock){
                System.out.println("第二次进入");
            }
        }
    }
}

面试题:

怎么理解乐观悲观锁?怎么实现?
答:1.乐观锁->CAS->Atomic*,CAS有v内存,A预期值,B准备写入的新值,
使用A和v对比,如果结果为true,表示无并发冲突,可以直接修改v为B,否则不修改,
java通过调用C++实现的unsafe中的本地方法Compare And Swap,C++通过调用操作系统中的Atomic中的原子指令,
2.悲观锁:synchronized在java中是将锁的ID放到对象头中实现的,在JVM是利用monitor实现,在操作系统中是通过互斥锁mutex实现的,

synchronized锁优化:
JDk1.6锁升级的过程,1.无锁,2.偏向锁,3.轻量级锁。4重量级锁,

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值