目录

常见概念:

一.使用多线程的几种方式?

二.如果我既重写了Thread类的run()方法,又重写了Runnable接口中的run()方法,应该怎么打印,如下?

三.说一下线程的几种状态?

四.如何实现多线程同步?

五.谈谈线程死锁,如何有效的避免死锁?

六.谈谈线程阻塞的原因?

七.请谈一谈线程中的run()方法和start()方法的区别?

八.说一下synchronized关键字和volatile关键字的区别?

九.如何保证线程安全?

十.Java多线程中notify()和notifyAll()的区别?

十一.什么是线程池?如何创建线程池?

十二.那么为什么要使用线程池呢?

十三.谈一谈Java中常见的几种锁?已经他们的应用场景?

十四.sleep()方法和wait()方法区别?

十五.什么是乐观锁和悲观锁?

十六.什么是BlockingQueue?说一下原理及应用场景?

十七.谈一谈java线程安全的集合有哪些?他们分别有什么特点?

十八.Java中为什么会出现AtomicXXX类?试分析下它的原理和缺点?

十九.什么是JMM?它存在是为了解决什么问题?

二十.ThreadLocal的用法及原理?


JMM参考资料:JMM概述_牧竹子的博客-CSDN博客_jmm

参考资料:https://blog.csdn.net/gongjdde/category_10948630.html?spm=1001.2014.3001.5482

常见概念:

JMM:内存模型可以理解为在特定的操作协议下,对特定的内存或者高速缓存进行读写访问的过程抽象描述,不同架构下的物理机拥有不一样的内存模型,Java虚拟机是一个实现了跨平台的虚拟系统,因此它也有自己的内存模型,即Java内存模型(Java Memory Model, JMM)。

happen-before:这个模型就是一个规范,为了描述这个规范,JMM引入了happen-before

内存屏障:为了禁止编译器重排序和cpu重排序,在编译器和cpu层面都有对应的指令,也就是内存屏障,这也正是JMM和happen-before规则的底层实现原理

线程安全的三个特性:原子性、可见性和有序性

原子性:操作不可中断

可见性:一个线程修改状态对另一个线程可见

有序性:Java内存模型中允许编译器和处理器对指令进行重排序,禁止重排序就是具有有序性

一.使用多线程的几种方式?

1.extend Thread,继承线程然后重写run()方法。

Thread thread = new Thread() { @Override public void run() { System.out.println("Thread started!"); }
};
thread.start();

2.impl Runnable,实现Runnable接口,然后实现run()方法。

Runnable runnable = new Runnable() { @Overridepublic void run() { System.out.println("Thread with Runnable started!");}
};
Thread thread = new Thread(runnable);
thread.start();

3.通过Excutors工具类创建线程池,常见的如下:

newFixedThreadPool(int nThreads):创建固定数量的线程池
newCachedThreadPool():创建缓存线程池
newSingleThreadExecutor():创建单个线程
newScheduledThreadPool(int corePoolSize):创建定时器线程池

4.通过Callable接口,重写call()方法,Callable属于Excutor框架功能类

二.如果我既重写了Thread类的run()方法,又重写了Runnable接口中的run()方法,应该怎么打印,如下?

public static void main(String[] args) {new Thread(() -> System.out.println("Runnable run")) {@Overridepublic void run() {System.out.println("Thread run");}}.start();
}

会打印Thread run,不会打印Runnable run。

原理是先看下Thread类中的run()方法源码,如下:

    @Overridepublic void run() {if (target != null) {target.run();}}

其中target就是传入的Runnable,如果重写了Thread的run()方法,就不会调用到原先的Thread的run()方法,自然不会走到Runnable调用的run()方法。

三.说一下线程的几种状态?

新建(New):新建一个线程。

运行(Runnable):t.start(),线程调用start()方法,注意调用start()方法不会立刻运行起来线程,而是进入到一个Ready(就绪)阶段,然后cpu启动线程进入Running(运行)阶段。

阻塞(Blocked):被synchronized锁住,表示线程阻塞于锁。

等待(Waiting):表示该状态需要等待其他线程通知或者中断。

超时等待(Timed_Waiting):该状态可以在特定时间后自行返回,也可以在特定时间内通过其他线程通知或者中断结束该状态。

终止(Terminated):表示该线程已经执行完成。

如下图:

参考资料:Java线程的6种状态及切换(透彻讲解)_潘建南的博客-CSDN博客_线程状态

四.如何实现多线程同步?

1.Synchronized,对代码块或者方法加锁,可以实现多线程读写同步。

2.ReentrantLock,更加精确的锁,可以加锁并且手动释放锁,也可以设置超时,可以实现多线程读写同步。

3.volatile关键词,轻量级的弱机制同步,可以保证可见性有序性不能保证原子性,在get和set的场景下是可以的,由于get和set的时候都加了读写内存屏障,在数据可见性上保证数据同步。不能有类似于++这种具有当前变量的多步操作,数据会出现不同步现象,此变量也不能包含在其他变量的不变式中。

4.cascompare and set(比较并置换),将比较和置换合成一个原子操作,是乐观锁的一种方式保证多线程同步。

5.CountDownLatch:可以让一个线程等待另一个线程,实现同步。

6.Handler:可以切换到同一线程上保证数据是同步的,避免在多线程出现脏数据

参考:2019-03-15:如何实现多线程中的同步? · Issue #6 · Moosphan/Android-Daily-Interview · GitHub

五.谈谈线程死锁,如何有效的避免死锁?

1.死锁的定义:所谓死锁是多线程或者多进程竞争资源而造成一种僵局(互相等待),若无外力作用,这些进程或线程一直无法向前推进

2.什么情况下出现死锁,如下:

  public void method1() {synchronized (String.class) {System.out.println("请求一个被 String.class 锁住的的方法");synchronized (Integer.class) {System.out.println("请求一个被 Integer.class 锁住的的方法");}}}public void method2() {synchronized (Integer.class) {System.out.println("请求一个被 Integer.class 锁住的的方法");synchronized (String.class) {System.out.println("请求一个被 String.class 锁住的的方法");}}}

线程A调用method1()方法获取到String.class锁,线程B同时调用了method2()方法获取到Integer.class锁。此时线程A继续获取锁Integer.class获取不到,因为被线程B持有,而线程B继续获取String.class锁也获取不到,此时被线程A持有。所以此时就造成了线程A在等待线程B,线程B在等待线程A的现象,此时就陷入僵局,导致死锁。

3.如何避免死锁:

①.注意加锁顺序,比如上面的两个方法加锁顺序一致就不会造成死锁。

②.可以设置超时,用ReentrantLock。

③.在生产者消费者模型中,通过notifyAll()可以比notify()减少死锁现象。

④.死锁的检测,有两个方案,

一.是记录线程持有锁和需要请求的锁,当发生需要请求锁迟迟等待不到时检测其他线程是否持有此锁,并且其他线程如果持有此锁然后检测是否在请求当前线程获取得到的锁,如果此线程当前请求的锁是当前线程持有的锁则发生死锁。

二.Linux 死锁检测模块 Lockdep ,比较复杂,参考:Linux 死锁检测模块 Lockdep 简介 - 魅族内核团队

4.怎么排查出现死锁。

日志中有:is held by “线程1”,说明是线程1持有锁

或者有:

- waiting to lock <0x00000000d613fa60> (a java.lang.String)

- locked <0x00000000d613fa98> (a java.lang.String)

可以看出当前线程持有锁和需要请求的锁

根据is held by判断线程之间是否发生死锁,或者根据线程请求锁和当前持有锁判断是否发生死锁。

日志参考:多线程【死锁案例及死锁的排查】 - 跑调大叔! - 博客园

参考 :2019-04-30:谈谈线程死锁,如何有效的避免线程死锁? · Issue #43 · Moosphan/Android-Daily-Interview · GitHub

六.谈谈线程阻塞的原因?

1.当前线程请求的(synchronized,ReentrantLock)被其他线程占用导致阻塞。

2.当前线程调用Thread.sleep(int n)方法放弃cpu进入休眠状态,休眠n秒后继续运行。

3.当前线程调用wait()方法进入等待状态,等其他线程调用notify()或者notifyAll()方法唤醒

4.使用CountDownLatch使一个线程等待另一个(或多个)线程执行完成然后执行。

5.线程IO或者进行远程通信时,会因为等待资源而进行阻塞状态。

参考:https://github.com/Moosphan/Android-Daily-Interview/issues/63

七.请谈一谈线程中的run()方法和start()方法的区别?

run()方法一般指的是重写Thread类中的run()方法或者是Runnable接口中的run()方法,如果是调用run()方法则没有意义。

start()方法是启动线程的方法,此时线程处于就绪状态,一旦得到cpu时间片则此线程进入运行状态,此时会执行run()方法,直到执行完成,线程终止。

八.说一下synchronized关键字和volatile关键字的区别?

1.本质上volatile是告诉虚拟机当前变量在寄存器(工作内存)中的值是不确定的,是失效的,需要从主存中获取synchronized是给线程加锁,只有获取锁的线程可以访问,其他线程被阻塞。

2.volatile仅能使用在变量级别,synchronized可以使用在变量、方法、代码块级别。

3.volatile可以保证可见性和有序性,不能保证原子性synchronized可以保证线程的原子性、可见性、有序性。

4.volatile不会造成线程阻塞,synchronized会造成线程阻塞。

九.如何保证线程安全?

1.Synchronized,对代码块或者方法加锁。

2.ReentrantLock,更加精确的锁,可以加锁并且手动释放锁,也可以设置超时

3.volatile关键词,轻量级的弱机制同步,可以保证可见性有序性不能保证原子性,在get和set的场景下是可以的,由于get和set的时候都加了读写内存屏障,在一个线程写多个线程读的情况下可以保证线程安全。不能有类似于++这种具有当前变量的多步操作,数据会出现不同步现象,不能保证线程安全

4.cascompare and set(比较并置换),将比较和置换合成一个原子操作,是乐观锁的一种方式保证线程安全的方式。

5.ThreadLocal:线程本地存储。如果一段代码中所需的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行?如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内。这样无需同步也能保证线程之间不出现数据的争用问题。

6.Handler:可以切换到同一线程上保证数据是同步的,避免在多线程出现脏数据

参考:【多线程】如何保证线程安全_LemmonTreelss的博客-CSDN博客_如何保证线程安全

十.Java多线程中notify()和notifyAll()的区别?

notify():只唤醒等待该锁的其中一个线程。

notifyAll():唤醒等待该锁的所有线程。

为什么可以唤醒其中一个线程并且获取锁还需要一个唤醒等待该锁的所有线程的方法呢。

因为对象内部锁中对象有两个池:锁池和等待池,在等待池中的线程需要唤醒进入锁池然后才能去获取锁。

锁池:假如已经有线程A获取到了锁,这时候又有线程B需要获取这把锁(比如需要调用synchronized修饰的方法或者需要执行synchronized修饰的代码块),由于该锁已经被占用,所以线程B只能等待这把锁,这时候线程B将会进入这把锁的锁池。
等待池:假设线程A获取到锁之后,由于一些条件的不满足(例如生产者消费者模式中生产者获取到锁,然后判断队列为满),此时需要调用对象锁的wait方法,那么线程A将放弃这把锁,并进入这把锁的等待池。

所以如果生产者线程阻塞调用notify()然后唤醒另一个生产者线程(或者消费者线程唤醒的是另一个消费者线程),则可能出现一直在等待池现象,出现死锁

如果是调用notifyAll()则是唤醒所有线程,不会出现死锁。

参考:https://blog.csdn.net/gongjdde/article/details/123792823

十一.什么是线程池?如何创建线程池?

线程池:线程池顾名思义就是有很多线程的池子,如果有任务可以在池子中寻找空闲线程执行任务,执行完成可以继续放在池子中进行复用,也可以终止线程,进行销毁。

线程池创建:可以通过Executors工具类创建线程池,如下:

newFixedThreadPool(int nThreads):创建固定数量的线程池
newCachedThreadPool():创建缓存线程池
newSingleThreadExecutor():创建单个线程
newScheduledThreadPool(int corePoolSize):创建定时器线程池

但是上面的创建方式有一定弊端,在极端情况下会OOM,原因:

newFixedThreadPool与newSingleThreadExecutor:请求队列最大长度为Integer.MAX_VALUE(无界),所以当有非常大量的任务时候会堆积大量请求,导致OOM。

newCachedTheadPool与newScheduledThreadPool:最大线程数是Integer.MAX_VALUE(无界),所以在非常大量任务时候会堆积大量线程,导致OOM。

所以推荐new ThreadPoolExecutor()方式创建线程池,最多参数构造方法如下:

ThreadPoolExecutor(int corePoolSize,                    //核心线程数int maximumPoolSize,                 //最大线程数long keepAliveTime,                  //核心线程保活时间TimeUnit unit,                       //时间单位BlockingQueue<Runnable> workQueue,   //阻塞队列ThreadFactory threadFactory,         //线程工厂:主要给线程起名RejectedExecutionHandler handler)    //饱和策略

十二.那么为什么要使用线程池呢?

1、减小消耗和提高效率:如果每当一个请求到达就创建一个新线程,开销是相当大的。在实际使用中,要是要用一个线程就去创建一个线程,那么服务器在创建和销毁线程上就会花费很多时间和消耗系统资源。

2、合理创建线程数:可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,使服务器崩溃。

3、方便管理线程:要用线程就从线程池里获取十分方便,举个例子:创建线程池就相当于银行经理招了一些业务员,业务员去服务顾客,银行经理直接管理那么几个业务员是十分方便管理的,而业务员可以为很多顾客不断的服务,就相当于线程不断得执行任务一样。

4、提高响应速度: 当任务到达时,任务可以不需要的等到线程创建就能立即执行。

十三.谈一谈Java中常见的几种锁?已经他们的应用场景?

1.synchronized:互斥锁,jdk1.6之前是重量级锁,jdk1.6及之后对锁进行了优化,锁会由偏向锁->轻量级锁->重量级锁升级。适合各个多线程访问同一资源的场景。

2.ReentrantLock:互斥锁,可以自行释放锁和添加锁超时以及修改成公平锁等。适用于需要更加高精度控制锁的情景。

3.ReentrantReadWriteLock:读写锁,读与读不互斥,读与写互斥,写与写互斥。适用于读多写少的场景。

4.StampedLock:读写改进锁,是jdk1.8中添加的锁,类似于读写锁,不过添加了乐观锁思想。在没有获取写锁的时候可以使用乐观锁读取,在获取写锁的时候可以使用读锁读取。适用于并发量少的写少读多的场景。

参考:StampedLock的理解和使用 - 沉默的背影 - 博客园

以上2.3.4参考:第三章 Lock与Condition_龚礼鹏的博客-CSDN博客

5.CAS乐观锁系列AtomicInteger系列、AtomicStampedReference系列、LongAdder系列等等。AtomicInteger适合简单的自增场景,AtomicStampedReference解决了AtomicInteger的ABA问题,LongAdder适用于最终一致性场景。

参考:第二章 Atomic类_龚礼鹏的博客-CSDN博客

十四.sleep()方法和wait()方法区别?

1.sleepThread的静态方法,所以通过对象名.sleep()调用并不会使得该对象线程进入休眠,而是当前线程进入休眠,wait()是Object类中的方法,因为锁对象是任意的,而Object类是所有类的基类。
2.sleep()调用之后不释放锁wait()方法调用之后释放锁
3.sleep()方法必须要传入参数,而wait()方法可以传入也可以不传入参数,参数即为时间。
4.sleep()方法可以用在任何地方wait()方法只能用在同步方法或者同步代码块中。因为wait(),notify(),notifyAll()都是对锁对象所在的线程进行操作,必须先要有锁对象。
5.sleep()方法不需要唤醒,在一定时间后(即参数),该线程会自动醒来,wait()无参数的方法必须由notify()或者notifyAll()来唤醒,如果是wait(long timeout)有参数的则在一定时间后可以自动唤醒

十五.什么是乐观锁和悲观锁?

乐观锁:默认为,某个线程在自己处理共享资源的时候,不会出现同一时刻来修改此资源的前提,只在处理完毕,最后写入内存的时候,检测是否此资源在之前未被修改,则写入修改的值,否则根据不同的实现方式执行不同的操作(例如报错或者自动重试),类似于读写锁的读锁AtomicInteger就是乐观锁。

悲观锁:默认为,某个线程在自己处理共享资源的时候,一定会出现同一时刻来修改此资源,所以刚拿到这个资源就直接加锁,不让其他线程来操作,加锁在逻辑处理之前。类似,synchronized关键字(jdk1.6之前),条件锁,数据库的行锁,表锁等就是悲观锁。

参考:史上最全 Java 中各种锁的介绍_龚礼鹏的博客-CSDN博客

十六.什么是BlockingQueue?说一下原理及应用场景?

BlockingQueue:具有阻塞功能的队列

原理:基本都是利用ReentrantLock锁Condition条件配合实现队列的阻塞和唤醒功能的。

具体实现类的原理如下:

ArrayBlockingQueue:基于数组的阻塞队列实现。内部是基于一个定长的数组实现的,以便缓存队列中的数据,除了数组内部还有记录首尾位置两个参数,然后是一把锁两个Condition类型的条件,分别是notFull和notEmpty,然后还有一个count参数是记录当前队列中元素数。添加(put)数据时当数据满了会调用notFull.await();进行阻塞等待,当取出(take)数据时会通过notFull.signal();唤醒不为满的条件;同理当队列为空的时候添加数据会唤醒队列不为空的阻塞等待。注意ArrayBlockingQueue此处是用到了一把锁,所以生产者和消费者的操作是不能并行执行的,这个是和LinkedBlockingQueue的最大区别。

LinkedBlockingQueue:基于链表的阻塞队列实现。内部是基于单向链表实现的,内部参数有两个头和尾的Node(节点),便于快速的插入和删除数据;和ArrayBlockingQueue一样有个count参数记录当前队列中元素数,不过此处countAtomicInteger类型;然后是两把锁和两个条件,阻塞原理和ArrayBlockingQueue类似,不过由于此处添加数据和取出数据是两个不同的锁,所以put和take操作可以并行的,由于count是put和take都会操作的公共资源,所以count是AtomicInteger类型,通过cas原理保证count是原子操作的。

上面是常用了两个阻塞队列,除了上面的两个阻塞队列,还有PriorityBlockingQueue(支持优先级的阻塞队列)、DelayQueue(按照时间从小到大出队的有序队列)、SynchronousQueue(不存储元素的阻塞队列)

应用场景:阻塞队列应用于生产者消费者的场景,一般常见的是线程池中。如下:

FixedThreadPool线程池SingleThreadExecutor中用到的是LinkedBlockingQueue阻塞队列,这样可以存放核心线程未来得及处理的任务。

CacheThreadPool线程池中使用的是SynchronousQueue阻塞队列,这样可以有很大的吞吐量,可以快速的让最大线程来处理任务。

ScheduledThreadPool线程池中使用DelayQueue,可以让任务按照定时器时间来排序执行。

参考:第五章 并发容器_龚礼鹏的博客-CSDN博客

https://github.com/Moosphan/Android-Daily-Interview/issues/194

十七.谈一谈java线程安全的集合有哪些?他们分别有什么特点?

1.早期的一些线程安全的集合:Vector是线程安全的ArrayList,原理是全部方法加synchronizedHashTable是线程安全的HashMap,原理是全部方法加synchronized

2.利用Collections.synchronizedXXX()系列,直接给HashMap、ArrayList等等加synchronized

3.CopyOnWriteArrayList也是安全的ArrayList,是读不加锁写的时候加锁并且复制一份数组操作,可能会出现读的时候数据未及时更新情况,但是效率相对于前两种较高。

4.ConcurrentHashMap也是安全的HashMap,在Java 7中利用分段锁(segment)的思想将HashMap分成很多段,每段之间互不影响,并发度较高,在Java 8中利用synchronized+cas锁的方式替换分段锁思想,并且数据结构添加了红黑树,这样解决了原先遍历链表效率低问题,然后synchronized只会锁住当前链表或者红黑树的头节点,可以理解为与当前持有锁操作的没有hash冲突就不会有并发操作,大大提高了效率。

十八.Java中为什么会出现AtomicXXX类?试分析下它的原理和缺点?

1.为什么会出现AtomicXXX:由于我们经常使用i++这样的操作,但是这个操作并不是原子操作,在多线程并发的时候会出现线程不安全的情况。这时候如果对i++操作进行加锁(synchronized)的话,在java 6之前是重量级锁,在线程并发度低的情况下比较影响效率,在java 6及之后才对synchronized进行了优化,效率大大提升,但是对于这种单个变量加锁还是有更优的方式,并且有些地方这种++操作并不适合加锁,比如在LinkedBlockingQueue的阻塞队列中有两个锁的共享变量count需要++,此时加锁就不太合适。所以出现了AtomicInteger类,用于线程安全的++操作

2.原理:主要原理是CAS(比较并置换),主要是有三个值,内存中的值(V)、加载到线程中预期的值(A)、计算结果后得到的值(B),如果V等于A,则将B写入进去,如果不相等则重新将V的值写入A中继续重复计算B的值,进行自旋操作。

底层原理是CAS这个操作是CAS 是通过调用 JNI的代码实现的,是原子操作

3.缺点:

①.会出现AtomicInteger会出现ABA问题

解决方案:可以通过添加版本号的方式解决,JDK 1.5开始提供AtomicStampedReference方式,添加版本号解决。

②.如果在多并发操作中,可能出现很多线程一直自旋,这样效率也比较低。

解决方案:推荐使用synchronized。当然jdk 1.8提供了LongAdder类采用分段锁的方式,这样效率也较高。

③.AtomicInteger共享变量是一个变量,不能多个变量一起进行原子操作。

解决方案:jdk1.5提供了AtomicReference,可以将多个共享变量放在一个类中进行原子操作。

十九.什么是JMM?它存在是为了解决什么问题?

JMM:内存模型可以理解为在特定的协议下,对内存或者高速缓存进行读写的一种抽象的描述,不同架构的物理机有不同的内存模型,因为Java虚拟机是一个跨平台虚拟机,所以有自己的内存模型,即JMM(Java 内存模型)。

内存模型是围绕多线性并发下的原子性、可见性、有序性建立的,为了解决高并发下的线程安全问题。

二十.ThreadLocal的用法及原理?

用法:新建ThreadLocal实例,然后在线程1和线程2中通过ThreadLocal调用set()方法塞入数据,然后调用ThreadLocal的get()方法获取数据;可以发现线程1获取的是线程1塞入的数据,线程2获取的是线程2塞入的数据。示例如下:

public class Main {public static void main(String[] args) {ThreadLocal<String> threadLocalOne = new ThreadLocal<>();ThreadLocal<String> threadLocalTwo = new ThreadLocal<>();new Thread(new Runnable() {@Overridepublic void run() {threadLocalOne.set("线程一的数据 --- threadLocalOne");threadLocalTwo.set("线程一的数据 --- threadLocalTwo");System.out.println(threadLocalOne.get());System.out.println(threadLocalTwo.get());}}).start();new Thread(new Runnable() {@Overridepublic void run() {System.out.println(threadLocalOne.get());System.out.println(threadLocalTwo.get());threadLocalOne.set("线程二的数据 --- threadLocalOne");threadLocalTwo.set("线程二的数据 --- threadLocalTwo");System.out.println(threadLocalOne.get());System.out.println(threadLocalTwo.get());}}).start();}
}

打印结果:

线程一的数据 --- threadLocalOne
线程一的数据 --- threadLocalTwo
null
null
线程二的数据 --- threadLocalOne
线程二的数据 --- threadLocalTwo

原理:ThreadLocal塞入的数据是放到线程的成员变量中的,与线程实例绑定的,所以可以实现线程隔离。具体描述是在线程中有一个ThreadlocalMap的成员变量,类似于HashMap,不过它是数组结构的,hash冲突后是用开放地址法解决冲突的,ThreadlocalMap的key是ThreadLocal,value是数据。

参考:ThreadLocal的奇思妙想

Java多线程及锁相关面试题相关推荐

  1. java书籍_还搞不定Java多线程和并发编程面试题?你可能需要这一份书单!

    点击蓝色"程序员书单"关注我哟 加个"星标",每天带你读好书! ​ 在介绍本书单之前,我想先问一下各位读者,你们之前对于Java并发编程的了解有多少呢.经过了1 ...

  2. JAVA多线程提高十四: 面试题

    前面针对多线程相关知识点进行了学习,那么我们来来看看常见的面试题: 1. 空中网面试题1 package com.kongzhongwang.interview; import java.util.c ...

  3. JAVA多线程和并发基础面试题

    多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题.(校对注:非常赞同这个观 ...

  4. JAVA面试题|JAVA锁相关面试题总结(一)

    JAVA基础篇面试题 文章目录 JAVA基础篇面试题 1. 什么是JMM 2. 介绍一下violated 3. 写一个单例模式 4. 介绍一下CAS 5. CAS的问题 6. ArrayList线程不 ...

  5. 【2022版】Java多线程与高并发面试题总结,108道题含答案解析。

    前言 最近面试的小伙伴很多,对此我整理了一份Java面试题手册:基础知识.JavaOOP.Java集合/泛型面试题.Java异常面试题.Java中的IO与NIO面试题.Java反射.Java序列化.J ...

  6. MySQL锁相关面试题

    对MySQL的锁了解吗? 当数据库有并发事务的时候,可能会产生数据的不一致,这时候需要一些机制来保证访问的次序,锁机制就是这样的一个机制. 例子:就像酒店的房间,如果大家随意进出,就会出现多人抢夺一个 ...

  7. Java多线程常见面试题及答案汇总1000道(春招+秋招+社招)

    Java多线程面试题以及答案整理[最新版]Java多线程高级面试题大全(2021版),发现网上很多Java多线程面试题都没有答案,所以花了很长时间搜集,本套Java多线程面试题大全,汇总了大量经典的J ...

  8. java 面试题 生产者 消费者_面试大厂必看!就凭借这份Java多线程和并发面试题,我拿到了字节和美团的offer!...

    最近好多粉丝私信我说在最近的面试中老是被问到多线程和高并发的问题,又对这一块不是很了解,很简单就被面试官给问倒了,被问倒的后果当然就是被刷下去了,因为粉丝要求,我最近也是花了两天时间 给大家整理了这一 ...

  9. Java多线程:synchronized | Volatile 和Lock和ReadWriteLock多方位剖析(一)

    前言 本文站在多线程初中级学习者的角度,较为全面系统的带你一起了解多线程与锁相关的知识点.带你一起解开与锁相关的各种概念.用法.利弊等.比如:synchronized.Volatile.Lock.Re ...

  10. 面试姊妹篇4:常见的Java多线程面试题

    主要内容 本文主要记录多线程相关的操作问题,这些问题主要出现在一些面试中,当然学会了对本身的代码能力也有提升. 目录 1.交替多线程 2.发令枪问题 3.多线程顺序执行 4.关于饥饿死锁 5.线程数设 ...

最新文章

  1. 让程序员头疼的文档问题怎么破?试试活文档
  2. linux 库函数拦截,如何使用net_dev_add()API过滤和拦截Linux数据包?
  3. oracle if后面为null,Oracle中NVL2 和NULLIF的用法
  4. 【论文学习】高频分量有助解释卷积神经网络泛化 High-frequency Component Helps Explain the Generalization of CNN
  5. CMOS图像传感器——相位对焦
  6. HDOJ 1233 (克鲁斯卡尔+并查集)
  7. 深度 | 数据仓库分层存储技术揭秘
  8. 基本shell编程【3】- 常用的工具awk\sed\sort\uniq\od
  9. win7安装TensorFlow-gpu 2.3详细教程(CUDA10.1,cuDNN7)
  10. java jar包与配置文件的写法
  11. 腾讯:人们回归工作导致四季度游戏收入减缓
  12. SpringBoot-@ConfigurationProperties注解
  13. protobuf协议_gRPC 使用 protobuf 构建微服务
  14. #Leetcode# 141. Linked List Cycle
  15. 敏捷开发框架_测试经理必知必会:敏捷开发3355原则
  16. 重写(Overriding)
  17. 使用Modern UI for WPF的导航功能
  18. 宇宙简史|生物学家也要了解的物理
  19. Zabbix3.4 通过163邮箱发送邮件监控报警
  20. (附源码)计算机毕业设计SSM加油站管理信息系统

热门文章

  1. [设计一个24GHz微带天线]书本公式介绍并用Octave计算初始值
  2. CGLIB 动态代理使用
  3. linux天气软件,Ubuntu 18.04 6款查询天气的小工具推荐(适用于其它Linux)
  4. ADAMS 脚本仿真
  5. MATLAB数值微积分与方程求解
  6. Sublime快捷键 PyV8的下载与安装
  7. 概率图模型--马尔可夫随机场
  8. socket通信压力测试
  9. 三宝机器人怎么充电_三宝机器人说明书
  10. centos lnmp一键安装