1、说说Java中实现多线程有几种方法
创建线程的常用三种方式:
1.
继承
Thread
类
2.
实现
Runnable
接口
3.
实现
Callable
接口(
JDK1.5>=
)
4.
线程池方式创建
通过继承
Thread
类或者实现
Runnable
接口、
Callable
接口都可以实现多线程,不过实现
Runnable
接口与实现
Callable
接口的方式基本相同,只是
Callable
接口里定义的方法返回值,可以声明抛出异
常而已。因此将实现
Runnable
接口和实现
Callable
接口归为一种方式。这种方式与继承
Thread
方式
之间的主要差别如下。
采用实现
Runnable
、
Callable
接口的方式创建线程的优缺点
优点:线程类只是实现了
Runnable
或者
Callable
接口,还可以继承其他类。这种方式下,多个线程
可以共享一个
target
对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将
CPU
、代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想。
缺点:编程稍微复杂一些,如果需要访问当前线程,则必须使用
Thread.currentThread()
方法
采用继承
Thread
类的方式创建线程的优缺点
优点:编写简单,如果需要访问当前线程,则无需使用
Thread.currentThread()
方法,直接使用
this
即可获取当前线程
缺点:因为线程类已经继承了
Thread
类,
Java
语言是单继承的,所以就不能再继承其他父类了。
2、如何停止一个正在运行的线程
1
、使用退出标志,使线程正常退出,也就是当
run
方法完成后线程终止。
2
、使用
stop
方法强行终止,但是不推荐这个方法,因为
stop
和
suspend
及
resume
一样都是过期作
废的方法。
3
、使用
interrupt
方法中断线程。
3、notify()和notifyAll()有什么区别?
notify
可能会导致死锁,而
notifyAll
则不会
任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行
synchronized
中的代码
使用
notifyall,
可以唤醒 所有处于
wait
状态的线程,使其重新进入锁的争夺队列中,而
notify
只能唤
醒一个。
wait()
应配合
while
循环使用,不应使用
if
,务必在
wait()
调用前后都检查条件,如果不满足,必须调
用
notify()
唤醒另外的线程来处理,自己继续
wait()
直至条件满足再往下执行。
notify()
是对
notifyAll()
的一个优化,但它有很精确的应用场景,并且要求正确使用。不然可能导致
死锁。正确的场景应该是
WaitSet
中等待的是相同的条件,唤醒任一个都能正确处理接下来的事
项,如果唤醒的线程无法正确处理,务必确保继续
notify()
下一个线程,并且自身需要重新回到
WaitSet
中
.
4、sleep()和wait() 有什么区别?
对于
sleep()
方法,我们首先要知道该方法是属于
Thread
类中的。而
wait()
方法,则是属于
Object
类
中的。
sleep()
方法导致了程序暂停执行指定的时间,让出
cpu
该其他线程,但是他的监控状态依然保持
者,当指定的时间到了又会自动恢复运行状态。在调用
sleep()
方法的过程中,线程不会释放对象
锁。
当调用
wait()
方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用
notify()
方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。
5、volatile 是什么?可以保证有序性吗?
一旦一个共享变量(类的成员变量、类的静态成员变量)被
volatile
修饰之后,那么就具备了两层语
义:
(1)
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对
其他线程来说是立即可见的
,volatile
关键字会强制将修改的值立即写入主存。
(2)
禁止进行指令重排序。
volatile
不是原子性操作
什么叫保证部分有序性
?
当程序执行到
volatile
变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结
果已经对后面的操作可见;在其后面的操作肯定还没有进行;
由于
flag
变量为
volatile
变量,那么在进行指令重排序的过程的时候,不会将语句
3
放到语句
1
、语句
2
前面,也不会讲语句
3
放到语句
4
、语句
5
后面。但是要注意语句
1
和语句
2
的顺序、语句
4
和语句
5
的顺序是不作任何保证的。
使用
volatile
一般用于 状态标记量 和 单例模式的双检锁。
6、Thread 类中的start() 和 run() 方法有什么区别?
start()
方法被用来启动新创建的线程,而且
start()
内部调用了
run()
方法,这和直接调用
run()
方法的
效果不一样。当你调用
run()
方法的时候,只会是在原来的线程中调用,没有新的线程启动,
start()
方法才会启动新线程。
7、为什么wait, notify 和 notifyAll这些方法不在thread类里面?
明显的原因是
JAVA
提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线
程需要等待某些锁那么调用对象中的
wait()
方法就有意义了。如果
wait()
方法定义在
Thread
类中,线
程正在等待的是哪个锁就不明显了。简单的说,由于
wait
,
notify
和
notifyAll
都是锁级别的操作,所
以把他们定义在
Object
类中因为锁属于对象。
8、为什么wait和notify方法要在同步块中调用?
1.
只有在调用线程拥有某个对象的独占锁时,才能够调用该对象的
wait(),notify()
和
notifyAll()
方
法。
2.
如果你不这么做,你的代码会抛出
IllegalMonitorStateException
异常。
3.
还有一个原因是为了避免
wait
和
notify
之间产生竞态条件。
wait()
方法强制当前线程释放对象锁。这意味着在调用某对象的
wait()
方法之前,当前线程必须已经
获得该对象的锁。因此,线程必须在某个对象的同步方法或同步代码块中才能调用该对象的
wait()
方
法。
在调用对象的
notify()
和
notifyAll()
方法之前,调用线程必须已经得到该对象的锁。因此,必须在某
个对象的同步方法或同步代码块中才能调用该对象的
notify()
或
notifyAll()
方法。
调用
wait()
方法的原因通常是,调用线程希望某个特殊的状态
(
或变量
)
被设置之后再继续执行。调用
notify()
或
notifyAll()
方法的原因通常是,调用线程希望告诉其他等待中的线程
:"
特殊状态已经被设
置
"
。这个状态作为线程间通信的通道,它必须是一个可变的共享状态
(
或变量
)
。
9、Java中interrupted 和 isInterruptedd方法的区别?
interrupted()
和
isInterrupted()
的主要区别是前者会将中断状态清除而后者不会。
Java
多线程的中
断机制是用内部标识来实现的,调用
Thread.interrupt()
来中断一个线程就会设置中断标识为
true
。
当中断线程调用静态方法
Thread.interrupted()
来检查中断状态时,中断状态会被清零。而非静态方
法
isInterrupted()
用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出
InterruptedException
异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能
被其它线程调用中断来改变。
10、Java中synchronized 和 ReentrantLock 有什么不同?
相似点:
这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如
果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等
待,而进行线程阻塞和唤醒的代价是比较高的
.
区别:
这两种方式最大区别就是对于
Synchronized
来说,它是
java
语言的关键字,是原生语法层面的互
斥,需要
jvm
实现。而
ReentrantLock
它是
JDK 1.5
之后提供的
API
层面的互斥锁,需要
lock()
和
unlock()
方法配合
try/finally
语句块来完成。
Synchronized
进过编译,会在同步块的前后分别形成
monitorenter
和
monitorexit
这个两个字节码
指令。在执行
monitorenter
指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线
程已经拥有了那个对象锁,把锁的计算器加
1
,相应的,在执行
monitorexit
指令时会将锁计算器就
减
1
,当计算器为
0
时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被
另一个线程释放为止。
由于
ReentrantLock
是
java.util.concurrent
包下提供的一套互斥锁,相比
Synchronized
,
ReentrantLock
类提供了一些高级功能,主要有以下
3
项:
1.
等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于
Synchronized
来说可以避免出现死锁的情况。
2.
公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,
Synchronized
锁非公平
锁,
ReentrantLock
默认的构造函数是创建的非公平锁,可以通过参数
true
设为公平锁,但公平锁
表现的性能不是很好。
3.
锁绑定多个条件,一个
ReentrantLock
对象可以同时绑定对个对象。
11、有三个线程T1,T2,T3,如何保证顺序执行?
在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的
join()
方法在一个线程中启动另一
个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个
(T3
调
用
T2
,
T2
调用
T1)
,这样
T1
就会先完成而
T3
最后完成。
实际上先启动三个线程中哪一个都行, 因为在每个线程的
run
方法中用
join
方法限定了三个线程的
执行顺序。
public class JoinTest2 {
// 1.
现在有
T1
、
T2
、
T3
三个线程,你怎样保证
T2
在
T1
执行完后执行,
T3
在
T2
执行完后执行
public static void main(String[] args) {
final Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t1");
}
});
final Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
// 引用t1
线程,等待
t1
线程执行完
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2");
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
// 引用t2线程,等待
t2
线程执行完
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t3");
}
});
t3.start();//这里三个线程的启动顺序可以任意,大家可以试下!
t2.start();
t1.start();
}
}
12、SynchronizedMap和ConcurrentHashMap有什么区别?
SynchronizedMap()
和
Hashtable
一样,实现上在调用
map
所有方法时,都对整个
map
进行同步。
而
ConcurrentHashMap
的实现却更加精细,它对
map
中的所有桶加了锁。所以,只要有一个线程
访问
map
,其他线程就无法进入
map
,而如果一个线程在访问
ConcurrentHashMap
某个桶时,其
他线程,仍然可以对
map
执行某些操作。
所以,
ConcurrentHashMap
在性能以及安全性方面,明显比
Collections.synchronizedMap()
更加
有优势。同时,同步操作精确控制到桶,这样,即使在遍历
map
时,如果其他线程试图对
map
进行
数据修改,也不会抛出
ConcurrentModificationException
。
13、什么是线程安全
线程安全就是说多线程访问同一段代码,不会产生不确定的结果。
又是一个理论的问题,各式各样的答案有很多,我给出一个个人认为解释地最好的:如果你的代码
在多线程下执行和在单线程下执行永远都能获得一样的结果,那么你的代码就是线程安全的。
这个问题有值得一提的地方,就是线程安全也是有几个级别的:
(
1
)不可变
像
String
、
Integer
、
Long
这些,都是
final
类型的类,任何一个线程都改变不了它们的值,要改变除
非新创建一个,因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用
(
2
)绝对线程安全
不管运行时环境如何,调用者都不需要额外的同步措施。要做到这一点通常需要付出许多额外的代
价,
Java
中标注自己是线程安全的类,实际上绝大多数都不是线程安全的,不过绝对线程安全的
类,
Java
中也有,比方说
CopyOnWriteArrayList
、
CopyOnWriteArraySet
(
3
)相对线程安全
相对线程安全也就是我们通常意义上所说的线程安全,像
Vector
这种,
add
、
remove
方法都是原子
操作,不会被打断,但也仅限于此,如果有个线程在遍历某个
Vector
、有个线程同时在
add
这个
Vector
,
99%
的情况下都会出现
ConcurrentModificationException
,也就是
fail-fast
机制。
(
4
)线程非安全
这个就没什么好说的了,
ArrayList
、
LinkedList
、
HashMap
等都是线程非安全的类
14、Thread类中的yield方法有什么作用?
Yield
方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃
CPU
占用而不能保证使其它线程一定能占用
CPU
,执行
yield()
的线程有可
能在进入到暂停状态后马上又被执行。
15、Java线程池中submit() 和 execute()方法有什么区别?
两个方法都可以向线程池提交任务,
execute()
方法的返回类型是
void
,它定义在
Executor
接口中
,
而
submit()
方法可以返回持有计算结果的
Future
对象,它定义在
ExecutorService
接口中,它扩展了
Executor
接口,其它线程池类像
ThreadPoolExecutor
和
ScheduledThreadPoolExecutor
都有这些
方法。
16、说一说自己对于 synchronized 关键字的了解
synchronized
关键字解决的是多个线程之间访问资源的同步性,
synchronized
关键字可以保证被它
修饰的方法或者代码块在任意时刻只能有一个线程执行。 另外,在
Java
早期版本中,
synchronized
属于重量级锁,效率低下,因为监视器锁(
monitor
)是依赖于底层的操作系统的
Mutex Lock
来实现的,
Java
的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一
个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核
态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的
synchronized
效率低的原因。庆幸的是在
Java 6
之后
Java
官方对从
JVM
层面对
synchronized
较
大优化,所以现在的
synchronized
锁效率也优化得很不错了。
JDK1.6
对锁的实现引入了大量的优
化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
17、说说自己是怎么使用 synchronized 关键字?
修饰实例方法
:
作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁 修饰静态方法
:
也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类
成员(
static
表明这是该类的一个静态资源,不管
new
了多少个对象,只有一份)。所以如果一个
线程
A
调用一个实例对象的非静态
synchronized
方法,而线程
B
需要调用这个实例对象所属类的静
态
synchronized
方法,是允许的,不会发生互斥现象,因为访问静态
synchronized
方法占用的
锁是当前类的锁,而访问非静态
synchronized
方法占用的锁是当前实例对象锁。 修饰代码块
:
指
定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。 总结:
synchronized
关
键字加到
static
静态方法和
synchronized(class)
代码块上都是是给
Class
类上锁。
synchronized
关键字加到实例方法上是给对象实例上锁。尽量不要使用
synchronized(String a)
因为
JVM
中,字
符串常量池具有缓存功能!
18、什么是线程安全?Vector是一个线程安全类吗?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每
次运行结果和单线程运行的结果是一样的,而且其他的变量 的值也和预期的是一样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失
误。很显然你可以将集合类分 成两组,线程安全和非线程安全的。
Vector
是用同步方法来实现线程安全的
,
而和它相似的
ArrayList
不是线程安全的。
19、 volatile关键字的作用?
一旦一个共享变量(类的成员变量、类的静态成员变量)被
volatile
修饰之后,那么就具备了两层语
义:
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对
其他线程来说是立即可见的。禁止进行指令重排序。
volatile
本质是在告诉
jvm
当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读
取;
synchronized
则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
volatile
仅能使用在变量级别;
synchronized
则可以使用在变量、方法、和类级别的。
volatile
仅能实现变量的修改可见性,并不能保证原子性;
synchronized
则可以保证变量的修改
可见性和原子性。
volatile
不会造成线程的阻塞;
synchronized
可能会造成线程的阻塞。
volatile
标记的变量不会被编译器优化;
synchronized
标记的变量可以被编译器优化。
20、常用的线程池有哪些?
newSingleThreadExecutor
:创建一个单线程的线程池,此线程池保证所有任务的执行顺序按
照任务的提交顺序执行。
newFixedThreadPool
:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线
程达到线程池的最大大小。
newCachedThreadPool
:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线
程池大小完全依赖于操作系统(或者说
JVM
)能够创建的最大线程大小。
newScheduledThreadPool
:创建一个大小无限的线程池,此线程池支持定时以及周期性执行
任务的需求。
newSingleThreadExecutor
:创建一个单线程的线程池。此线程池支持定时以及周期性执行任
务的需求。
21、简述一下你对线程池的理解
(如果问到了这样的问题,可以展开的说一下线程池如何用、线程池的好处、线程池的启动策略)
合理利用线程池能够带来三个好处。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降
低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
22、Java程序是如何执行的
我们日常的工作中都使用开发工具(
IntelliJ IDEA
或
Eclipse
等)可以很方便的调试程序,或者是通过打包工具把项目打包成
jar
包或者
war
包,放入
Tomcat
等
Web
容器中就可以正常运行了,但你
有没有想过
Java
程序内部是如何执行的?其实不论是在开发工具中运行还是在
Tomcat
中运行,
Java
程序的执行流程基本都是相同的,它的执行流程如下:
先把
Java
代码编译成字节码,也就是把
.java
类型的文件编译成
.class
类型的文件。这个过程
的大致执行流程:
Java
源代码
->
词法分析器
->
语法分析器
->
语义分析器
->
字符码生成器
->
最终生成字节码,其中任何一个节点执行失败就会造成编译失败;
把
class
文件放置到
Java
虚拟机,这个虚拟机通常指的是
Oracle
官方自带的
Hotspot JVM
;
Java
虚拟机使用类加载器(
Class Loader
)装载
class
文件;
类加载完成之后,会进行字节码效验,字节码效验通过之后
JVM
解释器会把字节码翻译成机器
码交由操作系统执行。但不是所有代码都是解释执行的,
JVM
对此做了优化,比如,以
Hotspot
虚拟机来说,它本身提供了
JIT
(
Just In Time
)也就是我们通常所说的动态编译器,
它能够在运行时将热点代码编译为机器码,这个时候字节码就变成了编译执行。
Java
程序执行
流程图如下:
23、锁的优化机制了解吗?
从
JDK1.6
版本之后,
synchronized
本身也在不断优化锁的机制,有些情况下他并不会是一个很重量级的锁了。优化机制包括自适应锁、自旋锁、锁消除、锁粗化、轻量级锁和偏向锁。
锁的状态从低到高依次为无锁
->
偏向锁
->
轻量级锁
->
重量级锁,升级的过程就是从低到高,降级在
一定条件也是有可能发生的。
自旋锁:由于大部分时候,锁被占用的时间很短,共享变量的锁定时间也很短,所有没有必要挂起
线程,用户态和内核态的来回上下文切换严重影响性能。自旋的概念就是让线程执行一个忙循环,
可以理解为就是啥也不干,防止从用户态转入内核态,自旋锁可以通过设置
-XX:+UseSpining
来开
启,自旋的默认次数是
10
次,可以使用
-XX:PreBlockSpin
设置。
自适应锁:自适应锁就是自适应的自旋锁,自旋的时间不是固定时间,而是由前一次在同一个锁上
的自旋时间和锁的持有者状态来决定。
锁消除:锁消除指的是
JVM
检测到一些同步的代码块,完全不存在数据竞争的场景,也就是不需要
加锁,就会进行锁消除。
锁粗化:锁粗化指的是有很多操作都是对同一个对象进行加锁,就会把锁的同步范围扩展到整个操
作序列之外。
偏向锁:当线程访问同步块获取锁时,会在对象头和栈帧中的锁记录里存储偏向锁的线程
ID
,之后这个线程再次进入同步块时都不需要
CAS
来加锁和解锁了,偏向锁会永远偏向第一个获得锁的线
程,如果后续没有其他线程获得过这个锁,持有锁的线程就永远不需要进行同步,反之,当有其他
线程竞争偏向锁时,持有偏向锁的线程就会释放偏向锁。可以用过设置
-XX:+UseBiasedLocking
开
启偏向锁。
轻量级锁:
JVM
的对象的对象头中包含有一些锁的标志位,代码进入同步块的时候,
JVM
将会使用
CAS
方式来尝试获取锁,如果更新成功则会把对象头中的状态位标记为轻量级锁,如果更新失败,
当前线程就尝试自旋来获得锁。
整个锁升级的过程非常复杂,我尽力去除一些无用的环节,简单来描述整个升级的机制。
简单点说,偏向锁就是通过对象头的偏向线程
ID
来对比,甚至都不需要
CAS
了,而轻量级锁主要就
是通过
CAS
修改对象头锁记录和自旋来实现,重量级锁则是除了拥有锁的线程其他全部阻塞。
24、说说进程和线程的区别?
1.
进程是一个
“
执行中的程序
”
,是系统进行资源分配和调度的一个独立单位。
2.
线程是进程的一个实体,一个进程中拥有多个线程,线程之间共享地址空间和其它资源(所以
通信和同步等操作线程比进程更加容易)
3.
线程上下文的切换比进程上下文切换要快很多。
(
1
)进程切换时,涉及到当前进程的
CPU
环境的保存和新被调度运行进程的
CPU
环境的设置。
(
2
)线程切换仅需要保存和设置少量的寄存器内容,不涉及存储管理方面的操作。
25,产生死锁的四个必要条件?
1.
互斥条件:一个资源每次只能被一个线程使用
2.
请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
3.
不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺
4.
循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
26、如何避免死锁?
指定获取锁的顺序,举例如下:
1.
比如某个线程只有获得
A
锁和
B
锁才能对某资源进行操作,在多线程条件下,如何避免死锁?
2.
获得锁的顺序是一定的,比如规定,只有获得
A
锁的线程才有资格获取
B
锁,按顺序获取锁就可
以避免死锁!!!
27,线程池核心线程数怎么设置呢?
分为
CPU
密集型和
IO
密集型
CPU密集型
这种任务消耗的主要是
CPU
资源,可以将线程数设置为
N
(
CPU
核心数)
+1
,比
CPU
核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦
任务暂停,
CPU
就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用
CPU
的空
闲时间。
IO
密集型
这种任务应用起来,系统会用大部分的时间来处理
I/O
交互,而线程在处理
I/O
的时间段内不会占
用
CPU
来处理,这时就可以将
CPU
交出给其它线程使用。因此在
I/O
密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 : 核心线程数
=CPU
核心数量
*2
。
28,Java线程池中队列常用类型有哪些?
ArrayBlockingQueue
是一个基于数组结构的有界阻塞队列,此队列按
FIFO
(先进先出)原则
对元素进行排序。
LinkedBlockingQueue
一个基于链表结构的阻塞队列,此队列按
FIFO
(先进先出) 排序元
素,吞吐量通常要高于
ArrayBlockingQueue
。
SynchronousQueue
一个不存储元素的阻塞队列。
PriorityBlockingQueue
一个具有优先级的无限阻塞队列。
PriorityBlockingQueue
也是基于
最小二叉堆实现
DelayQueue
只有当其指定的延迟时间到了,才能够从队列中获取到该元素。
DelayQueue
是一个没有大小限制的队列,
因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费
者)才会被阻塞。
这里能说出前三种也就差不多了,如果能说全那是最好。
29,线程安全需要保证几个基本特征?
原子性,简单说就是相关操作不会中途被其他线程干扰,一般通过同步机制实现。
可见性,是一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将
线程本地状态反映到主内存上,
volatile
就是负责保证可见性的。
有序性,是保证线程内串行语义,避免指令重排等。
30,说一下线程之间是如何通信的?
线程之间的通信有两种方式:共享内存和消息传递。
共享内存
在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写
-
读内存中的公共状态来
隐式进行通信。典型的共享内存通信方式,就是通过共享对象进行通信。
线程
A
与 线程
B
之间如果要通信的话,那么就必须经历下面两个步骤:
1.
线程
A
把本地内存
A
更新过得共享变量刷新到主内存中去。
2.
线程
B
到主内存中去读取线程
A
之前更新过的共享变量。
消息传递
在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行
通信。在
Java
中典型的消息传递方式,就是
wait()
和
notify()
,或者
BlockingQueue
。
参考阅读
发表评论