锁、volatile、CAS底层理解

面试题分享

一、volatile部分

1.请你说说volatile的理解?

volatile是java虚拟机提供的轻量级的同步机制。拥有三大特性:保证可见性、进制指令重排、不保证原子性

2,JMM的理解

JMM(java内存模型)本身是一种抽象的概念并不真实的存在,他描述的是一种规范,通过这组规范定义了各种变量(实例字段,静态字段和构成数组对象的元素)的访问方式。

JMM规定:

  • 线程解锁前,必须把共享变量的值刷新会主内存
  • 线程加锁前,必须读取主内存的最新值到自己的工作内存
  • 加锁解锁用的是同一把锁
案例1: 数据可见性
import java.util.concurrent.TimeUnit;
/**
 * 验证volatile的可见性
 */
public class VolatileDemo {
    volatile  int number = 0;
    public void changeNum(){
        this.number = 50;
    }

    public static void main(String[] args) {
        VolatileDemo volatileDemo = new VolatileDemo();
       new Thread(()->{
           System.out.println(Thread.currentThread().getName()+"\t the primary value!");
           try {
               TimeUnit.SECONDS.sleep(3);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           volatileDemo.changeNum();
           System.out.println(Thread.currentThread().getName()+"\t change value");
       },"aaa").start();
       while(volatileDemo.number == 0){

       }
        System.out.println(Thread.currentThread().getName()+"\t finally value");
    }
}

结果:

请添加图片描述

案例2: 不保证原子性
public class VolatileDemo {
    volatile  int number = 0;

    public void changeNum(){
       number++;
    }

    public static void main(String[] args) {
        VolatileDemo volatileDemo = new VolatileDemo();
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 1; j <= 100; j++) {
                    volatileDemo.changeNum();
                }
            },String.valueOf(i)).start();
        }
        while (Thread.activeCount() > 2){
            //线程让步
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()+"\t finally number value:"+volatileDemo.number);
    }
}
3、如何解决原子性操作:

1.加synchronized

2,使用我们的juc下的AtomicInteger

4.线程安全如何保证?

1.工作内存与主内存同步延迟现象导致可见性问题

  • Synchronized
  • Volatile

2.对于指令重排导致可见性问题和有序性问题

volatile关键字作用可以禁止指令重排。

5、哪些地方使用volatile?
  • 单例模式DCL代码
  • CAS底层

二、CAS理解

1、谈谈你对CAS的理解?

比较并替换,它是一种CPU并发的原语,功能是判断 内存中的某个位置是否为预选期,如果是则更改为新的值,该过程是原子性(底层Unsafe)的。

并发原语的执行必须是连续的,在执行的过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓数据不一致的问题。

CAS机制当中使用了3个基本操作数: 内存地址V,旧的预期值A,要修改的新值B,当你更新一个变量的时候,只有当变量的预期值A和内存地址V中的实际值相同时,才会将内存地址V对应的值修改为B。

2.认识CAS底层 unsafe .getAndAddInt

unsafe类 +CAS思想(自旋实现)

unsafe 是CAS的核心类,由于java语言无法直接访问底层系统,需要通过本地的native方法来访问,unsafe相当于一个后门,该类可以直接操作特定内存的数据,unsafe来存在于sun.misc包中,java中CAS操作的执行依赖于insafe类的方法。

CAS案例:

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2);

        System.out.println(atomicInteger.compareAndSet(2, 10) +"\t current date:" + atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(2, 100) +"\t current date:" + atomicInteger.get());
        atomicInteger.getAndIncrement();

    }
}

结果:

请添加图片描述
3、CAS缺点

  • 循环时间长开销很大
    分析源码知,getAndAddInt方法执行时,有一个do while,如果CAS失败,会一直进行尝试,如果CAS长时间一直不成功,可能会给CPU带来很大的压力。

  • 只能保证一个共享变量的原子性操作

  • ABA问题
    案例: ABA问题以及解决方案

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

//ABA问题演示
public class ABDemo {
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);

    public static void main(String[] args) {
        System.out.println("=============ABA问题的产生=============");
        new Thread(()->{
            atomicReference.compareAndSet(100,101);
            atomicReference.compareAndSet(101,100);
        },"t1").start();


        new Thread(()->{
            try {
                //为了确保线程1 ABA  执行成功
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicReference.compareAndSet(100, 200) + "\t update Number Value: "+ atomicReference.get().toString());
        },"t2").start();

        // 暂停一会线程
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("=============ABA问题的解决=============");

        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t 第一次的版本号:"+stamp);
            // 暂停一秒钟 3线程
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
           atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);
      System.out.println(Thread.currentThread().getName() + "\t 第二次的版本号:"+ atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t 第三次的版本号:"+ atomicStampedReference.getStamp());

        },"t3").start();


        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t 第一次的版本号:"+stamp);
            // 暂停3秒钟4线程,保证上面的3线程完成一次ABA操作
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"\t 是否修改成功: "+ result +"\t 当前最新实际版本号:" + atomicStampedReference.getStamp());
            System.out.println(Thread.currentThread().getName()+"\t 当前实际最新值:" +atomicStampedReference.getReference());
        },"t4").start();
    }
}

结果:

请添加图片描述

三、集合类HashSet

1、hashset的底层是hashmap,为什么new HashSet<>.add(“a”)不出错(HashMap是基于key value形式存储数据)?

hashSet的底层是hashMap,当执行add()操作时,会执行put()方法,

public boolean add(E e){
    return map.put(e.PRESENT) == null;
}

hashset中的add,就是hashmap中的put元素的key,而value是Object类型的常量,因此value是恒定的。

private static final Object PRESENT = new Object();
2、为什么说hashset不允许有重复的元素?

HashSet中不允许有重复的元素,因为hashset的底层是hashmap,而hashSet中的元素都存放在HashMap的key上,value是恒定的。

3、怎样检测某个元素是否存在?

通过重写元素中的equals()和hashcode()方法,当向Set中添加数据的时候,先通过key计算出hashcode()方法,此时,就可确定key元素的位置。

四、锁

1、公平锁与非公平锁

并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁和非公平锁。**默认是非公平锁。**非公平锁的优点在于吞吐量比公平锁大。

公平锁: 在并发中,每个线程在获取锁时会先查看当前此锁维护的等待队列,之后按照FIFO的规则从队列中获取自己。

非公平锁: 该线程一上来就直接尝试占有锁,如果尝试失败,就采用类似公平锁的那种方式。

2、可重入锁(又称递归锁)

概念:指的是同一个线程的外层方法获取锁的时候,在进入内层方法会自动获取锁。即 线程可以进入任何一个它已经拥有的锁所同步着的代码块。

实现方式: lock 和 Synchronized

案例1:基于synchronized 实现可重入锁

//synchronized 实现可重入锁
public class SynLock {
    public static void main(String[] args) {
        Object o = new Object();
        new Thread(()->{
            synchronized (o){
                System.out.println(Thread.currentThread().getName()+"  最外层");

                synchronized (o) {
                    System.out.println(Thread.currentThread().getName() + "  最中层");

                    synchronized (o) {
                        System.out.println(Thread.currentThread().getName() + "  最内层");
                    }
                }
            }
        },"AAA").start();
    }
}

案例2: 基于lock 实现可重入锁

//lock 实现可重入锁
public static void main(String[] args) {
        Lock lock = new ReentrantLock();
           //创建线程
            new Thread(()->{
                try{
                    lock.lock();
                    System.out.println(Thread.currentThread().getName()+"  最外层");
                    try{
                        lock.lock();
                        System.out.println(Thread.currentThread().getName()+"  最中层");
                        try{
                            lock.lock();
                            System.out.println(Thread.currentThread().getName()+"  最内层");
                        }finally {
                            lock.unlock();
                        }
                    }finally {
                        lock.unlock();
                    }
                }finally {
                    lock.unlock();
                }
            },"AAA").start();
    }
}

结果:
img

3.自旋锁

指尝试获取锁的线程不会立即阻塞,而是采用循环的方式尝试获取锁,好处是减少线程上下文的切换的消耗,缺点就是循环会消耗CPU.

案例:自旋锁

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

public class SpinLockDemo {
    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"\t  come in");
        while (!atomicReference.compareAndSet(null,thread)){

        }
    }
    public void myUnLock(){
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread,null);
        System.out.println(Thread.currentThread().getName()+"\t invoked myUnlock!");

    }

    public static void main(String[] args) {
        SpinLockDemo spinLockDemo = new SpinLockDemo();
        new Thread(()->{
            spinLockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.myUnLock();
        },"AAA").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            spinLockDemo.myLock();
            spinLockDemo.myUnLock();
        },"BBB").start();
    }
}

结果:

请添加图片描述

4.独占锁与共享锁

独占锁:指该锁一次只能被一个线程所持有,对ReentrantLock和Synchronized而言都是独占锁。

共享锁: 指该锁可被多个线程锁持有。对于ReentrantReadWriteLock其读锁是共享锁,写锁是独占锁。

区别: 读读共享。读写,写写不共享。

写操作; 原子 + 独占 , 整个过程必须是一个完整的统一体,中间不允许有分割。

案例:读写锁案例

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

//资源类
class MyCache{
    private volatile Map<String,Object> map = new HashMap<>();
    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void put(String key,Object value){
        rwLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"\t 正在写入一个资源信息: "+key);
            try {
                TimeUnit.MICROSECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"\t 写入完成:");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            rwLock.writeLock().unlock();
        }
    }


    public void get(String key){
       // 读锁
       rwLock.readLock().lock();
        try{
            System.out.println(Thread.currentThread().getName() + "\t 正在读取:");
            try{
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t 读取完成: " +result);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
        rwLock.readLock().unlock();
        }
    }
}

public class ReadAndWriteDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for (int i = 1; i <= 5 ; i++) {
            final int temp = i;
            new Thread(()->{
               myCache.put(temp+"",temp+"");
            },String.valueOf(i)).start();
        }

        for (int i = 1; i <= 5 ; i++) {
            final int tempInt = i;
            new Thread(()->{
                myCache.get(tempInt+"");
            },String.valueOf(i)).start();
        }

    }

}

结果:
请添加图片描述

五、阻塞队列升级版

1、种类分析:

1、ArrayBlockingQueue

由数组结构组成的有界阻塞队列。

2、LinkedBlockingQueue

由链表结构组成的有界(默认值的大小为Integer.MAX_VALUE)阻塞队列。但是Integer.MAX_VALUE的值很大,大概有21亿之大,英雌箱相当于无界。

3、SynchronizedQueue

不存储元素的阻塞队列,也就是说只要存一个元素就要取出来

案例:SynchronizedQueue 的使用

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

public class SynchronizedQueue {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"\t put 1" );
                blockingQueue.put("1");

                System.out.println(Thread.currentThread().getName()+"\t put 2" );
                blockingQueue.put("2");

                System.out.println(Thread.currentThread().getName()+"\t put 3" );
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"AAA").start();



        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                System.out.println(Thread.currentThread().getName()+"\t" +blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                System.out.println(Thread.currentThread().getName()+"\t" +blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                System.out.println(Thread.currentThread().getName()+"\t" +blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


        },"BBB").start();
    }
}

结果:

请添加图片描述

4、PriorityBlockingQueue

支持优先级排序的无界阻塞队列。

5、DelayQueue

使用优先级队列实现的延误无界阻塞队列。

6、LinkedTransferQueue

由链表结构组成的无界阻塞队列。

7、LinkedBlockingQueue

由链表结构组成的双向阻塞队列。

2、线程之间的通信(生产者与消费者)

注意: 对于多线程 ,一般用while( )语句进行判断,减少 if语句的使用

1.0版本过度到2.0版本

请添加图片描述

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//  资源类
class  ShareData{
    private int number = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public  void increment() throws InterruptedException {
       lock.lock();
       try {
           //1.判断
           while (number != 0){
               //等待,不能生产
               condition.await();
           }
           // 2.干活做事
           number++;
           System.out.println(Thread.currentThread().getName()+"\t"+ number);

           //3.通知唤醒
           condition.signalAll();

       }catch (Exception e){
           e.printStackTrace();
       }finally {
           lock.unlock();
       }
    }

    public  void decrement() throws InterruptedException {
        lock.lock();
        try {
            //1.判断
            while (number == 0){
                //等待,不能生产
                condition.await();
            }
            // 2.干活做事
            number--;
            System.out.println(Thread.currentThread().getName()+"\t"+ number);

            //3.通知唤醒
            condition.signalAll();

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
/**
 * 题目: 一个初始值为0的变量,两个线程对其操作,一个加1,一个减一,来5轮
 * 1.  线程    操作    资源类
 * 2.  判断    做事    通知
 * 3.  防止虚假唤醒机制
 */
public class ProdConsumer_TraditionDemo {
    public static void main(String[] args) {

        ShareData shareData = new ShareData();
        new Thread(()->{
            for (int i = 1; i <= 5; i++) {
                try {
                    shareData.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AAA").start();

        new Thread(()->{
            for (int i = 1; i <= 5; i++) {
                try {
                    shareData.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BBB").start();
    }
}

结果:

请添加图片描述

3.synchronized和Lock有什么区别?用新的Lock有啥好处?
1、原始构成

synchronized是关键字,属于JVM层面。

synchronized底层是通过monitor、monitorexit对象来完成。

Lock是具体的类,是属于api底面的锁。

2.使用方法

synchronized不需要手动释放锁,当synchronized代码执行完后系统会自动让线程释放对锁的占用。

ReentrantLock则需要用户手动释放锁,如果没有手动释放锁,可能会造成死锁的现象。因此,需要lock()和unLock()方法配合try/finally语句来完成。

3、加锁是否公平

synchronized非公平锁

ReentrantLock即可公平也可非公平,默认是公平锁,构造方法可以传入boolean值,true是公平锁,false是非公平锁。

4、等待是否中断

synchronized不可中断,除非抛出异常或者正常运行完成。ReentrantLock可中断,需要设置超时方法,tryLock(long timeout,TimeUnit unit),使用interrupt()方法可中断。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值