第二季:5公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁【Java面试题】

简介: 第二季:5公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁【Java面试题】

前言


2022 10/4 23:01

路漫漫其修远兮,吾将上下而求索


本文是根据尚硅谷学习所做笔记

仅供学习交流使用,转载注明出处


推荐

尚硅谷Java大厂面试题第2季,面试必刷,跳槽大厂神器

第二季大佬总结

值传递

说明

本文目录前是相关视频的名字和具体视频中思维导图的名字

题目

第一季:4方法的参数传递机制【Java面试题】

24 TransferValue醒脑小练习

package transfervalue5;
/**
 * 值传递和引用传递
 */
public class TransferValueDemo {
    public void changeValue1(int age) {
        age = 30;
    }
    public void changeValue2(Person person) {
        person.setPersonName("XXXX");
    }
    public void changeValue3(String str) {
        str = "XXX";
    }
    public static void main(String[] args) {
        TransferValueDemo test = new TransferValueDemo();
        // 定义基本数据类型
        int age = 20;
        test.changeValue1(age);
        System.out.println("age ----" + age);
        // 实例化person类
        Person person = new Person("abc");
        test.changeValue2(person);
        System.out.println("personName-----" + person.getPersonName());
        // String 不可变型
        String str = "abc";
        test.changeValue3(str);
        System.out.println("string-----" + str);
    }
}

结果

age ----20
personName-----XXXX
string-----abc

changeValue1的执行过程

八种基本数据类型,在栈里面分配内存,属于值传递


栈管运行,堆管存储


当们执行 changeValue1的时候,因为int是基本数据类型,所以传递的是int = 20这个值,相当于传递的是一个副本,main方法里面的age并没有改变,因此输出的结果 age还是20,属于值传递


changeValue2的执行过程

因为Person是属于对象,传递的是内存地址,当执行changeValue2的时候,会改变内存中的Person的值,属于引用传递,两个指针都是指向同一个地址

changeValue3的执行过程

String不属于基本数据类型,但是为什么执行完成后,还是abc呢?

这是因为String的特殊性,当我们执行String str = "abc"的时候,它会把 abc 放入常量池中

当我们执行changeValue3的时候,会重新新建一个xxx,并没有销毁abc,然后指向xxx,然后最后我们输出的是main中的引用,还是指向的abc,因此最后输出结果还是abc


2022 10/4 23:24


身体抱恙


2022 10/9 16:21

第二季:5公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁

说明

本文目录前是相关视频的名字和具体视频中思维导图的名字

题目

5公平锁/非公平锁/可重入锁/递归锁/自旋锁谈谈你的理解?请手写一个自旋锁

25 java锁之公平和非公平锁

公平和非公平锁

是什么

公平锁

是指多个线程按照申请锁的顺序来获取锁,类似于排队买饭,先来后到,先来先服务,就是公平的,也就是队列


非公平锁

是指多个线程获取锁的顺序,并不是按照申请锁的顺序,有可能申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转,或者饥饿的线程(也就是某个线程一直得不到锁)

两者区别

公平锁/非公平锁


并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,默认是非公平锁


关于两者区别:

公平锁:Threads acquire a fair lock in the order in which they requested it


公平锁,就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己


非公平锁: a nonfair lock permits barging: threads requesting a lock can jump ahead of the queue of waiting threads if the lock happens to be available when it is requested.


非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式。

题外话

Java ReentrantLock而言,

通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。

对于Synchronized而言,也是一种非公平锁

26 java锁之可重入锁和递归锁理论知识

可重入锁(又名递归锁)

是什么

可重入锁(也叫做递归锁)

指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取到该锁的代码,在同一线程在外层方法获取锁的时候,在进入内层方法会自动获取锁

也就是说:线程可以进入任何一个它已经拥有的锁所同步的代码块

ReentrantLock / Synchronized 就是一个典型的可重入锁

可重入锁的最大作用就是避免死锁

27 java锁之可重入锁和递归锁代码验证

ReentrantLockDemo

参考1

package lock5;
class Phone{
    public synchronized void sendMS() throws Exception{
        System.out.println(Thread.currentThread().getName()+"\t invoked sendMS()");
        sendEmail();
    }
    public synchronized void sendEmail() throws Exception{
        System.out.println(Thread.currentThread().getName()+"\t ####invoked sendEmail()");
    }
    //================================================================
}
/**
 * 可重入锁(也叫做递归锁)
 *
 * 指的是同一线程外层函数获得锁之后﹐内层递归函数仍然能获取该锁的代码,
 * 在同一个线程在外层方泫获取锁的时候,在进入内层方法会自动获取锁
 *
 * 也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。
 *
 * case one
 * Synchronized就是一个典型的可重入锁
 * t1  invoked sendMS()           t1线程外层函数获得锁之后
 * t1  ####invoked sendEmail()    t1在进入内层方法会自动获取锁
 * t2  invoked sendMS()
 * t2  ####invoked sendEmail()
 *
 *
 */
public class ReenterLockDemo {
    public static void main(String[] args) {
        Phone phone=new Phone();
        new Thread(()->{
            try {
                phone.sendMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"t1").start();
        new Thread(()->{
            try {
                phone.sendMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"t2").start();
    }
}

参考2

package lock5;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Phone implements Runnable{
    public synchronized void sendMS() throws Exception{
        System.out.println(Thread.currentThread().getName()+"\t invoked sendMS()");
        sendEmail();
    }
    public synchronized void sendEmail() throws Exception{
        System.out.println(Thread.currentThread().getName()+"\t ####invoked sendEmail()");
    }
    //================================================================================
    Lock lock=new ReentrantLock();
    @Override
    public void run() {
        get();
    }
   public void get(){
        lock.lock();
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"\t invoked get()");
            set();
        }finally {
            lock.unlock();//不配对将产生死锁
            lock.unlock();
        }
   }
    public void set(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"\t #####invoked set()");
        }finally {
            lock.unlock();
        }
    }
}
/**
 * 可重入锁(也叫做递归锁)
 *
 * 指的是同一线程外层函数获得锁之后﹐内层递归函数仍然能获取该锁的代码,
 * 在同一个线程在外层方泫获取锁的时候,在进入内层方法会自动获取锁
 *
 * 也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。
 *
 * case one
 * Synchronized就是一个典型的可重入锁
 * t1  invoked sendMS()           t1线程外层函数获得锁之后
 * t1  ####invoked sendEmail()    t1在进入内层方法会自动获取锁
 * t2  invoked sendMS()
 * t2  ####invoked sendEmail()
 *
 * case two
 * ReentrantLock就是一个典型的可重入锁
 * t3  invoked get()
 * t3  #####invoked set()
 * t4  invoked get()
 * t4  #####invoked set()
 *
 */
public class ReenterLockDemo {
    public static void main(String[] args) {
        Phone phone=new Phone();
        new Thread(()->{
            try {
                phone.sendMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"t1").start();
        new Thread(()->{
            try {
                phone.sendMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"t2").start();
        System.out.println();
        System.out.println();
        System.out.println();
        System.out.println();
        //tsleep
        try{
              TimeUnit.SECONDS.sleep(1);
        }catch (InterruptedException e){
              e.printStackTrace();
        }
        Thread t3 =new Thread(phone,"t3");
        Thread t4 =new Thread(phone,"t4");
        t3.start();
        t4.start();
    }
}

28 java锁之自旋锁理论知识

自旋锁

自旋锁(spinlock)

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


29 java锁之自旋锁代码验证

SpinLockDemo

package lock5;
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)){
//            System.out.println(Thread.currentThread().getName()+"\t自旋中ing");
        }
    }
    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();
           //tsleep
           try{ TimeUnit.SECONDS.sleep(5); }catch (InterruptedException e){ e.printStackTrace(); }
           spinLockDemo.myUnlock();
       },"AA").start();
        //tsleep
        try{ TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); }
        new Thread(()->{
            spinLockDemo.myLock();
            try{ TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); }
            spinLockDemo.myUnlock();
        },"BB").start();
    }
}

30 java锁之读写锁理论知识

独占锁(写锁) / 共享锁(读锁) / 互斥锁

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


共享锁:指该锁可以被多个线程锁持有

对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁

读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。

ReadWriteLockDemo

Before
package lock5;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
class MyCache{//资源类
    private volatile Map<String,Object> map =new HashMap<>();
    public void put(String key,Object value){
        System.out.println(Thread.currentThread().getName()+"\t 正在写入:"+key);
        //tsleep
        try{ TimeUnit.MILLISECONDS.sleep(300); }catch (InterruptedException e){ e.printStackTrace(); }
        map.put(key, value);
        System.out.println(Thread.currentThread().getName()+"\t 写入完成:");
    }
    public void get(String key){
        System.out.println(Thread.currentThread().getName()+"\t 正在读取:"+key);
        //tsleep
        try{ TimeUnit.MILLISECONDS.sleep(300); }catch (InterruptedException e){ e.printStackTrace(); }
        Object result = map.get(key);
        System.out.println(Thread.currentThread().getName()+"\t 读取完成:"+result);
    }
}
/**
 * 多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。
 * 但是
 * 如果有一个线程想去写共享资源来,就不应该再有其它线程可以对该资源进行读或写
 * 小总结:
 *      读-读能共存
 *      读-写不能共存
 *      写-写不能共存
 *
 *      写操作:原子+独占,整个过程必须是一个完整的统一体,中间不许被分割,被打断
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache=new MyCache();
        //forthread10
        for (int i = 1; i <= 5; i++) {
            final int tempInt=i;
            new Thread(()->{
                 myCache.put(tempInt+"",tempInt+"");
            },String.valueOf(i)).start();
        }
        for (int i = 1; i <= 5; i++) {
            final int tempInt=i;
            new Thread(()->{
                myCache.get(tempInt+"");
            },String.valueOf(i)).start();
        }
    }
}

结果

3  正在写入:3
4  正在写入:4
5  正在写入:5
1  正在写入:1
2  正在写入:2
1  正在读取:1
2  正在读取:2
3  正在读取:3
4  正在读取:4
5  正在读取:5
2  读取完成:null
2  写入完成:
1  写入完成:
3  写入完成:
1  读取完成:null
4  读取完成:null
5  写入完成:
5  读取完成:5
3  读取完成:null
4  写入完成:
After

写锁是为了写写互斥
读锁是为了读写互斥

package lock5;
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);
            //tsleep
            try{ TimeUnit.MILLISECONDS.sleep(300); }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 正在读取:"+key);
            //tsleep
            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 void clearMap(){
        map.clear();
    }
}
/**
 * 多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。
 * 但是
 * 如果有一个线程想去写共享资源来,就不应该再有其它线程可以对该资源进行读或写
 * 小总结:
 *      读-读能共存
 *      读-写不能共存
 *      写-写不能共存
 *
 *      写操作:原子+独占,整个过程必须是一个完整的统一体,中间不许被分割,被打断
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache=new MyCache();
        //forthread10
        for (int i = 1; i <= 5; i++) {
            final int tempInt=i;
            new Thread(()->{
                 myCache.put(tempInt+"",tempInt+"");
            },String.valueOf(i)).start();
        }
        for (int i = 1; i <= 5; i++) {
            final int tempInt=i;
            new Thread(()->{
                myCache.get(tempInt+"");
            },String.valueOf(i)).start();
        }
    }
}

结果

2  正在写入:2
2  写入完成:
3  正在写入:3
3  写入完成:
1  正在写入:1
1  写入完成:
4  正在写入:4
4  写入完成:
5  正在写入:5
5  写入完成:
1  正在读取:1
2  正在读取:2
3  正在读取:3
4  正在读取:4
5  正在读取:5
4  读取完成:4
3  读取完成:3
2  读取完成:2
1  读取完成:1
5  读取完成:5

最后


2022 10/9 20:25


p24~p31


Markdown 10342 字数 650 行数

HTML 9906 字数 457 段落

相关文章
|
4月前
|
Java 测试技术 微服务
最新技术栈下 Java 面试高频技术点实操指南详解
本指南结合最新Java技术趋势,涵盖微服务(Spring Cloud Alibaba)、响应式编程(Spring WebFlux)、容器化部署(Docker+Kubernetes)、函数式编程、性能优化及测试等核心领域。通过具体实现步骤与示例代码,深入讲解服务注册发现、配置中心、熔断限流、响应式数据库访问、JVM调优等内容。适合备战Java面试,提升实操能力,助力技术进阶。资源链接:[https://pan.quark.cn/s/14fcf913bae6](https://pan.quark.cn/s/14fcf913bae6)
170 25
|
4月前
|
缓存 Java 关系型数据库
2025 年最新华为 Java 面试题及答案,全方位打造面试宝典
Java面试高频考点与实践指南(150字摘要) 本文系统梳理了Java面试核心考点,包括Java基础(数据类型、面向对象特性、常用类使用)、并发编程(线程机制、锁原理、并发容器)、JVM(内存模型、GC算法、类加载机制)、Spring框架(IoC/AOP、Bean生命周期、事务管理)、数据库(MySQL引擎、事务隔离、索引优化)及分布式(CAP理论、ID生成、Redis缓存)。同时提供华为级实战代码,涵盖Spring Cloud Alibaba微服务、Sentinel限流、Seata分布式事务,以及完整的D
202 2
|
4月前
|
存储 安全 Java
常见 JAVA 集合面试题整理 自用版持续更新
这是一份详尽的Java集合面试题总结,涵盖ArrayList与LinkedList、HashMap与HashTable、HashSet与TreeSet的区别,以及ConcurrentHashMap的实现原理。内容从底层数据结构、性能特点到应用场景逐一剖析,并提供代码示例便于理解。此外,还介绍了如何遍历HashMap和HashTable。无论是初学者还是进阶开发者,都能从中受益。代码资源可从[链接](https://pan.quark.cn/s/14fcf913bae6)获取。
222 3
|
3月前
|
缓存 Java API
Java 面试实操指南与最新技术结合的实战攻略
本指南涵盖Java 17+新特性、Spring Boot 3微服务、响应式编程、容器化部署与数据缓存实操,结合代码案例解析高频面试技术点,助你掌握最新Java技术栈,提升实战能力,轻松应对Java中高级岗位面试。
336 0
|
4月前
|
存储 安全 Java
2025 最新史上最全 Java 面试题独家整理带详细答案及解析
本文从Java基础、面向对象、多线程与并发等方面详细解析常见面试题及答案,并结合实际应用帮助理解。内容涵盖基本数据类型、自动装箱拆箱、String类区别,面向对象三大特性(封装、继承、多态),线程创建与安全问题解决方法,以及集合框架如ArrayList与LinkedList的对比和HashMap工作原理。适合准备面试或深入学习Java的开发者参考。附代码获取链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
1416 48
|
4月前
|
算法 架构师 Java
Java 开发岗及 java 架构师百度校招历年经典面试题汇总
以下是百度校招Java岗位面试题精选摘要(150字): Java开发岗重点关注集合类、并发和系统设计。HashMap线程安全可通过Collections.synchronizedMap()或ConcurrentHashMap实现,后者采用分段锁提升并发性能。负载均衡算法包括轮询、加权轮询和最少连接数,一致性哈希可均匀分布请求。Redis持久化有RDB(快照恢复快)和AOF(日志更安全)两种方式。架构师岗涉及JMM内存模型、happens-before原则和无锁数据结构(基于CAS)。
116 5
|
4月前
|
Java API 微服务
2025 年 Java 校招面试全攻略:从面试心得看 Java 岗位求职技巧
《2025年Java校招最新技术要点与实操指南》 本文梳理了2025年Java校招的核心技术栈,并提供了可直接运行的代码实例。重点技术包括: Java 17+新特性(Record类、Sealed类等) Spring Boot 3+WebFlux响应式编程 微服务架构与Spring Cloud组件 Docker容器化部署 Redis缓存集成 OpenAI API调用 通过实际代码演示了如何应用这些技术,如Java 17的Record类简化POJO、WebFlux构建响应式API、Docker容器化部署。
153 5
|
4月前
|
缓存 NoSQL Java
Java Redis 面试题集锦 常见高频面试题目及解析
本文总结了Redis在Java中的核心面试题,包括数据类型操作、单线程高性能原理、键过期策略及分布式锁实现等关键内容。通过Jedis代码示例展示了String、List等数据类型的操作方法,讲解了惰性删除和定期删除相结合的过期策略,并提供了Spring Boot配置Redis过期时间的方案。文章还探讨了缓存穿透、雪崩等问题解决方案,以及基于Redis的分布式锁实现,帮助开发者全面掌握Redis在Java应用中的实践要点。
201 6
|
4月前
|
安全 Java API
2025 年 Java 校招面试常见问题及详细答案汇总
本资料涵盖Java校招常见面试题,包括Java基础、并发编程、JVM、Spring框架、分布式与微服务等核心知识点,并提供详细解析与实操代码,助力2025校招备战。
200 1
|
4月前
|
算法 Java 微服务
2025 年 Java 面试宝典社招春招秋招实操全方位攻略
2025年Java面试宝典涵盖核心技术及最新趋势,分为四大板块:1. Java基础:深入数据类型、多态等特性,结合学生信息管理等实例;2. JVM核心:解析内存模型与GC算法,附多线程转账等场景应用;3. 高并发方案:详解synchronized与线程池配置,提供Web服务器优化案例;4. Spring生态:剖析IoC/AOP原理,演示微服务架构实现。特别新增Java 17+特性实操,包括Record类、密封接口等语法糖,整合Spring Boot 3、响应式编程及云原生技术,通过订单状态机、API网关配置。
265 1