Java 并发编程面试一

目录

一、在 java 中守护线程和本地线程区别?

二、线程与进程的区别?

三、什么是多线程中的上下文切换?

四、死锁与活锁的区别,死锁与饥饿的区别

五、Java 中用到的线程调度算法是什么?

六、什么是线程组,为什么在 Java 中不推荐使用?

七、为什么使用 Executor 框架?

八、在 Java 中 Executor 和 Executors 的区别?

九、什么是原子操作?在 Java Concurrency API 中有哪些原

子类(atomic classes)?

十、Java Concurrency API 中的 Lock 接口(Lock interface)

是什么?对比同步它有什么优势?

十一、什么是 Executors 框架?

十二、什么是阻塞队列?阻塞队列的实现原理是什么?如何使用

阻塞队列来实现生产者-消费者模型?

十三、什么是 Callable 和 Future?

十四、什么是 FutureTask?使用 ExecutorService 启动任务。

十五、什么是并发容器的实现?

十六、多线程同步和互斥有几种实现方法,都是什么?

十七、什么是竞争条件?你怎样发现和解决竞争?

十八、你将如何使用 thread dump?你将如何分析 Thread

dump?

二十、Java 中你怎样唤醒一个阻塞的线程?

二十一、在 Java 中 CycliBarriar 和 CountdownLatch 有什么区

别?

二十二、什么是不可变对象,它对写并发应用有什么帮助?

二十三、什么是多线程中的上下文切换?

二十四、Java 中用到的线程调度算法是什么?

二十五、什么是线程组,为什么在 Java 中不推荐使用?

二十六、为什么使用 Executor 框架比使用应用创建和管理线程好?

二十七、java 中有几种方法可以实现一个线程?

二十八、如何停止一个正在运行的线程?

二十九、notify()和 notifyAll()有什么区别?

三十、什么是 Daemon 线程?它有什么意义?

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

三十二、什么是可重入锁(ReentrantLock)?

三十三、当一个线程进入某个对象的一个 synchronized 的实例方

法后,其它线程是否可进入此对象的其它方法?

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

三十五、SynchronizedMap 和 ConcurrentHashMap 有什么区

别?

三十六、CopyOnWriteArrayList 可以用于什么应用场景?

三十七、什么叫线程安全?servlet 是线程安全吗?

三十八、volatile 有什么用?能否用一句话说明下 volatile 的应用

场景?

三十九、为什么代码会重排序?

四十、在 java 中 wait 和 sleep 方法的不同?

四十一、一个线程运行时发生异常会怎样?

四十二、如何在两个线程间共享数据?

四十三、Java 中 notify 和 notifyAll 有什么区别?

四十四、为什么 wait, notify 和 notifyAll 这些方法不在 thread

类里面?

四十五、什么是 ThreadLocal 变量?

四十六、Java 中 interrupted 和 isInterrupted 方法的区别?

四十七、为什么 wait 和 notify 方法要在同步块中调用?

四十八、为什么你应该在循环中检查等待条件?

四十九、Java 中的同步集合与并发集合有什么区别?

五十、什么是线程池? 为什么要使用它?

五十一、怎么检测一个线程是否拥有锁?

五十二、你如何在 Java 中获取线程堆栈?

五十三、JVM 中哪个参数是用来控制线程的栈堆栈小的?

五十四、Thread 类中的 yield 方法有什么作用?

五十五、Java 中 ConcurrentHashMap 的并发度是什么?

五十六、Java 中 Semaphore 是什么?

五十七、Java 线程池中 submit() 和 execute()方法有什么区别?

五十八、什么是阻塞式方法?

五十九、Java 中的 ReadWriteLock 是什么?

六十、volatile 变量和 atomic 变量有什么不同?

六十一、可以直接调用 Thread 类的 run ()方法么?

六十二、如何让正在运行的线程暂停一段时间?

六十三、你对线程优先级的理解是什么?

六十四、什么是线程调度器(Thread Scheduler)和时间分片(Time

Slicing )?

六十五、你如何确保 main()方法所在的线程是 Java 程序最后结束

的线程?

六十六、线程之间是如何通信的?

六十七、为什么线程通信的方法 wait(), notify()和 notifyAll()被定

义在 Object 类里?

六十八、为什么 wait(), notify()和 notifyAll ()必须在同步方法或

者同步块中被调用?

六十九、为什么 Thread 类的 sleep()和 yield ()方法是静态的?

七十、如何确保线程安全?

七十一、同步方法和同步块,哪个是更好的选择?

七十二、如何创建守护线程?

七十三、什么是 Java Timer 类?如何创建一个有特定时间间隔的

任务?


一、在 java 中守护线程和本地线程区别?

java 中的线程分为两种:守护线程(Daemon)和用户线程(User)。

任何线程都可以设置为守护线程和用户线程,通过方法 Thread.setDaemon(bool
on);true 则把该线程设置为守护线程,反之则为用户线程。Thread.setDaemon()
必须在 Thread.start()之前调用,否则运行时会抛出异常。
两者的区别
唯一的区别是判断虚拟机(JVM)何时离开,Daemon 是为其他线程提供服务,如果
全部的 User Thread 已经撤离,Daemon 没有可服务的线程,JVM 撤离。也可
以理解为守护线程是 JVM 自动创建的线程(但不一定),用户线程是程序创建的
线程;比如 JVM 的垃圾回收线程是一个守护线程,当所有线程已经撤离,不再产
生垃圾,守护线程自然就没事可干了,当垃圾回收线程是 Java 虚拟机上仅剩的线
程时,Java 虚拟机会自动离开。
扩展 :Thread Dump 打印出来的线程信息,含有 daemon 字样的线程即为守护
进程,可能会有:服务守护进程、编译守护进程、windows 下的监听 Ctrl+break
的守护进程、Finalizer 守护进程、引用处理守护进程、GC 守护进程。

二、线程与进程的区别?

进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元。
一个程序至少有一个进程,一个进程至少有一个线程。

三、什么是多线程中的上下文切换?

多线程会共同使用一组计算机上的 CPU,而线程数大于给程序分配的 CPU 数量时,
为了让各个线程都有执行的机会,就需要轮转使用 CPU。不同的线程切换使用 CPU
发生的切换数据等就是上下文切换。

四、死锁与活锁的区别,死锁与饥饿的区别

死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成
的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
产生死锁的必要条件
1、互斥条件:所谓互斥就是进程在某一时间内独占资源。
2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3、不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
活锁 :任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,
失败,尝试,失败。
活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而
处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
饥饿 :一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执
行的状态。
Java 中导致饥饿的原因
1、高优先级线程吞噬所有的低优先级线程的 CPU 时间。
2、线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前
持续地对该同步块进行访问。
3、线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的 wait 方
法),因为其他线程总是被持续地获得唤醒。

五、Java 中用到的线程调度算法是什么?

采用时间片轮转的方式。可以设置线程的优先级,会映射到下层的系统上面的优
先级上,如非特别需要,尽量不要用,防止线程饥饿

六、什么是线程组,为什么在 Java 中不推荐使用?

ThreadGroup 类,可以把线程归属到某一个线程组中,线程组中可以有线程对象,
也可以有线程组,组中还可以有线程,这样的组织结构有点类似于树的形式。
为什么不推荐使用?因为使用有很多的安全隐患吧,没有具体追究,如果需要使
用,推荐使用线程池

七、为什么使用 Executor 框架?

每次执行任务创建线程 new Thread()比较消耗性能,创建一个线程是比较耗时、
耗资源的。
调用 new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制的创建,
线程之间的相互竞争会导致过多占用系统资源而导致系统瘫痪,还有线程之间的
频繁交替也会消耗很多系统资源。
接使用 new Thread() 启动的线程不利于扩展,比如定时执行、定期执行、定时
定期执行、线程中断等都不便实现。

八、在 Java 中 Executor 和 Executors 的区别?

Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务
的需求。
Executor 接口对象能执行我们的线程任务。
ExecutorService 接口继承了 Executor 接口并进行了扩展,提供了更多的方法我
们能获得任务执行的状态并且可以获取任务的返回值。
使用 ThreadPoolExecutor 可以创建自定义线程池。
Future 表示异步计算的结果,他提供了检查计算是否完成的方法,以等待计算的
完成,并可以使用 get()方法获取计算的结果。

九、什么是原子操作?在 Java Concurrency API 中有哪些原

子类(atomic classes)?

原子操作(atomic operation)意为”不可被中断的一个或一系列操作” 。
处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。
在 Java 中可以通过锁和循环 CAS 的方式来实现原子操作。 CAS 操作——
Compare & Set,或是 Compare & Swap,现在几乎所有的 CPU 指令都支持 CAS
的原子操作。
原子操作是指一个不受其他操作影响的操作任务单元。原子操作是在多线程环境
下避免数据不一致必须的手段。
int++并不是一个原子操作,所以当一个线程读取它的值并加 1 时,另外一个线程
有可能会读到之前的值,这就会引发错误。
为了解决这个问题,必须保证增加操作是原子的,在 JDK1.5 之前我们可以使用同
步技术来做到这一点。到 JDK1.5,java.util.concurrent.atomic 包提供了 int 和
long 类型的原子包装类,它们可以自动的保证对于他们的操作是原子的并且不需
要使用同步
java.util.concurrent 这个包里面提供了一组原子类。其基本的特性就是在多线程
环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当
某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像
自旋锁一样,一直等到该方法执行完成,才由 JVM 从等待队列中选择一个另一个
线程进入,这只是一种逻辑上的理解。
原子类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
原子数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
原子属性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,
AtomicReferenceFieldUpdater
解决 ABA 问题的原子类:AtomicMarkableReference(通过引入一个 boolean
来反映中间有没有变过),AtomicStampedReference(通过引入一个 int 来累
加来反映中间有没有变过)

十、Java Concurrency API 中的 Lock 接口(Lock interface)

是什么?对比同步它有什么优势?

Lock 接口比同步方法和同步块提供了更具扩展性的锁操作。
他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的
条件对象。
它的优势有
可以使锁更公平
可以使线程在等待锁的时候响应中断
可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间
可以在不同的范围,以不同的顺序获取和释放锁
整体上来说 Lock 是 synchronized 的扩展版,Lock 提供了无条件的、可轮询的
(tryLock 方法)、定时的(tryLock 带参方法)、可中断的(lockInterruptibly)、可多
条件队列的(newCondition 方法)锁操作。另外 Lock 的实现类基本都支持非公平
锁(默认)和公平锁,synchronized 只支持非公平锁,当然,在大部分情况下,非
公平锁是高效的选择。

十一、什么是 Executors 框架?

Executor 框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框
架。
无限制的创建线程会引起应用程序内存溢出。所以创建一个线程池是个更好的
解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。利用
Executors 框架可以非常方便的创建一个线程池。

十二、什么是阻塞队列?阻塞队列的实现原理是什么?如何使用

阻塞队列来实现生产者-消费者模型?

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。
这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当
队列满时,存储元素的线程会等待队列可用。
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消
费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者
也只从容器里拿元素。
JDK7 提供了 7 个阻塞队列。分别是
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
Java 5 之前实现同步存取时,可以使用普通的一个集合,然后在使用线程的协作
和线程同步可以实现生产者,消费者模式,主要的技术就是用好,
wait ,notify,notifyAll,sychronized 这些关键字。而在 java 5 之后,可以使用阻
塞队列来实现,此方式大大简少了代码量,使得多线程编程更加容易,安全方面
也有保障。
BlockingQueue 接口是 Queue 的子接口,它的主要用途并不是作为容器,而是
作为线程同步的的工具,因此他具有一个很明显的特性,当生产者线程试图向
BlockingQueue 放入元素时,如果队列已满,则线程被阻塞,当消费者线程试图
从中取出一个元素时,如果队列为空,则该线程会被阻塞,正是因为它所具有这
个特性,所以在程序中多个线程交替向 BlockingQueue 中放入元素,取出元素,
它可以很好的控制线程之间的通信。
阻塞队列使用最经典的场景就是 socket 客户端数据的读取和解析,读取数据的线
程不断将数据放入队列,然后解析线程不断从队列取数据解析。

十三、什么是 Callable 和 Future?

Callable 接口类似于 Runnable,从名字就可以看出来了,但是 Runnable 不会返
回结果,并且无法抛出返回结果的异常,而 Callable 功能更强大一些,被线程执
行后,可以返回值,这个返回值可以被 Future 拿到,也就是说,Future 可以拿到
异步执行任务的返回值。
可以认为是带有回调的 Runnable。
Future 接口表示异步任务,是还没有完成的任务给出的未来结果。所以说 Callable
用于产生结果,Future 用于获取结果。

十四、什么是 FutureTask?使用 ExecutorService 启动任务。

在 Java 并发程序中 FutureTask 表示一个可以取消的异步运算。它有启动和取消
运算、查询运算是否完成和取回运算结果等方法。只有当运算完成的时候结果才
能取回,如果运算尚未完成 get 方法将会阻塞。一个 FutureTask 对象可以对调用
了 Callable 和 Runnable 的对象进行包装,由于 FutureTask 也是调用了 Runnable
接口所以它可以提交给 Executor 来执行。

十五、什么是并发容器的实现?

何为同步容器:可以简单地理解为通过 synchronized 来实现同步的容器,如果有
多个线程调用同步容器的方法,它们将会串行执行。比如 Vector,Hashtable,
以及 Collections.synchronizedSet,synchronizedList 等方法返回的容器。
可以通过查看 Vector,Hashtable 等这些同步容器的实现代码,可以看到这些容
器实现线程安全的方式就是将它们的状态封装起来,并在需要同步的方法上加上
关键字 synchronized。
并发容器使用了与同步容器完全不同的加锁策略来提供更高的并发性和伸缩性,
例如在 ConcurrentHashMap 中采用了一种粒度更细的加锁机制,可以称为分段
锁,在这种锁机制下,允许任意数量的读线程并发地访问 map,并且执行读操作
的线程和写操作的线程也可以并发的访问 map,同时允许一定数量的写操作线程
并发地修改 map,所以它可以在并发环境下实现更高的吞吐量。

十六、多线程同步和互斥有几种实现方法,都是什么?

线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程
的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。
线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若
干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它
要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成
是一种特殊的线程同步。
线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式
就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,
而用户模式就是不需要切换到内核态,只在用户态完成操作。
用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。内核模
式下的方法有:事件,信号量,互斥量。

十七、什么是竞争条件?你怎样发现和解决竞争?

当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的
顺序时,则我们认为这发生了竞争条件(race condition)。

十八、你将如何使用 thread dump?你将如何分析 Thread

dump?

新建状态(New)
用 new 语句创建的线程处于新建状态,此时它和其他 Java 对象一样,仅仅在堆区
中被分配了内存。
就绪状态(Runnable)
当一个线程对象创建后,其他线程调用它的 start()方法,该线程就进入就绪状态,
Java 虚拟机会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运
行池中,等待获得 CPU 的使用权。
运行状态(Running)
处于这个状态的线程占用 CPU,执行程序代码。只有处于就绪状态的线程才有机
会转到运行状态。
阻塞状态(Blocked)
阻塞状态是指线程因为某些原因放弃 CPU,暂时停止运行。当线程处于阻塞状态
时,Java 虚拟机不会给线程分配 CPU。直到线程重新进入就绪状态,它才有机会
转到运行状态。
阻塞状态可分为以下 3 种:
位于对象等待池中的阻塞状态(Blocked in object’s wait pool)
当线程处于运行状态时,如果执行了某个对象的 wait()方法,Java 虚拟机就会把
线程放到这个对象的等待池中,这涉及到“线程通信”的内容。
位于对象锁池中的阻塞状态(Blocked in object’s lock pool)
当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已
经被其他线程占用,Java 虚拟机就会把这个线程放到这个对象的锁池中,这涉及
到“线程同步”的内容。
其他阻塞状态(Otherwise Blocked)
当前线程执行了 sleep()方法,或者调用了其他线程的 join()方法,或者发出了 I/O
请求时,就会进入这个状态。
死亡状态(Dead)
当线程退出 run()方法时,就进入死亡状态,该线程结束生命周期。
十九、为什么我们调用 start()方法时会执行 run()方法,为什么
我们不能直接调用 run()方法?
当你调用 start()方法时你将创建新的线程,并且执行在 run()方法里的代码。
但是如果你直接调用 run()方法,它不会创建新的线程也不会执行调用线程的代码,
只会把 run 方法当作普通方法去执行。

二十、Java 中你怎样唤醒一个阻塞的线程?

在 Java 发展史上曾经使用 suspend()、resume()方法对于线程进行阻塞唤醒,但
随之出现很多问题,比较典型的还是死锁问题。
解决方案可以使用以对象为目标的阻塞,即利用 Object 类的 wait()和 notify()方
法实现线程阻塞。
首先,wait、notify 方法是针对对象的,调用任意对象的 wait()方法都将导致线程
阻塞,阻塞的同时也将释放该对象的锁,相应地,调用任意对象的 notify()方法则
将随机解除该对象阻塞的线程,但它需要重新获取改对象的锁,直到获取成功才
能往下执行;其次,wait、notify 方法必须在 synchronized 块或方法中被调用,
并且要保证同步块或方法的锁对象与调用 wait、notify 方法的对象是同一个,如
此一来在调用 wait 之前当前线程就已经成功获取某对象的锁,执行 wait 阻塞后当
前线程就将之前获取的对象锁释放。

二十一、在 Java 中 CycliBarriar 和 CountdownLatch 有什么区

别?

CyclicBarrier 可以重复使用,而 CountdownLatch 不能重复使用。

Java 的 concurrent 包里面的 CountDownLatch 其实可以把它看作一个计数器,
只不过这个计数器的操作是原子操作,同时只能有一个线程去操作这个计数器,
也就是同时只能有一个线程去减这个计数器里面的值。
你可以向 CountDownLatch 对象设置一个初始的数字作为计数值,任何调用这个
对象上的 await()方法都会阻塞,直到这个计数器的计数值被其他的线程减为 0 为
止。
所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待
的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法
被重置。如果需要重置计数,请考虑使用 CyclicBarrier。
CountDownLatch 的一个非常典型的应用场景是:有一个任务想要往下执行,但
必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续
往下执行的任务调用一个 CountDownLatch 对象的 await()方法,其他的任务执
行完自己的任务后调用同一个 CountDownLatch 对象上的 countDown()方法,
  • 10
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

u010142437

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值