多线程常见面试问答知识点
多线程相关知识点
说说阻塞队列的实现:可以参考 ArrayBlockingQueue 的底层实现(锁和同步都行);
如果队列是空的,消费者会一直等待,当生产者添加元素时候,消费者是如何知道当前队列有元素的呢?如果让你来设计阻塞队列你会如何设计,让生产者和消费者能够高效率的进行通讯呢?让我们先来看看 JDK 是如何实现的。
使用通知模式实现。所谓通知模式,就是当生产者往满的队列里添加元素时会阻塞住生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用。
通过查看 JDK 源码发现 ArrayBlockingQueue 使用了 Condition 来实现当我们往队列里插入一个元素时,如果队列不可用,阻塞生产者主要通过 LockSupport.park(this);来实现继续进入源码,发现调用 setBlocker 先保存下将要阻塞的线程,然后调用 unsafe.park 阻塞当前线程 unsafe.park 是个 native 方法,park 这个方法会阻塞当前线程,只有以下四种情况中的一种发生时,该方法才会返回。
与 park 对应的 unpark 执行或已经执行时。注意:已经执行是指 unpark 先执行,然后再执行的 park。线程被中断时。如果参数中的 time 不是零,等待了指定的毫秒数时。
发生异常现象时。这些异常事先无法确定。我们继续看一下 JVM 是如何实现 park 方法的,park 在不同的操作系统使用不同的方式实现,在 linux 下是使用的是系统方法 pthread_cond_wait 实现。
实现代码在 JVM 源码路径 src/os/linux/vm/os_linux.cpp 里的 os::PlatformEvent::park 方法 pthread_cond_wait 是一个多线程的条件变量函数,cond 是 condition 的缩写,字面意思可以理解为线程在等待一个条件发生,这个条件是一个全局变量。
这个方法接收两个参数,一个共享变量 cond,一个互斥量 mutex。而 unpark 方法在 linux 下是使用 pthread_cond_signal 实现的。park 在 windows 下则是使用 WaitForSingleObject 实现的。当队列满时,生产者往阻塞队列里插入一个元素,生产者线程会进入 WAITING (parking)状态
进程通讯的方式:消息队列,共享内存,信号量,socket 通讯等;
1 无名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的 2 亲缘关系通常是指父子进程关系。
2 高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。
3 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
4 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
5 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
6 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。7 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。
共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。8 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信
Excutors 可以产生哪些线程池;
1、newCachedThreadPool:用来创建一个可缓存线程池,该线程池没有长度限制,对于新的任务,如果有空闲的线程,则使用空闲的线程执行,如果没有,则新建一个线程来执行任务。如果线程池长度超过处理需要,可灵活回收空闲线程
2、newFixedThreadPool :用来创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。定长线程池的大小通常根据系统资源进行设置:Runtime.getRuntime().availableProcessors()
3、newScheduledThreadPool:用来创建一个定长线程池,并且支持定时和周期性的执行任务
4、newSingleThreadExecutor:用来创建一个单线程化的线程池,它只用唯一的工作线程来执行任务,一次只支持一个,所有任务按照指定的顺序执行
为什么要用线程池;
在 Java 中,如果每当一个请求到达就创建一个新线程,开销是相当大的。在实际使用中,每个请求创建新线程的服务器在创建和销毁线程上花费的时间和消耗的系统资源,甚至可能要比花在处理实际的用户请求的时间和资源要多得多。
除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个 JVM 里创建太多的线程,可能会导致系统由于过度消耗内存或“切换过度”而导致系统资源不足。
为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务,这就是“池化资源”技术产生的原因。
线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。
这样,就可以立即为请求服务,使应用程序响应更快。另外,通过适当地调整线程池中的线程数目可以防止出现资源不足的情况
volatile 关键字的用法:使多线程中的变量可见;
volatile 关键字,作用是强制线程去公共堆栈中访问 isContinuePrint 的值。
使用 volatile 关键字增加了实例变量在多个线程之间的可见性,但 volatile 关键字有一个致命的缺陷是不支持原子性
synchronized 与 volatile 关键字之间的比较
关键字 volatile 是线程同步的轻量实现,所以 volatile 关键字性能比 synchronized 好。
volatile 只能修饰变量,synchronized 可以修饰方法,代码块 volatile 不会阻塞线程,synchronized 会阻塞线程 volatile 能保证数据的可见性,不保证原子性,synchronized 可以保证原子性,可以间接保证可见性,它会将公共内存和私有内存的数据做同步处理。volatile 解决的是变量在多个线程之间的可见性,
synchronized 解决的是多个线程之间访问资源的同步性 请记住 Java 的同步机制都是围绕两点:原子性,线程之间的可见性.只有满足了这两点才能称得上是同步的。Java 中的 synchronized 和 volatile 两个关键字分别执行的是原子性和线程之间的可见性
版权声明: 本文为 InfoQ 作者【浅羽技术】的原创文章。
原文链接:【http://xie.infoq.cn/article/6d4f4aef9a09f5d8f3de673c5】。文章转载请联系作者。
评论