多线程及锁

1.lock锁和synchronized锁的区别。

1:Synchronized 是Java的一个关键字,而Lock是java.util.concurrent.Locks 包下的一个接口;

2:Synchronized 使用过后,会自动释放锁,而Lock需要手动上锁、手动释放锁;(粒度可控)

3:Lock提供了更多的实现方法,而且可响应中断、可定时, 而synchronized 关键字不能响应中断;

4:synchronized关键字是非公平锁,即,不能保证等待锁的那些线程们的顺序,而Lock的子类ReentrantLock默认是非公平锁,但是可通过一个布尔参数的构造方法实例化出一个公平锁;

5:synchronized无法判断是否已经获取到锁,而Lock通过tryLock()方法可以判断是否已获取到锁;

6:Lock可以通过分别定义读写锁提高多个线程读操作的效率。

7:二者的底层实现不一样,synchronized是同步阻塞,采用的是悲观并发策略;Lock是同步非阻塞,采用的是乐观并发策略

PS:

公平锁与非公平锁:

公平锁就是:先等待的线程,先获得锁。 非公平锁就是:不能够保证 等待锁的 那些线程们的顺序, 公平锁因为需要维护一个等待锁资源的队列,所以性能相对低下

2.乐观锁和悲观锁的区别

悲观锁的思想是"先悲观地认为会发生冲突",在访问数据之前会将其锁定,直到操作完成后才会释放锁。确保在整个数据访问过程中,只有一个线程或进程能够访问数据,其他的线程或进程需要等待。

乐观锁的思想是"先乐观地认为不会发生冲突",在访问数据时不加锁,而在数据更新时进行冲突检测。多个线程或进程可以同时访问数据,但在提交更新时,会检查是否有其他线程或进程对数据进行了修改。如果发现冲突,就进行处理。乐观锁一般会使用版本号机制和CAS算法实现。通过版本号一致与否,即给数据加上版本,来同步更新数据以及加上版本号。

总的来说,悲观锁是先锁定资源再进行操作,保证数据的一致性,但并发性较差;而乐观锁是先进行操作再检查冲突,允许多个线程或进程同时访问,但是在更新的时候会判断一下在此期间其他线程或进程有没有去更新这个数据,可以使用版本号机制和CAS算法实现。选择使用哪种锁取决于具体的场景和需求。

3.synchronized锁的升级过程

  1. 无锁状态(Unlocked):当一个线程访问一个同步代码块时,并没有其他线程正在访问该代码块,那么该线程可以直接获取锁。
  2. 偏向锁状态(Biased):当一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。
  3. 轻量级锁状态(Lightweight):当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。
  4. 重量级锁状态(Heavyweight):当轻量级锁外的其他线程自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁就升级为重量级锁。

4.countDownLatch的三个方法

  • CountDownLatch(int count); //构造方法,创建一个值为count 的计数器。
  • await();//阻塞当前线程,将当前线程加入阻塞队列。
  • countDown();//对计数器进行递减1操作,当计数器递减至0时,当前线程会去唤醒阻塞队列里的所有线程。
  • getcount();//获取当前计数器的值

5.synchronized 静态和非静态的区别

  • 修饰非静态方法,实际上是对调用该方法的对象加锁,俗称"对象锁"。
  • 修饰静态方法,实际上是对该类对象加锁,俗称"类锁"。

6.线程的6种状态

一个线程在某一个时间点处于一个状态,这些状态不反应操作系统的线程状态。

  • NEW 新创建的线程,尚未启动的线程状态,新创建了一个线程对象,但还没有调用start()方法。
  • RUNNABLE 在Jvm虚拟机中执行的线程所处的状态(为什么不是running? jvm可以运行)
  • BLOCKED 被阻塞等待监视器锁定的线程处于此的状态
  • WAITING 正在等待另外一个线程执行特定动作的线程所处的状态
  • TIME_WAITING 同上,它有超时时间
  • TERMINATED 已退出的线程处于此状态,终止

7.Runnable和Callable的区别

相同点:

  • 都是Java中用于实现多线程的接口

不同点:

  • Runnable没有返回值,Callable有返回值
  • Runnable不能抛出异常,Callable可以抛出异常
  • Runnable接口可以由Thread类直接执行,Callable接口需要通过FutureTask类执行

Runnable适合于不需要返回结果、不需要处理异常的简单场景;

Callable更适合需要返回结果、需要处理异常的复杂场景。

8.wait和sleep的区别

  1. 使用方式:wait()方法是Object类的方法,需要在synchronized块内使用;而sleep()方法是Thread类的静态方法,可以在任何地方调用。
  2. 锁的释放:wait()方法会释放当前线程持有的锁;而sleep()方法不会释放锁。
  3. 唤醒方式:wait()方法需要被其他线程调用notify()或notifyAll()方法来唤醒等待的线程;而sleep()方法会在指定的时间到达后自动唤醒。
  4. 异常抛出:wait()方法必须在try-catch块中进行异常处理;而sleep()方法则可以不做异常处理(由编译器处理)。
  5. 使用范围:wait()方法通常用于线程之间的同步和等待,常与notify()或notifyAll()一起使用;而sleep()方法主要用于线程的暂停和延迟执行。

9.lock接口的实现类

  1. ReentrantLock:可重入锁,Java 中较为常用的一种锁。与 synchronized 关键字相似,ReentrantLock 支持多个同时访问该锁的线程,但需要显式地获取和释放锁,具有更灵活的控制能力。
  2. ReentrantReadWriteLock:可重入读写锁,同时支持读和写的锁定机制,读锁可以被多个线程同时获取,写锁只能被单个线程获取。因为读的操作通常不会改变共享资源的状态,读写锁可以提高并发效率。
  3. StampedLock:StampedLock 是 Java 8 中新增的一种锁,提供了乐观读锁和悲观写锁两种模式。乐观读锁不会阻塞其他线程,但可能会失败;而悲观写锁会阻塞其他线程的读写操作。

10.读写锁

读锁和写锁,其中读锁允许多个线程同时获得,因为读操作本身是线程安全的,而写锁则是互斥锁,不允许多个线程同时获得写锁,并且写操作和读操作也是互斥的。

11.JDK自带线程池有哪几种,阿里为什么不推荐

OOM(out of memory(存储器,内存)): 当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error(注:非exception,因为这个问题已经严重到不足以被应用处理)。

详细版本:

1)newFixedThreadPool(固定大小的线程池)

每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程

不推荐使用的原因

这个线程池到时没有上个线程池豪横了, 它定死了线程数量, 所以线程数量是不会超出的,但是它的任务队列是无界的LinkedBlockingQueue, 对于加进来的任务处理不过来就会存入任务队列中, 并且无限制的存入队列。 这个线程池感觉就是家里有地, 无论来多少货都往里面装。这个线程池如果使用不当很容易导致OOM

补充:LinkedBlockingQueue 是Java中的一个阻塞队列,它是基于链表实现的,具有 无界限制 (但可以设置容量大小)。它的特点是当队列为空时,从队列中获取元素的操作会被阻塞;当队列已满时,往队列里添加元素的操作会被阻塞。

2)newSingleThreadExecutor(单线程的线程池)

这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

不推荐使用的原因

这个线程池只有一个线程, 比newFixedThreadPool还穷, 但是任务队列和上面一样, 没有限制, 很容易就使用不当导致OOM

这个是定时任务的线程池, 没有定义线程创建数量的上线, 同时任务队列也没有定义上限, 如果前一次定时任务还没有完成, 后一个定时任务的运行时间到了, 它也会运行, 线程不够就创建。 这样如果定时任务运行的时间过长, 就会导致前后两个定时任务同时执行,如果他们之间有锁,还有可能出现死锁, 此时灾难就发生了。

3)newScheduledThreadPool(可调度的线程池 周期线程池)

线程池支持定时以及周期性执行任务的需求,以创建一个固定大小的线程池,该线程池可以在指定的延迟时间后执行任务,也可以按照一定的时间间隔周期性地执行任务。

不推荐使用的原因

这个线程池到时没有上个线程池豪横了, 它定死了线程数量, 所以线程数量是不会超出的,但是它的任务队列是无界的LinkedBlockingQueue, 对于加进来的任务处理不过来就会存入任务队列中, 并且无限制的存入队列。 这个线程池感觉就是家里有地, 无论来多少货都往里面装。这个线程池如果使用不当很容易导致OOM

4)newCachedThreadPool(可缓存的线程池)

如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲( 60 秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM )能够创建的最大线程大小。

不推荐使用使用的原因:

最大线程数是Integer.MAX_VALUE, 并且任务队列是SynchronousQueue。 也就是说这个线程池对任务来者不拒,线程不够用就创建一个, 感觉就像一个豪横的富豪。 这就是问题所在了, 如果同一时刻应用的来了大量的任务, 这个线程池很容易就创建过多的线程, 而创建线程又是一个很耗性能的事情, 这就容易导致应用卡顿或者直接OOM

补充:SynchronousQueue 是Java中的一个阻塞队列,它不存储元素,而是在生产者线程将元素放入队列时,必须等待消费者线程将元素取出后才能继续执行。也就是说,每个插入操作必须等待一个对应的移除操作。

简略版本(背这个):

1.newFixedThreadPool(固定大小的线程池)

2.newSingleThreadExecutor(单线程的线程池)

3.newScheduledThreadPool(可调度的线程池 周期 线程池)

4.newCachedThreadPool(可缓存的线程池) 不限制最大线程数

为什么不推荐:

任务队列无限制,容易导致oom

OOM: 当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error(注:非exception,因为这个问题已经严重到不足以被应用处理)。

12.什么场景会使线程进入waiting状态,如何唤醒。

当某个线程获取到锁后,发现当前还不满足执行的条件,就可以调用对象锁的wait方法,进入等待状态。 直到某个时刻,外在条件满足了,就可以由其他线程通过调用notify ()或者notifyAll ()方法,来唤醒此线程。

13、事务失效的几种场景

1 非public修饰的方法

@Transactional注解只能在在public修饰的方法下使用。

2 类内部访问

类内部直接访问类内部的方法。

解决方法:在该Service类中使用AopContext.currentProxy()获取代理对象

3 数据库不支持事务

MySQL中,MyISAM引擎不支持事物,InnoDB 支持事物

4 异常类型不匹配

@Transactional 注解默认只处理运行时异常( RuntimeException 和 error ),而不会处理受检异常( Exception 的子类)。当抛出未被捕获的运行时异常时,Spring 会触发事务回滚操作,将之前的操作撤销;而对于未被捕获的受检异常,Spring 不会触发事务回滚操作。如果需要处理受检异常并触发事务回滚,可以通过 rollbackFor 和 noRollbackFor 属性来指定需要回滚或不需要回滚的异常类型。

5 传播属性设置问题 PROPAGATION_NOT_SUPPORTED

事务声明传播属性为不支持事务,spring提供的一种传播机制,表示以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

6 捕获异常未抛出

事务管理是通过try...catch捕获异常才能做回滚的,如果业务代码的异常try...catch并没有向外抛出,这时出现异常后事务就不会回滚而使得事务的一致性没法满足,进而事务失效

7 Bean没有纳入Spring IOC容器管理

8 事务方法内启动新线程进行异步操作

14、sso单点登录如何实现

Token:采用 Token 方式实现 SSO,其原理是在用户登录之后,认证服务器生成一个 Token,并将该 Token 存储在共享的存储中(例如 Redis、数据库等),然后将该 Token 返回给用户。用户在访问其他应用系统时,该系统会向认证服务器发送请求,并带上 Token。认证服务器通过 Token 来判断该用户是否已经登录过,如果已经登录过,则返回一个新的 Token 给应用系统,否则需要用户重新登录。

单点登录(Single Sign-On,简称 SSO)是指用户只需要登录一次,即可在多个应用系统中访问受授权的资源。其原理是在用户登录之后,服务器会颁发一个令牌(Token),并将该令牌保存在共享的存储中(例如 Redis、数据库等),然后在用户访问其他应用系统时,该系统会向认证服务器发送请求,如果该用户已经登录过,则认证服务器会颁发一个新的令牌,旧的令牌作废,否则需要用户重新登录。

实现多个设备的登录,可以使用hash结构存储

15、spu,sku,批次

  1. SPU (Standard Product Unit,标准产品单位):SPU是指标准的产品单位,通常表示某种产品的基本型号或规格。它可以看作是一个产品的基本框架,包含了该产品的核心属性和特征。SPU可以理解为一个产品族的总称,例如手机品牌下的某一款型号。 小米13
  2. SKU(Stock Keeping Unit,库存单位):SKU是指为了区分和管理不同的库存产品而设定的一种标识符。每个SKU代表一个具体的库存产品,可以根据产品的规格、颜色、尺寸、包装等因素进行区分。一个SPU可以对应多个SKU,每个SKU代表该SPU的一个具体变体。
  3. 批次 :批次是指根据产品的生产时间和地点等属性,批量生产的一批产品。同一批次的产品具有相同或相近的特征和属性。为了追踪和管理不同批次的产品,通常会为每个批次分配一个唯一的批次号 (可乐,安慕希
相关推荐
逊嘘几秒前
【Java语言】抽象类与接口
java·开发语言·jvm
Half-up3 分钟前
C语言心型代码解析
c语言·开发语言
morris1318 分钟前
【SpringBoot】Xss的常见攻击方式与防御手段
java·spring boot·xss·csp
我要洋人死13 分钟前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人25 分钟前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
Source.Liu25 分钟前
【用Rust写CAD】第二章 第四节 函数
开发语言·rust
monkey_meng25 分钟前
【Rust中的迭代器】
开发语言·后端·rust
科技探秘人25 分钟前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
余衫马28 分钟前
Rust-Trait 特征编程
开发语言·后端·rust
JerryXZR31 分钟前
前端开发中ES6的技术细节二
前端·javascript·es6