必懂系列!Java并发面试题

Java面试篇【并发编程·进阶】常见面试题(2024最新) synchronized关键字解决的是多线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能被一个线程执行。监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock来实现的,java的线程是映射到操作系统原生线程之上。如果要挂起一个线程,或者唤醒一个线程,都需要os来帮忙,而os实现线程切换需要从用户态转换为内核态。需要时间长,成本高。 阅读详情

题 图:pexels

来 源:https://ifeve.com/

预 计 阅 读 时 间:15分钟

多线程

java中有几种方法可以实现一个线程?
  1. 继承Thread类;

  2. 实现Runnable接口;

  3. 实现Callable接口通过FutureTask包装器来创建Thread线程;

  4. 使用ExecutorService、Callable、Future实现有返回结果的多线程(也就是使用了ExecutorService来管理前面的三种方式)。

详情参见: 

https://radiancel.github.io/2018/08/02/Multithreading/

如何停止一个正在运行的线程?

1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。2、使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。3、使用interrupt方法中断线程。

参考: 

https://www.cnblogs.com/greta/p/5624839.html

notify()和notifyAll()有什么区别?

如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。

优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

参考: 

再谈notify和notifyAll的区别和相同

sleep()和 wait()有什么区别?

对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。

sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。

当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。

参考: 

https://www.cnblogs.com/hongten/p/hongtenjavasleep_wait.html

什么是Daemon线程?它有什么意义?

Java语言自己可以创建两种进程“用户线程”和“守护线程”

用户线程:就是我们平时创建的普通线程.

守护线程:主要是用来服务用户线程.

Daemon就是守护线程,他的意义是:

只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。

Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。

参考: 

https://www.cnblogs.com/ChrisWang/archive/2009/11/28/1612815.html

java如何实现多线程之间的通讯和协作?

参考这篇: 

http://www.cnblogs.com/hapjin/p/5492619.html

锁

什么是可重入锁(ReentrantLock)?

线程可以进入任何一个它已经拥有的锁所同步着的代码块。

代码设计如下:


   
  1. public class Lock{

  2.   boolean isLocked = false;

  3.   Thread  lockedBy = null;

  4.   int lockedCount = 0;

  5.   public synchronized void lock()

  6.           throws InterruptedException{

  7.       Thread thread = Thread.currentThread();

  8.       while(isLocked && lockedBy != thread){

  9.           wait();

  10.       }

  11.       isLocked = true;

  12.       lockedCount++;

  13.       lockedBy = thread;

  14.   }

  15.   public synchronized void unlock(){

  16.       if(Thread.currentThread() == this.lockedBy){

  17.           lockedCount--;

  18.           if(lockedCount == 0){

  19.               isLocked = false;

  20.               notify();

  21.           }

  22.       }

  23.   }

  24. }

参考链接: 

https://www.cnblogs.com/dj3839/p/6580765.html

当一个线程进入某个对象的一个synchronized的实例方法后,其它线程是否可进入此对象的其它方法?

如果其他方法前加了synchronized关键字,就不能,如果没加synchronized,则能够进去。

如果这个方法内部调用了wait(),则可以进入其他加synchronized的方法。

如果其他方法加了synchronized关键字,并且没有调用wai方法,则不能。

ynchronized和java.util.concurrent.locks.Lock的异同?

主要相同点:Lock能完成Synchronized所实现的所有功能。

主要不同点:Lock有比Synchronized更精确的线程予以和更好的性能。Synchronized会自动释放锁,但是Lock一定要求程序员手工释放,并且必须在finally从句中释放。

乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

乐观锁是假设每次操作都不会冲突,若是遇到冲突失败就重试直到成功;悲观锁是让其他线程都等待,等锁释放完了再竞争锁。

乐观锁实现方式:cas,volatile

悲观锁实现方式:synchronized,Lock

并发框架

SynchronizedMap和ConcurrentHashMap有什么区别?

SynchronizedMap()和Hashtable一样,实现上在调用map所有方法时,都对整个map进行同步。而ConcurrentHashMap的实现却更加精细,它对map中的所有桶加了锁。所以,只要有一个线程访问map,其他线程就无法进入map,而如果一个线程在访问ConcurrentHashMap某个桶时,其他线程,仍然可以对map执行某些操作。

所以,ConcurrentHashMap在性能以及安全性方面,明显比Collections.synchronizedMap()更加有优势。同时,同步操作精确控制到桶,这样,即使在遍历map时,如果其他线程试图对map进行数据修改,也不会抛出ConcurrentModificationException。

参考: 

https://www.cnblogs.com/shamo89/p/6700353.html

CopyOnWriteArrayList可以用于什么应用场景?

CopyOnWriteArrayList的特性是针对读操作,不做处理,和普通的ArrayList性能一样。而在写操作时,会先拷贝一份,实现新旧版本的分离,然后在拷贝的版本上进行修改操作,修改完后,将其更新至就版本中。

那么他的使用场景就是:一个需要在多线程中操作,并且频繁遍历。其解决了由于长时间锁定整个数组导致的性能问题,解决方案即写时拷贝。

另外需要注意的是CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。

参考: 

https://www.cnblogs.com/duchanggang/p/4627082.html https://www.cnblogs.com/yw-technology/p/7476106.html

线程安全

什么叫线程安全?servlet是线程安全吗?

线程安全就是说多线程访问同一代码,不会产生不确定的结果。

在多线程环境中,当各线程不共享数据的时候,即都是私有(private)成员,那么一定是线程安全的。但这种情况并不多见,在多数情况下需要共享数据,这时就需要进行适当的同步控制了。

线程安全一般都涉及到synchronized, 就是一段代码同时只能有一个线程来操作 不然中间过程可能会产生不可预制的结果。

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

Servlet不是线程安全的,详见:漫画 | Servlet属于线程安全的吗?

同步有几种实现方法?
1.同步方法

即有synchronized关键字修饰的方法。

由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

2.同步代码块

即有synchronized关键字修饰的语句块。

被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。

3.使用特殊域变量(volatile)实现线程同步

a.volatile关键字为域变量的访问提供了一种免锁机制,

b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,

c.因此每次使用该域就要重新计算,而不是使用寄存器中的值

d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量

4.使用重入锁实现线程同步

在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。

5.使用局部变量实现线程同步。

参考: 

https://www.cnblogs.com/jiansen/p/7351872.html

volatile有什么用?能否用一句话说明下volatile的应用场景?

作用是:作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值,即不是从寄存器里取备份值,而是去该地址内存存储的值。

一句话说明volatile的应用场景:

对变量的写操作不依赖于当前值且该变量没有包含在具有其他变量的不变式中。

请说明下java的内存模型。

Java内存模型的逻辑视图

为了保证并发编程中可以满足原子性、可见性及有序性。有一个重要的概念,那就是内存模型。

为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。

通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。

它解决了 CPU 多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。

内存模型解决并发问题主要采用两种方式:
  1. 限制处理器优化

  2. 使用内存屏障

关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节。

Java内存模型定义了以下八种操作来完成:
  • lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。

  • unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

  • read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用

  • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。

  • use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。

  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。

  • store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。

  • write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作,如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。

也就是read和load之间,store和write之间是可以插入其他指令的,如对主内存中的变量a、b进行访问时,可能的顺序是read a,read b,load b, load a。

Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:
  • 不允许read和load、store和write操作之一单独出现

  • 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。

  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。

  • 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。

  • 一个变量在同一时刻只允许一条线程对其进行lock操作,lock和unlock必须成对出现

  • 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值

  • 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。

  • 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。

参考: 

https://www.cnblogs.com/nexiyi/p/javamemorymodelandthread.html http://ifeve.com/java-memory-model-6/

为什么代码会重排序?

直接参考: 

https://www.cnblogs.com/toov5/p/9831696.html https://blog.youkuaiyun.com/qq_32646795/article/details/78221064

并发容器和框架

如何让一段程序并发的执行,并最终汇总结果?

使用CyclicBarrier 在多个关口处将多个线程执行结果汇总, CountDownLatch 在各线程执行完毕后向总线程汇报结果。

CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。

CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。

这样应该就清楚一点了,对于CountDownLatch来说,重点是那个“一个线程”, 是它在等待,而另外那N的线程在把“某个事情”做完之后可以继续等待,可以终止。而对于CyclicBarrier来说,重点是那N个线程,他们之间任何一个没有完成,所有的线程都必须等待。

从api上理解就是CountdownLatch有主要配合使用两个方法countDown()和await(),countDown()是做事的线程用的方法,await()是等待事情完成的线程用个方法,这两种线程是可以分开的(下面例子:CountdownLatchTest2),当然也可以是同一组线程;CyclicBarrier只有一个方法await(),指的是做事线程必须大家同时等待,必须是同一组线程的工作。


   
  1. import java.util.concurrent.CountDownLatch;

  2. import java.util.concurrent.ExecutorService;

  3. import java.util.concurrent.Executors;

  4. /**

  5. * 各个线程执行完成后,主线程做总结性工作的例子

  6. * @author xuexiaolei

  7. * @version 2017年11月02日

  8. */

  9. public class CountdownLatchTest2 {

  10.   private final static int THREAD_NUM = 10;

  11.   public static void main(String[] args) {

  12.       CountDownLatch lock = new CountDownLatch(THREAD_NUM);

  13.       ExecutorService exec = Executors.newCachedThreadPool();

  14.       for (int i = 0; i < THREAD_NUM; i++) {

  15.           exec.submit(new CountdownLatchTask(lock, "Thread-"+i));

  16.       }

  17.       try {

  18.           lock.await();

  19.       } catch (InterruptedException e) {

  20.           e.printStackTrace();

  21.       }

  22.       System.out.println("大家都执行完成了,做总结性工作");

  23.       exec.shutdown();

  24.   }

  25.   static class CountdownLatchTask implements Runnable{

  26.       private final CountDownLatch lock;

  27.       private final String threadName;

  28.       CountdownLatchTask(CountDownLatch lock, String threadName) {

  29.           this.lock = lock;

  30.           this.threadName = threadName;

  31.       }

  32.       @Override public void run() {

  33.           System.out.println(threadName + " 执行完成");

  34.           lock.countDown();

  35.       }

  36.   }

  37. }

CyclicBarrier例子:


   
  1. import java.util.concurrent.*;

  2. /**

  3. *

  4. * @author xuexiaolei

  5. * @version 2017年11月02日

  6. */

  7. public class CyclicBarrierTest {

  8.   private final static int THREAD_NUM = 10;

  9.   public static void main(String[] args) {

  10.       CyclicBarrier lock = new CyclicBarrier(THREAD_NUM, new Runnable() {

  11.           @Override public void run() {

  12.               System.out.println("这阶段大家都执行完成了,我总结一下,然后开始下一阶段");

  13.           }

  14.       });

  15.       ExecutorService exec = Executors.newCachedThreadPool();

  16.       for (int i = 0; i < THREAD_NUM; i++) {

  17.           exec.submit(new CountdownLatchTask(lock, "Task-"+i));

  18.       }

  19.       exec.shutdown();

  20.   }

  21.   static class CountdownLatchTask implements Runnable{

  22.       private final CyclicBarrier lock;

  23.       private final String threadName;

  24.       CountdownLatchTask(CyclicBarrier lock, String threadName) {

  25.           this.lock = lock;

  26.           this.threadName = threadName;

  27.       }

  28.       @Override public void run() {

  29.           for (int i = 0; i < 3; i++) {

  30.               System.out.println(threadName + " 执行完成");

  31.               try {

  32.                   lock.await();

  33.               } catch (BrokenBarrierException e) {

  34.                   e.printStackTrace();

  35.               } catch (InterruptedException e) {

  36.                   e.printStackTrace();

  37.               }

  38.           }

  39.       }

  40.   }

  41. }

如何合理的配置java线程池?如CPU密集型的任务,基本线程池应该配置多大?IO密集型的任务,基本线程池应该配置多大?用有界队列好还是无界队列好?任务非常多的时候,使用什么阻塞队列能获取最好的吞吐量?

虽然Exectors可以生成一些很常用的线程池,但毕竟在什么情况下使用还是开发者最清楚的。在某些自己很清楚的使用场景下,java线程池还是推荐自己配置的。下面是java线程池的配置类的参数,我们逐一分析一下:


   
  1. public ThreadPoolExecutor(

  2. int corePoolSize,

  3. int maximumPoolSize,

  4. long keepAliveTime,

  5. TimeUnit unit,

  6. BlockingQueue workQueue,

  7. ThreadFactory threadFactory,

  8. RejectedExecutionHandler handler

  9. )

  • corePoolSize - 池中所保存的线程数,包括空闲线程。

  • maximumPoolSize - 池中允许的最大线程数。

  • keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

  • unit - keepAliveTime 参数的时间单位。

  • workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。用BlocingQueue的实现类都可以。

  • threadFactory - 执行程序创建新线程时使用的工厂。自定义线程工厂可以做一些额外的操作,比如统计生产的线程数等。

  • handler - 饱和策略,即超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。策略有:Abort终止并抛出异常,Discard悄悄抛弃任务,Discard-Oldest抛弃最老的任务策略,Caller-Runs将任务退回给调用者策略。

至于线程池应当配置多大的问题,一般有如下的经验设置:
  1. 如果是CPU密集型应用,则线程池大小设置为N+1。

  2. 如果是IO密集型应用,则线程池大小设置为2N+1。

用有界队列好还是无界队列好?这种问题的答案肯定是视情况而定:
  1. 有界队列有助于避免资源耗尽的情况发生。但他带来了新的问题:当队列填满后,新的任务怎么办?所以有界队列适用于执行比较耗资源的任务,同时要设计好相应的饱和策略。

  2. 无界队列和有界队列刚好相反,在资源无限的情况下可以一直接收新任务。适用于小任务,请求和处理速度相对持平的状况。

  3. 其实还有一种同步移交的队列 SynchronousQueue ,这种队列不存储任务信息,直接将任务提交给线程池。可以理解为容量只有1的有界队列,在特殊场景下有特殊作用,同样得设计好相应的饱和策略。

参考:

https://blog.youkuaiyun.com/qq_34039315/article/details/78542498

如何使用阻塞队列实现一个生产者和消费者模型?请写代码。

下面这是一个完整的生产者消费者代码例子,对比传统的wait、nofity代码,它更易于理解。

ProducerConsumerPattern.java如下:


   
  1. import java.util.concurrent.BlockingQueue;

  2. import java.util.concurrent.LinkedBlockingQueue;

  3. import java.util.logging.Level;

  4. import java.util.logging.Logger;

  5. public class ProducerConsumerPattern {

  6.   public static void main(String args[]){

  7.    //Creating shared object

  8.    BlockingQueue sharedQueue = new LinkedBlockingQueue();

  9.    //Creating Producer and Consumer Thread

  10.    Thread prodThread = new Thread(new Producer(sharedQueue));

  11.    Thread consThread = new Thread(new Consumer(sharedQueue));

  12.    //Starting producer and Consumer thread

  13.    prodThread.start();

  14.    consThread.start();

  15.   }

  16. }

生产者,Producer.java如下:


   
  1. class Producer implements Runnable {

  2.   private final BlockingQueue sharedQueue;

  3.   public Producer(BlockingQueue sharedQueue) {

  4.       this.sharedQueue = sharedQueue;

  5.   }

  6.   @Override

  7.   public void run() {

  8.       for(int i=0; i<10; i++){

  9.           try {

  10.               System.out.println("Produced: " + i);

  11.               sharedQueue.put(i);

  12.           } catch (InterruptedException ex) {

  13.               Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);

  14.           }

  15.       }

  16.   }

  17. }

消费者,Consumer.java如下所示:


   
  1. class Consumer implements Runnable{

  2.   private final BlockingQueue sharedQueue;

  3.   public Consumer (BlockingQueue sharedQueue) {

  4.       this.sharedQueue = sharedQueue;

  5.   }

  6.   @Override

  7.   public void run() {

  8.       while(true){

  9.           try {

  10.               System.out.println("Consumed: "+ sharedQueue.take());

  11.           } catch (InterruptedException ex) {

  12.               Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);

  13.           }

  14.       }

  15.   }

  16. }

参考: 

https://www.cnblogs.com/expiator/p/9317929.html

多读少写的场景应该使用哪个并发容器,为什么使用它?比如你做了一个搜索引擎,搜索引擎每次搜索前需要判断搜索关键词是否在黑名单里,黑名单每天更新一次。

Java中的锁

如何实现乐观锁(CAS)?如何避免ABA问题?

CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”这其实和乐观锁的冲突检查+数据更新的原理是一样的。

这里再强调一下,乐观锁是一种思想。CAS是这种思想的一种实现方式。

ABA问题:

比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。

解决方法:通过版本号(version)的方式来解决,每次比较要比较数据的值和版本号两项内容即可。

读写锁可以用于什么应用场景?

在多线程的环境下,对同一份数据进行读写,会涉及到线程安全的问题。比如在一个线程读取数据的时候,另外一个线程在写数据,而导致前后数据的不一致性;一个线程在写数据的时候,另一个线程也在写,同样也会导致线程前后看到的数据的不一致性。

这时候可以在读写方法中加入互斥锁,任何时候只能允许一个线程的一个读或写操作,而不允许其他线程的读或写操作,这样是可以解决这样以上的问题,但是效率却大打折扣了。因为在真实的业务场景中,一份数据,读取数据的操作次数通常高于写入数据的操作,而线程与线程间的读读操作是不涉及到线程安全的问题,没有必要加入互斥锁,只要在读-写,写-写期间上锁就行了。

对于以上这种情况,读写锁是最好的解决方案!其中它的实现类:ReentrantReadWriteLock--顾名思义是可重入的读写锁,允许多个读线程获得ReadLock,但只允许一个写线程获得WriteLock

读写锁的机制:

"读-读" 不互斥

"读-写" 互斥

"写-写" 互斥

参考:

https://www.cnblogs.com/liang1101/p/6475555.html

什么时候应该使用可重入锁?

可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。

在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁。

参考:

http://ifeve.com/javalocksee4/

什么场景下可以使用volatile替换synchronized?

状态标志:把简单地volatile变量作为状态标志,来达成线程之间通讯的目的,省去了用synchronized还要wait,notify或者interrupt的编码麻烦。

替换重量级锁:如果某个变量仅是单次读或者单次写操作,没有复合操作(i++,先检查后判断之类的)就可以用volatile替换synchronized。

并发工具

如何实现一个流控程序,用于控制请求的调用次数?

   
  1. import java.util.Random;

  2. import java.util.concurrent.ExecutorService;

  3. import java.util.concurrent.Executors;

  4. import java.util.concurrent.Semaphore;

  5. /**

  6. * 阻塞访问的线程,直到获取了访问令牌

  7. * @author xuexiaolei

  8. * @version 2017年11月15日

  9. */

  10. public class FlowControl2 {

  11.   private final static int MAX_COUNT = 10;

  12.   private final Semaphore semaphore = new Semaphore(MAX_COUNT);

  13.   private final ExecutorService exec = Executors.newCachedThreadPool();

  14.   public void access(int i){

  15.       exec.submit(new Runnable() {

  16.           @Override public void run() {

  17.               semaphore.acquireUninterruptibly();

  18.               doSomething(i);

  19.               semaphore.release();

  20.           }

  21.       });

  22.   }

  23.   public void doSomething(int i){

  24.       try {

  25.           Thread.sleep(new Random().nextInt(100));

  26.           System.out.println(String.format("%s 通过线程:%s 访问成功",i,Thread.currentThread().getName()));

  27.       } catch (InterruptedException e) {

  28.       }

  29.   }

  30.   public static void main(String[] args) {

  31.       FlowControl2 web = new FlowControl2();

  32.       for (int i = 0; i < 2000; i++) {

  33.           web.access(i);

  34.       }

  35.   }

  36. }

(end)

【推荐阅读】

布隆过滤器实战!垃圾邮件识别?重复元素判断?缓存穿透?

Lucene&Solr&ElasticSearch-面试题

 Java 9 ← 2017,2019 → Java 13 ,来看看Java两年来的变化

永远不要在代码中使用「User」这个单词

(java思维导图)

长按关注,每天java一下,成就架构师

我就知道你在看!

【金三银四】Java并发编程面试题(2021最新版) 目录 前言 一、基础知识 1. 为什么要使用并发编程 2. 多线程应用场景 3. 并发编程有什么缺点 4. 并发编程三个必要因素是什么? 5. Java 程序中怎么保证多线程的运行安全? 6. 并行和并发有什么区别? 7. 什么是多线程 8. 多线程的好处 9. 多线程的劣势: 10. 线程和进程区别 11. 什么是上下文切换? 12. 守护线程和用户线程有什么区别呢? 13. 如何在 Windows 和 Linux 上查找哪个线程cpu利用率最高? 14. 什么是线程死锁 阅读详情

相关推荐

Java并发面试题-Java并发进阶

Java 并发进阶常见面试题总结:关键字解决的是多个线程之间访问资源的同步性,关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。另外,在 Java 早期版本中,属于重量级锁,效率低下。为什么呢?因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。

ma_firsttime的博客 966

Java并发高频面试题(2024最新版)

3. 如果在线程池中使用ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使用完之后,应该要把设置的key,value,也就是Entry对象进行回收,但线程池中的线程不会回收,而线程对象是通过强引用指向ThreadLocalMap,ThreadLocalMap也是通过强引⽤指向Entry对象,线程不被回收,Entry对象也就不会被回收,从而出现内存泄漏,解决办法是,在使⽤了ThreadLocal对象之后,手动调用ThreadLocal的remove⽅法,手动清楚Entry对象。

qq_42806650的博客 6239

高薪程序员&Java面试题精讲系列汇总

因为优快云没有分类归纳博客的功能,所以特写本帖汇总《高薪程序员面试题精讲系列教程》,方便大家查阅!希望各位小伙伴,可以从我的拙作中能对Java的高频面试题有所掌握,也希望各位可以多给与指教!目前本系列文章持续更新中,敬请关注...... 如果你觉得本系列博文对你有帮助,请给个 点赞评论 吧,手写博文不易,一篇文章可能得花一两天的时间才能写好,你的支持是我坚持下去的动力! 本系列面试题涵盖的内容如下所示: 面试题系列文章链接: 高薪程序员&面试题精讲系列01之开篇介绍 高薪程序员&a.

一一哥 3824

【Java并发面试】10道不得不会的Java并发基础面试题

1. start()方法和run()方法的区别 2. volatile关键字的作用 3. sleep方法和wait方法有什么区别 4. 如何停止一个正在运行的线程? 5. java如何实现多线程之间的通讯和协作?(如何在两个线程间共享数据?) 6. 什么是ThreadLocal? 7. Java中CyclicBarrier 和 CountDownLatch有什么不同? 8. 如何避免死锁? 9. Java中synchronized 和 ReentrantLock 有什么不同? 10. 有三个线程T1,T2,

JavaPub 4948

Java并发编程面试题--共87题

不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题。Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员的欢迎。大多数待遇丰厚的Java开发职位都要求开发者精通多线程技术并且有丰富的Java程序开发、调试、优化经验,所以线程相关的问题在面试中经常会被提到。 注:本文档根据http://www.importnew.com/12773.html 和http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-answers/ 这两个网站的并发编程试题集整理得到的文档,

Nacos漏洞利用工具V3.0.5实战:从认证绕开到内存马植入的全流程指南

本文详细解析了Nacos漏洞利用工具V3.0.5在实战中的应用,从认证绕开到内存马植入的全流程。文章深入探讨了如何利用Nacos的历史漏洞,如默认口令、认证逻辑绕过、Derby SQL注入及Jraft反序列化,实现权限提升与持久化控制,并提供了防御视角与加固建议,旨在帮助安全人员理解攻击链以加强防护。

h6j7k8l9p0的博客 484

【Java面试系列】2021年最新最全java面试题大全及答案解析

1.序言 学习如逆水行舟,尤其是IT行业有着日新月异的节奏,我们更要抓紧每一次可以学习和进步的机会。所以,没有撤退可言。 即使是面试跳槽,那也是一个学习的过程。只有全面的复习,才能让我们更好的充实自己,武装自己,为自己的面试之路不再坎坷!今天就给大家带来,较为全面的Java面试题大全! 2.面试大全 2.1Java面试题基础系列228道(上) 2.2Java面试题基础系列228道(下...

Yangy的博客 4万+

IC618新手避坑指南:从零在Linux上配置Cadence Virtuoso环境(含cds.lib文件详解)

本文详细介绍了在Linux系统上配置Cadence Virtuoso IC618环境的完整指南,特别针对新手常见问题提供解决方案。重点解析了cds.lib文件的关键配置,包括库路径定义和工艺库管理,并分享了高效工作流设置和典型问题排查技巧,帮助工程师快速搭建稳定的EDA设计环境。

weixin_26805597的博客 557

JAVA高并发多线程必须懂的50个问题

http://www.importnew.com/12773.html ImportNew 首页所有文章资讯Web架构基础技术书籍教程Java小组工具资源 Java线程面试题 Top 50 2014/08/21 | 分类: 基础技术 | 27 条评论 | 标签: 多线程, 面试题 分享到: 692 本文由 ImportNew - 李 广 翻译自 javarevisited。欢迎加入翻译小

u011163372的博客 5万+

从驱动编译到数据传输:RK3588与FPGA的PCIe通信实战解析

本文详细解析了RK3588与FPGA通过PCIe通信的实战过程,涵盖驱动编译、数据传输及性能优化等关键步骤。通过XDMA驱动实现高效DMA传输,适用于视频处理、数据采集等高性能场景,帮助开发者快速掌握嵌入式系统中的PCIe通信技术。

weixin_29214691的博客 528

TP4056在单节锂电池充电管理中的创新应用与电路优化

本文深入探讨了TP4056线性充电管理芯片在单节锂电池充电电路中的创新应用与优化方案。针对“边充边用”的核心挑战,文章详细解析了利用PMOS管构建智能供电切换电路的原理,实现了外部电源与电池供电的无缝、安全切换,有效杜绝了电池“边充边放”的风险。同时,提供了关键的元器件选型、PCB布局布线技巧及常见问题排查指南,为创客和硬件开发者提供了高性价比、高可靠性的锂电池电源管理实践方案。

rock5的博客 501

10、秒懂系列之 Java 并发编程面试题

守护线程是一个不阻止Java虚拟机(JVM)退出的线程当所有非守护线程终止时,JVM只是放弃所有剩余的守护线程;守护线程通常用于为其他线程执行一些支持或服务任务,但我们应该考虑到它们可能随时被放弃;要将一个线程作为守护线程启动,应该在调用start()之前使用setDaemon()方法设置为守护线程如下所示;"));额外的奇怪的是,如果将上面的代码放在 main() 内运行,则可能无法打印该消息。而发生这种情况的原因,是因为 main() 线程在守护线程运行到打印消息之前就已经终止。

深入Java世界:探索编程之美与技术深度 1012

从STM32F411到华大HC32F460:一个真实项目移植的踩坑与填坑全记录

本文详细记录了从STM32F411移植到华大HC32F460芯片的全过程,包括开发环境搭建、时钟树配置、中断处理、外设驱动、看门狗陷阱、Flash操作和低功耗设计等关键坑点及解决方案。特别针对HC32F460的200MHz主频、双PLL架构和HSRUN模式等特性进行了深入分析,为工程师提供实用的移植指南,帮助节省大量调试时间。

weixin_33691817的博客 421

Java并发面试题&知识点总结(上篇)

大家好,我是栗筝i,从 2022 年 10 月份开始,我便开始致力于对 Java 技术栈进行全面而细致的梳理。这一过程,不仅是对我个人学习历程的回顾和总结,更是希望能够为各位提供一份参考。因此得到了很多读者的正面反馈。而在 2023 年 10 月份开始,我将推出 Java 面试题/知识点系列内容,期望对大家有所助益,让我们一起提升。今天与您分享的,是 Java 并发知识面试题系列的总结篇(上篇),我诚挚地希望它能为您带来启发,并在您的职业生涯中起到助益作用。

栗筝i的博客 3114

MOS管的体二极管在LLC中的作用

MOSFET是一个三端器件(G、D、S),在N沟道MOSFET的结构中,源极和漏极之间的N型材料中自然形成了一个寄生的PN结,这个PN结就是体二极管(Body Diode),它是不可避免的器件结构。📌 对于N沟MOSFET:体二极管是从漏极 D 到源极 S 的正向导通二极管,方向为 D → S。MOS管的体二极管在LLC中并不是主功率通道,但却是实现软开关(ZVS)和能量过渡的关键“临时路径”。用得好,它是你的朋友;忽略它,电路就可能烧毁。

weixin_43199439的博客 702

详述 MIMIC 数据库26张数据表(一)

作者按照自身理解和实验要求对MIMIC-III的26张数据表进行整理归纳并进行介绍。

越努力 越幸运 7237

【搞定 Java 并发面试】面试最常问的 Java 并发基础常见面试题总结!

本文为 SnailClimb 的原创,目前已经收录自我开源的 JavaGuide 中(61.5 k Star!【Java学习 面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。欢迎 Star!)。 另外推荐一篇原创:终极推荐!可能是最适合你的Java学习路线 方法 网站 书籍推荐! Java 并发基础常见面试题总结 1. 什么是线程和进程? 1.1. 何为进程? 进程是程...

JavaGuide 1万+

拉曼光谱入门:7.拉曼光谱仪光学系统结构详解

本文系统拆解了拉曼光谱仪从激光器输出经聚焦照明、二向色镜/陷波滤光片组滤除瑞丽散射、衍射光栅色散至CCD探测的完整光路,阐述了各光学模块在微弱非弹性散射信号提取、杂散光抑制及高分辨光谱成像中的工程原理与技术参数要求。

qq_53529450的博客 225

一文整理总结常见Java后端面试题系列——Java并发篇(2022最新版)

初始(NEW):新创建了一个线程对象,但还没有调用start()方法;运行(RUNNABLE):Java 线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”;线程对象创建后,其它线程(如 main 线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 CPU的 使用权,此时处于就绪状态(ready)。就绪状态的线程在获得 CPU 时间片后变为运行中状态(running)。阻塞(BLOCKED):表示线程阻塞于锁;

程序猿周周的博客 1万+

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

【搞定 Java 并发面试】面试最常问的 Java 并发进阶常见面试题总结!

本文为 SnailClimb 的原创,目前已经收录自我开源的 JavaGuide 中(61.5 k Star!【Java学习 面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。觉得内容不错再 Star!)。 另外推荐一篇原创:终极推荐!可能是最适合你的Java学习路线 方法 网站 书籍推荐! Java 并发进阶常见面试题总结 1. synchronized 关键字 1.1...

JavaGuide 7500

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

【Java面试系列】Java并发系列面试题

目录 1、在 java 中守护线程和本地线程区别? 2、线程与进程的区别? 3、什么是多线程中的上下文切换? 4、死锁与活锁的区别,死锁与饥饿的区别? 6、什么是线程组,为什么在 Java 中不推荐使用? 7、为什么使用 Executor 框架? 8、在 Java 中 Executor 和 Executors 的区别? 9、如何在 Windows 和 Linux 上查找哪个线程...

Yangy的博客 1万+

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

一文秒懂系列之 Java 并发编程面试题

守护线程是一个不阻止 Java 虚拟机 ( JVM ) 退出的线程。当所有非守护线程终止时,JVM 只是放弃所有剩余的守护线程。守护线程通常用于为其他线程执行一些支持或服务任务,但我们应该考虑到它们可能随时被放弃。要将一个线程作为守护线程启动,应该在调用start()之前使用方法设置为守护线程。如下所示"));中断 ( interrupt ) 标志或中断状态是线程中断时设置的内部线程标志 (flag属性 )。要设置一个线程的中断标志,只需要简单的在线程对象上调用方法。

smart_an的专栏 765

Java并发编程面试题(一)

Java并发编程75道面试题及答案 阿里常用Java并发编程面试试题 Java并发编程必懂知识点解析 Java并发编程面试题 Top 50 整理版 投行的15个多线程和并发面试题 ...

大鹏视界U 1811

2025年Java秋招面试必看的 | Java并发编程 面试题

然而,面对厚重的理论书籍,我感到有些无从下手。当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,Java 虚拟机就会把这个线程放到这个对象的锁池中,这涉及到“线程同步”的内容。线程组和线程池是两个不同的概念,他们的作用完全不同,前者是为了方便线程的管理,后者是为了管理线程的生命周期,复用线程,减少创建销毁线程的开销。当线程处于运行状态时,如果执行了某个对象的 wait()方法,Java 虚拟机就会把线程放到这个对象的等待池中,这涉及到“线程通信”的内容。

mxt51220的博客 882

秒懂系列之 Java 并发编程面试题

1、守护线程是一个不阻止Java虚拟机(JVM)退出的线程当所有非守护线程终止时,JVM只是放弃所有剩余的守护线程;2、守护线程通常用于为其他线程执行一些支持或服务任务,但我们应该考虑到它们可能随时被放弃;3、要将一个线程作为守护线程启动,应该在调用start()之前使用setDaemon()方法设置为守护线程如下所示;"));1、中断(interrupt)标志或中断状态是线程中断时设置的内部线程标志(flag属性);2、

二师兄撒欢之地 533

Java并发编程面试题大全

Java并发编程核心要点摘要 本文系统介绍了Java并发编程的核心知识点,主要包括: 并发基础:解释了并发编程概念、串行与并行的区别、适用场景及三大特性(原子性、可见性、有序性) 线程机制:详细说明线程状态转换、创建方式(继承Thread/实现Runnable)、中断操作的正确实现,并对比了进程与线程的区别 线程安全:重点分析了i++非原子性问题,从字节码层面剖析线程不安全原因,并介绍了synchronized、volatile等同步机制 实用技术:涵盖守护线程、线程优先级设置、线程挂起恢复的现代实现方式等

weixin_39956506的博客 772
上一篇: 布隆过滤器实战!垃圾邮件识别?重复元素判断?缓存穿透?
下一篇: 如何设计一个本地缓存
Java思维导图
博客等级 码龄10年 306粉丝 71原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值