Java锁策略

目录

乐观锁

悲观锁

公平锁和非公平锁

独占锁和共享锁

可重入锁

自旋锁


乐观锁

它认为一般情况下不会出现问题,所以它在使用的时候不会加锁,只有在数据修改的时候才会判断有没有锁竞争,如果没有就会直接修改数据,如果有则会返还失败信息给用户自行处理

1.乐观锁的经典事项:

CAS(Compare And Swap)对比且替换

CAS 实现:

三个重要内容:V【内存中的值】,A【预期的旧值】,B【新值】

一个 CAS 涉及到以下操作:
  1. 比较 A 与 V 是否相等。(比较)
  2.  如果比较相等,将 B 写入 V。(替换)
  3. 返回操作是否成功。

Java在返回失败之后会加一个自旋锁,也就是死循环(for( ; ; )),会将A刷新为V,一直自旋对比替换,

面试题1:CAS实现的原理是什么?

针对不同的操作系统,JVM 用到了不同的 CAS 实现原理,简单来讲:

  1. java CAS 利用的的是 unsafe 这个类提供的 CAS 操作;Unsafe本地类和Unsafe CompareAndSwapXXX(本地方法),它是C/C++实现的原生方法;
  2. unsafe CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg(原子指令)
  3. Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 cpu 硬件提供的 lock 机制保证其原子性。

UnSafe :

JDK 给Java提供了一种可以操作操作系统手段,通过UnSafe 实现操作系统的原子性操作。

简而言之,是因为硬件予以了支持,软件层面才能做到

CAS在Java中的应用:AtomicInteger / Atomic*

在多线程中实现 i++、i--保证线程安全的方法:

  1. 加锁
  2. ThreadLocal
  3. AtomicInteger

面试题2:乐观锁(CAS)存在什么问题?

答:ABA问题。

例如:A给B转100块钱,不小心点了两次提交按钮

CSA:V=100,A=100,B=0

第一次:V=100 == A=100  -> true -> V=0

第二次:(此时V=0,A=100,B=0)   V == A ->false ->停止运行

结果是正常的。

但是如果假设在第一次和第二次转账中间A得到了100元转账 V->100

就会让第二次(此时V=100,A=100,B=0)   V == A -> true  -> V=0 ---》转账成功

A只给B转账100,但是A损失了200。

这就是ABA问题。

代码1:模拟ABA问题

/*
模拟ABA问题
*/

import java.util.concurrent.atomic.AtomicReference;

public class ThreadDemo {
    private static AtomicReference money = new AtomicReference(100);
    public static void main(String[] args) throws InterruptedException {

        //转账 -100
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("1执行转账");
                boolean rs=money.compareAndSet(100,0);
                System.out.println(rs);
            }
        });
        t1.start();
        t1.join();
        //转账 +100
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("3执行转账");
                boolean rs=money.compareAndSet(0,100);
                System.out.println(rs);
            }
        });
        t3.start();

        t3.join();
        //转账 -100
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("2执行转账");
                boolean rs=money.compareAndSet(100,0);
                System.out.println(rs);
            }
        });
        t2.start();
        t1.join();
        t2.join();

    }
}

运行结果:

面试题:ABA如何处理?

答:加入版本信息,例如携带 AtomicStampedReference 之类的时间戳作为版本信息,保证不会出现老的值。 每次修改的时候判断预期的旧值和版本号,每次修改成功之后更改版本号,这样即使预期的值和V值相等,也不会修改值。

代码2:Java解决ABA问题(AtomicStampedReference )

/*
解决ABA问题
*/

import java.util.concurrent.atomic.AtomicStampedReference;

public class ThreadDemo {
    private static AtomicStampedReference money = new AtomicStampedReference(100,1);
    public static void main(String[] args) throws InterruptedException {

        //转账 -100
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("1执行转账-100");
                boolean rs=money.compareAndSet(100,0,1,2);
                System.out.println(rs);
            }
        });
        t1.start();
        t1.join();
        //转账 +100
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("3执行转账+100");
                boolean rs=money.compareAndSet(0,100,2,3);
                System.out.println(rs);
            }
        });
        t3.start();

        t3.join();
        //转账 -100
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("2执行转账-100");
                boolean rs=money.compareAndSet(100,0,1,2);
                System.out.println(rs);
            }
        });
        t2.start();
        t2.join();

    }
}

运行结果:

参数1:V(期望的旧值)

参数2:B(新值)

参数3:旧版本号

参数4:新版本号

但是如果将上面的代码中的所有的100改成1000,

运行结果:

与上次的运行结果不一样,原因如下:

AtomicStampeReference注意事项:

里面的旧值对比的是引用,并不是实际的值。这就涉及到 Integer 高速缓存:-128到127(当取值为此范围的时候会使用缓存值,不会重新new对象)

悲观锁

定义:悲观锁认为只要执行多线程就会出现问题,所以在进入方法之后会直接加锁

悲观锁的实现:synchronized

synchronized有关面试题:

1.讲一下synchronized?synchronized是如何实现的?

答:在java层面:是将锁放在对象头中(每个对象都有对象头)中;

       在JVM层面:是监视器锁

       在操作系统层面:是互斥锁 mutex

2.synchronized在1.7有什么优化?(做了什么优化?)

答:无锁 -> 偏向锁 -> 轻量级锁(自旋锁)-> 重量级锁

公平锁和非公平锁

公平锁:获取锁的顺序按照访问的先后顺序获取。 通过设置参数为true来设置为公平锁,例如  new ReentrantLock(true) 
非公平锁:获取锁的顺序不会按照访问的先后顺序获取。不传参或传入false 性能比较高,Java设计里面的默认策略

独占锁和共享锁

独占锁:指的是这一把锁只能被一个线程拥有。例如,synchronized,lock

共享锁:指的是一把锁可以被多个线程同时拥有。例如,读写锁ReentrantReadWriteLock 读写锁当中的读锁是共享锁,写锁是独占锁,读锁写锁之间是独占锁


/*
   读写锁实例
*/
public class ThreadDemo {
    public static void main(String[] args) {
        //创建一个读写锁
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

        //得到读锁
        ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
        //得到写锁
        ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

        ThreadPoolExecutor executor = new ThreadPoolExecutor(10,10,0, TimeUnit.SECONDS
                ,new LinkedBlockingQueue<>(1000));

        //TODO 任务1 读锁演示
        executor.execute(new Runnable() {
            @Override
            public void run() {
                //加锁
                readLock.lock();
                try {

                    System.out.println(Thread.currentThread().getName()+"进入了读锁"+new Date());
                    Thread.sleep(3000);//如果1s之内是共享锁,否则就是独占锁
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //释放锁
                    readLock.unlock();
                }
            }
        });
        //TODO 任务2 读锁演示
        executor.execute(new Runnable() {
            @Override
            public void run() {
                //加锁
                readLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName()+"进入了读锁"+new Date());
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //释放锁
                    readLock.unlock();
                }
            }
        });

        //TODO 任务3 写锁演示
        executor.execute(new Runnable() {
            @Override
            public void run() {
                //加锁
                writeLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName()+"进入了写锁"+new Date());
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //释放锁
                    writeLock.unlock();
                }
            }
        });

        //TODO 任务4 写锁演示
        executor.execute(new Runnable() {
            @Override
            public void run() {
                //加锁
                writeLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName()+"进入了写锁"+new Date());
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //释放锁
                    writeLock.unlock();
                }
            }
        });

    }
}

 运行结果:

可重入锁

可重入锁:一个线程在拥有了一把锁之后,可以重复的进入,就是可重入锁。经典代表 synchronized

自旋锁

自旋锁:相当于死循环,一直循环尝试获取锁。

while(true){
   if(尝试获取锁){
            break;
   }
}
面试题:
 
1. 什么是偏向锁 ?
 
2. java synchronized 是怎么实现的,有了解过么?
 
3. synchronized 实现原理 是什么? 如果线程处于活动状态,升级为轻量级锁的状态。
 
 
JVM synchronized 锁分为 无锁、偏向锁、轻量级锁、重量级锁 状态。会根据情况,进行依次升级。
 
无锁: 没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功,其他修改失败的线程会不断重试直到修改成功。
 
偏向锁:在线程初次访问的时候,将线程的ID放到对象头偏向锁ID字段中,每次访问时判断一下线程的id是否等于对象头中的偏向锁Id ,如果相等则表明这个线程
 
轻量级锁: 轻量级锁是指当锁是偏向锁的时候,被第二个线程 B 所访问,此时偏向锁就会升级为轻量级锁,线程 B 会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。 当前只有一个等待线程,则该
 
线程将通过自旋进行等待。但是当自旋超过一定的次数时,轻量级锁便会 升级为重量级锁;当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁。
 
重量级锁: 指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。重量级锁通过对象内部的监视器(monitor )实现,而其中 monitor 的本质是依赖于底层操作系统的Mutex Lock 实现,
 
操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值