java中ConcurrentHashMap是java queue线程安全全的,我这样写有什么问题吗?

java中进程和线程的有什么区别?-_星空见康网
java中进程和线程的有什么区别?
java中进程和线程的有什么区别?
一个进程可以有多个线程,举个很常见的例子!不是java中的区别,进程和线程是系统里的两个很基本的概念,你运行了qq程序就一个进程,如果你开几个窗口聊天,可以说线程是进程的子,这几个窗口就是qq进程下的几个线程
不是java中任何都是一样为达到一个目的 正在执行的叫进程为了这个目的 几条线路一起工作的叫线程
进程是静态的 是一个镜像 线程是动态的 一个程序在运行的时候会先开辟线程然后执行里面的方法 开辟更多...
程序是计算机指令的集合,它以文件的形式存储在磁盘上。程序是通常我们所写好的存储于计算机上没有执行的指...
是一个线程也是一个进程,一个java程序启动后它就是一个进程,进程相当于一个空盒,它只提供资源装载的...
// 生产者与消费者问题 public class ProducerConsumer { publi...
这么说吧,电脑系统里有一个任务管理器,任务管理器中有许多进程,一个进程的下面又有很多线程。就是这个意...
你好 在Java中有两类线程:用户线程 (User Thread)、守护线程 (Daemon Thr...
Java线程之新特征-有返回值的线程 [百度经验]
我简单一点说,平时你的程序是单线程的,结果就是由第一行执行到最后一行,或者中间有跳转,但是你执行一行...
在一个程序中,这些独立运行的程序片断叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理...
你可能想看的相关专题java.util.ConcurrentHashMap的Golang实现
o 最后回复来自
为什么要移植ConcurrentHashMap
在实际开发中,很多情况下我们需要并发地访问Map。而Golang的标准库并未提供类似java.util.concurrent中的各种高级并发数据结构。如果根据Golang文档的建议,用goroutines和channel来实现对Map的并发访问,实质上是将并发变成串行,性能表现不佳。如果用Mutex或者RWMutex包装Map来实现并发读写,性能同样也无法令人满意。
相比之下,Java的java.util.ConcurrentHashMap利用分段锁和弱一致性的迭代器实现降低了锁粒度,在保证线程安全的基础上减少了并发瓶颈和提高了性能。因此,我们完全可以将Java的ConcurrentHashMap移植到Golang来解决高性能并发访问Map的问题。
开始使用ConcurrentMap
项目地址在
目前实现的方法有:
ContainsKey
PutIfAbsent
RemoveEntry
CompareAndReplace
Quick Start
m := concurrent.NewConcurrentMap()
previou, err := m.Put(1, 10)
//return nil, nil
previou, err = m.PutIfAbsent(1, 20)
//return 10, nil
val, err := m.Get(1)
//return 10, nil
s := m.Size()
//return 1
m.PutAll(map[interface{}]interface{}{
ok, err := m.RemoveEntry(1, 100)
//return true, nil
previou, err = m.Replace(2, 20)
//return 200, nil
ok, err = m.CompareAndReplace(2, 200, 20)
//return false, nil
previou, err = m.Remove(2)
//return 20, nil
s = m.Size()
//return 0
//枚举ConcurrentMap
itr := m.Iterator()
for itr.HasNext() {
entry := itr.NextEntry()
k, v := entry.Key, entry.Value
性能测试结果
性能测试中使用了LockMap作为参考,LockMap是一个RWMutex来同步的LockMap实现。
测试CPU为:Xeon E3-.30GHZ, 四核心8线程
测试方案采用runtime.GOMAXPROCS(8),9个goroutines,每个goroutines读写10万个key/value。
测试结果如下:
LockMap Put ---------------------
386.822 ms/op
ConcurrentMap Put --------------- 99.956 ms/op
LockMap Get -----------------------
45.643 ms/op
ConcurrentMap Get ---------------- 60.583 ms/op
LockMap PutAndGet---------------
820.547 ms/op
ConcurrentMap PutAndGet ------- 138.508 ms/op
这里单纯测试Get时LockMap的性能超过了ConcurrentMap,这是因为LockMap使用了RWMutex来同步,而RWMutex的读是可以多线程并发的。如果同时进行Put和Get,那么可以看到ConcurrentMap表现出了巨大的性能优势。
从上面的测试结果看,ConcurrentMap相对于LockMap提升了300%至500%的性能,这是相当惊人的。
实现ConcurrentMap中的一些问题
ConcurrentMap的HashMap实现完全照搬Java的ConcurrentHashMap代码,基本就是语法的翻译而已,因此ConcurrentMap的实现机制可以直接参考这篇
Golang与Java不同的是Java的Object提供了hashCode()方法,无需单独实现Key的hash函数。但Golang标准库的hash函数是封装在c的实现代码中($GOROOT\src\pkg\runtime\alg.c),并未公开,因此我们必须自己实现一个通用的hash函数。通用hash函数最容易想到的实现方式是使用变量的内存地址作为hashcode,但这并不是百分百可行的。因为某些情况下我们无法从interface{}取到原始变量的地址,而且对string的判断也不能使用内存地址,否则会导致hash(&aa&) != hash(&a& + &b&),这里必须使用不同的方法来处理(之后会再有一个帖子来谈谈hash函数实现中的问题)。
ConcurrentMap的限制以及与Golang的Map本身的区别
与Golang的Map一样,ConcurrentMap也不能使用slice、function、map以及包含这些类型的struct来作为key,这个是Golang语言带来的限制,较难解决。
一个与Golang语言的Map区别较大的地方是,ConcurrentMap的Iterator方法返回的迭代器是一个弱一致的迭代器。也就是説,如果在迭代器对象返回后,对ConcurrentMap的修改不一定会在迭代器的后续迭代中“可见”。这是为了消除ConcurrentMap的并发瓶颈而做出的设计上的妥协,并非bug。
这个项目做的很不错,包含了详细的注释,赞。Java多线程(三)之ConcurrentHashMap深入分析
一、Map体系
Hashtable是JDK 5之前Map唯一线程安全的内置实现(Collections.synchronizedMap不算)。Hashtable继承的是Dictionary(Hashtable是其唯一公开的子类),并不继承AbstractMap或者HashMap。尽管Hashtable和HashMap的结构非常类似,但是他们之间并没有多大联系。
ConcurrentHashMap是HashMap的线程安全版本,ConcurrentSkipListMap是TreeMap的线程安全版本。
最终可用的线程安全版本Map实现是ConcurrentHashMap/ConcurrentSkipListMap/Hashtable/Properties四个,但是Hashtable是过时的类库,因此如果可以的应该尽可能的使用ConcurrentHashMap和ConcurrentSkipListMap。
二、concurrentHashMap的结构
我们通过ConcurrentHashMap的类图来分析ConcurrentHashMap的结构。
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
ConcurrentHashMap 类中包含两个静态内部类 HashEntry 和 Segment。HashEntry 用来封装映射表的键 / 值对;Segment 用来充当锁的角色,每个 Segment 对象守护整个散列映射表的若干个桶。每个桶是由若干个 HashEntry 对象链接起来的链表。一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的数组。
三、ConcurrentHashMap实现原理
锁分离 (Lock Stripping)
比如HashTable是一个过时的容器类,通过使用synchronized来保证线程安全,在线程竞争激烈的情况下HashTable的效率非常低下。原因是所有访问HashTable的线程都必须竞争同一把锁。那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术。
ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。同样当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
ConcurrentHashMap有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里&按顺序&是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。不变性是多线程编程占有很重要的地位,下面还要谈到。
&* The segments, each of which is a specialized hash table&
final Segment&K,V&[]&&&
&不变(Immutable)和易变(Volatile)
ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。如果使用传统的技术,如HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据。ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的。HashEntry代表每个hash链中的一个节点,其结构如下所示:
static final class HashEntry&K,V& {&&&
&&& final K&&&
&&& volatile V&&&
&&& final HashEntry&K,V&&&&
可以看到除了value不是final的,其它值都是final的,这意味着不能从hash链的中间或尾部添加或删除节点,因为这需要修改next引用值,所有的节点的修改只能从头部开始。对于put操作,可以一律添加到Hash链的头部。但是对于remove操作,可能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍,最后一个节点指向要删除结点的下一个结点。这在讲解删除操作时还会详述。为了确保读操作能够看到最新的值,将value设置成volatile,这避免了加锁。
四、ConcurrentHashMap具体实现
ConcurrentHashMap的初始化
ConcurrentHashMap初始化方法是通过initialCapacity,loadFactor, concurrencyLevel几个参数来初始化segments数组,段偏移量segmentShift,段掩码segmentMask和每个segment里的HashEntry数组 。
初始化segments数组。让我们来看一下初始化segmentShift,segmentMask和segments数组的源代码。
if (concurrencyLevel & MAX_SEGMENTS)&
&&& concurrencyLevel = MAX_SEGMENTS;&
// Find power-of-two sizes best matching arguments&
int sshift = 0;&
int ssize = 1;&
while (ssize & concurrencyLevel) {&
&&& ssize &&= 1;&
segmentShift = 32 -&
segmentMask = ssize - 1;&
this.segments = Segment.newArray(ssize);&
由上面的代码可知segments数组的长度ssize通过concurrencyLevel计算得出。为了能通过按位与的哈希算法来定位segments数组的索引,必须保证segments数组的长度是2的N次方(power-of-two size),所以必须计算出一个是大于或等于concurrencyLevel的最小的2的N次方值来作为segments数组的长度。假如concurrencyLevel等于14,15或16,ssize都会等于16,即容器里锁的个数也是16。注意concurrencyLevel的最大大小是65535,意味着segments数组的长度最大为65536,对应的二进制是16位。
初始化segmentShift和segmentMask。这两个全局变量在定位segment时的哈希算法里需要使用,sshift等于ssize从1向左移位的次数,在默认情况下concurrencyLevel等于16,1需要向左移位移动4次,所以sshift等于4。segmentShift用于定位参与hash运算的位数,segmentShift等于32减sshift,所以等于28,这里之所以用32是因为ConcurrentHashMap里的hash()方法输出的最大数是32位的,后面的测试中我们可以看到这点。segmentMask是哈希运算的掩码,等于ssize减1,即15,掩码的二进制各个位的值都是1。因为ssize的最大长度是65536,所以segmentShift最大值是16,segmentMask最大值是65535,对应的二进制是16位,每个位都是1。
初始化每个Segment。输入参数initialCapacity是ConcurrentHashMap的初始化容量,loadfactor是每个segment的负载因子,在构造方法里需要通过这两个参数来初始化数组中的每个segment。
上面代码中的变量cap就是segment里HashEntry数组的长度,它等于initialCapacity除以ssize的倍数c,如果c大于1,就会取大于等于c的2的N次方值,所以cap不是1,就是2的N次方。segment的容量threshold=(int)cap*loadFactor,默认情况下initialCapacity等于16,loadfactor等于0.75,通过运算cap等于1,threshold等于零。
if (initialCapacity & MAXIMUM_CAPACITY)&
&&& initialCapacity = MAXIMUM_CAPACITY;&
int c = initialCapacity /&
if (c * ssize & initialCapacity)&
int cap = 1;&
while (cap & c)&
&&& cap &&= 1;&
for (int i = 0; i & this.segments. ++i)&
&&& this.segments[i] = new Segment&K,V&(cap, loadFactor);&
定位Segment
既然ConcurrentHashMap使用分段锁Segment来保护不同段的数据,那么在插入和获取元素的时候,必须先通过哈希算法定位到Segment。可以看到ConcurrentHashMap会首先使用Wang/Jenkins hash的变种算法对元素的hashCode进行一次再哈希。
private static int hash(int h) {&
&&&&&&& h += (h && 15) ^ 0xffffcd7d;&
&&&&&&& h ^= (h &&& 10);&
&&&&&&& h += (h && 3);&
&&&&&&& h ^= (h &&& 6);&
&&&&&&& h += (h && 2) + (h && 14);&
&&&&&&& return h ^ (h &&& 16);&
之所以进行再哈希,其目的是为了减少哈希冲突,使元素能够均匀的分布在不同的Segment上,从而提高容器的存取效率。假如哈希的质量差到极点,那么所有的元素都在一个Segment中,不仅存取元素缓慢,分段锁也会失去意义。我做了一个测试,不通过再哈希而直接执行哈希计算。
System.out.println(Integer.parseInt(&0001111&, 2) & 15);&
System.out.println(Integer.parseInt(&0011111&, 2) & 15);&
System.out.println(Integer.parseInt(&0111111&, 2) & 15);&
System.out.println(Integer.parseInt(&1111111&, 2) & 15);&
计算后输出的哈希值全是15,通过这个例子可以发现如果不进行再哈希,哈希冲突会非常严重,因为只要低位一样,无论高位是什么数,其哈希值总是一样。我们再把上面的二进制数据进行再哈希后结果如下,为了方便阅读,不足32位的高位补了0,每隔四位用竖线分割下。
可以发现每一位的数据都散列开了,通过这种再哈希能让数字的每一位都能参加到哈希运算当中,从而减少哈希冲突。ConcurrentHashMap通过以下哈希算法定位segment。
final Segment&K,V& segmentFor(int hash) {&
&&&&&&& return segments[(hash &&& segmentShift) & segmentMask];&
默认情况下segmentShift为28,segmentMask为15,再哈希后的数最大是32位二进制数据,向右无符号移动28位,意思是让高4位参与到hash运算中, (hash &&& segmentShift) & segmentMask的运算结果分别是4,15,7和8,可以看到hash值没有发生冲突。
ConcurrentHashMap的get操作
前面提到过ConcurrentHashMap的get操作是不用加锁的,我们这里看一下其实现:
public V get(Object key) {&
&&& int hash = hash(key.hashCode());&
&&& return segmentFor(hash).get(key, hash);&
看第三行,segmentFor这个函数用于确定操作应该在哪一个segment中进行,几乎对ConcurrentHashMap的所有操作都需要用到这个函数,我们看下这个函数的实现:
final Segment&K,V& segmentFor(int hash) {&
&&& return segments[(hash &&& segmentShift) & segmentMask];&
这个函数用了位操作来确定Segment,根据传入的hash值向右无符号右移segmentShift位,然后和segmentMask进行与操作,结合我们之前说的segmentShift和segmentMask的值,就可以得出以下结论:假设Segment的数量是2的n次方,根据元素的hash值的高n位就可以确定元素到底在哪一个Segment中。
在确定了需要在哪一个segment中进行操作以后,接下来的事情就是调用对应的Segment的get方法:
V get(Object key, int hash) {&
&&& if (count != 0) { // read-volatile&
&&&&&&& HashEntry&K,V& e = getFirst(hash);&
&&&&&&& while (e != null) {&
&&&&&&&&&&& if (e.hash == hash && key.equals(e.key)) {&
&&&&&&&&&&&&&&& V v = e.&
&&&&&&&&&&&&&&& if (v != null)&
&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&& return readValueUnderLock(e); // recheck&
&&&&&&&&&&& }&
&&&&&&&&&&& e = e.&
&&&&&&& }&
先看第二行代码,这里对count进行了一次判断,其中count表示Segment中元素的数量,我们可以来看一下count的定义:
可以看到count是volatile的,实际上这里里面利用了volatile的语义:
对volatile字段的写入操作happens-before于每一个后续的同一个字段的读操作。
因为实际上put、remove等操作也会更新count的值,所以当竞争发生的时候,volatile的语义可以保证写操作在读操作之前,也就保证了写操作对后续的读操作都是可见的,这样后面get的后续操作就可以拿到完整的元素内容。
然后,在第三行,调用了getFirst()来取得链表的头部:
HashEntry&K,V& getFirst(int hash) {&
&&& HashEntry&K,V&[] tab =&
&&& return tab[hash & (tab.length - 1)];&
同样,这里也是用位操作来确定链表的头部,hash值和HashTable的长度减一做与操作,最后的结果就是hash值的低n位,其中n是HashTable的长度以2为底的结果。
在确定了链表的头部以后,就可以对整个链表进行遍历,看第4行,取出key对应的value的值,如果拿出的value的值是null,则可能这个key,value对正在put的过程中,如果出现这种情况,那么就加锁来保证取出的value是完整的,如果不是null,则直接返回value。
ConcurrentHashMap的put操作
看完了get操作,再看下put操作,put操作的前面也是确定Segment的过程,这里不再赘述,直接看关键的segment的put方法:
V put(K key, int hash, V value, boolean onlyIfAbsent) {&
&&& lock();&
&&& try {&
&&&&&&& int c =&
&&&&&&& if (c++ & threshold) // ensure capacity&
&&&&&&&&&&& rehash();&
&&&&&&& HashEntry&K,V&[] tab =&
&&&&&&& int index = hash & (tab.length - 1);&
&&&&&&& HashEntry&K,V& first = tab[index];&
&&&&&&& HashEntry&K,V& e =&
&&&&&&& while (e != null && (e.hash != hash || !key.equals(e.key)))&
&&&&&&&&&&& e = e.&
&&&&&&& V oldV&
&&&&&&& if (e != null) {&
&&&&&&&&&&& oldValue = e.&
&&&&&&&&&&& if (!onlyIfAbsent)&
&&&&&&&&&&&&&&& e.value =&
&&&&&&& }&
&&&&&&& else {&
&&&&&&&&&&& oldValue =&
&&&&&&&&&&& ++modC&
&&&&&&&&&&& tab[index] = new HashEntry&K,V&(key, hash, first, value);&
&&&&&&&&&&& count = // write-volatile&
&&&&&&& }&
&&&&&&& return oldV&
&&& } finally {&
&&&&&&& unlock();&
首先对Segment的put操作是加锁完成的,然后在第五行,如果Segment中元素的数量超过了阈值(由构造函数中的loadFactor算出)这需要进行对Segment扩容,并且要进行rehash,关于rehash的过程大家可以自己去了解,这里不详细讲了。
第8和第9行的操作就是getFirst的过程,确定链表头部的位置。
第11行这里的这个while循环是在链表中寻找和要put的元素相同key的元素,如果找到,就直接更新更新key的value,如果没有找到,则进入21行这里,生成一个新的HashEntry并且把它加到整个Segment的头部,然后再更新count的值。
ConcurrentHashMap的remove操作
Remove操作的前面一部分和前面的get和put操作一样,都是定位Segment的过程,然后再调用Segment的remove方法:
V remove(Object key, int hash, Object value) {&
&&& lock();&
&&& try {&
&&&&&&& int c = count - 1;&
&&&&&&& HashEntry&K,V&[] tab =&
&&&&&&& int index = hash & (tab.length - 1);&
&&&&&&& HashEntry&K,V& first = tab[index];&
&&&&&&& HashEntry&K,V& e =&
&&&&&&& while (e != null && (e.hash != hash || !key.equals(e.key)))&
&&&&&&&&&&& e = e.&
&&&&&&& V oldValue =&
&&&&&&& if (e != null) {&
&&&&&&&&&&& V v = e.&
&&&&&&&&&&& if (value == null || value.equals(v)) {&
&&&&&&&&&&&&&&& oldValue =&
&&&&&&&&&&&&&&& // All entries following removed node can stay&
&&&&&&&&&&&&&&& // in list, but all preceding ones need to be&
&&&&&&&&&&&&&&& // cloned.&
&&&&&&&&&&&&&&& ++modC&
&&&&&&&&&&&&&&& HashEntry&K,V& newFirst = e.&
&&&&&&&&&&&&&&& for (HashEntry&K,V& p = p != p = p.next)&
&&&&&&&&&&&&&&&&&&& newFirst = new HashEntry&K,V&(p.key, p.hash,&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& newFirst, p.value);&
&&&&&&&&&&&&&&& tab[index] = newF&
&&&&&&&&&&&&&&& count = // write-volatile&
&&&&&&&&&&& }&
&&&&&&& }&
&&&&&&& return oldV&
&&& } finally {&
&&&&&&& unlock();&
首先remove操作也是确定需要删除的元素的位置,不过这里删除元素的方法不是简单地把待删除元素的前面的一个元素的next指向后面一个就完事了,我们之前已经说过HashEntry中的next是final的,一经赋值以后就不可修改,在定位到待删除元素的位置以后,程序就将待删除元素前面的那一些元素全部复制一遍,然后再一个一个重新接到链表上去,看一下下面这一幅图来了解这个过程:
假设链表中原来的元素如上图所示,现在要删除元素3,那么删除元素3以后的链表就如下图所示:
ConcurrentHashMap的size操作
在前面的章节中,我们涉及到的操作都是在单个Segment中进行的,但是ConcurrentHashMap有一些操作是在多个Segment中进行,比如size操作,ConcurrentHashMap的size操作也采用了一种比较巧的方式,来尽量避免对所有的Segment都加锁。
前面我们提到了一个Segment中的有一个modCount变量,代表的是对Segment中元素的数量造成影响的操作的次数,这个值只增不减,size操作就是遍历了两次Segment,每次记录Segment的modCount值,然后将两次的modCount进行比较,如果相同,则表示期间没有发生过写入操作,就将原先遍历的结果返回,如果不相同,则把这个过程再重复做一次,如果再不相同,则就需要将所有的Segment都锁住,然后一个一个遍历了,具体的实现大家可以看ConcurrentHashMap的,这里就不贴了。
您对本文章有什么意见或着疑问吗?请到您的关注和建议是我们前行的参考和动力&&
您的浏览器不支持嵌入式框架,或者当前配置为不显示嵌入式框架。15个顶级Java多线程面试题及回答 | 码农-
Java 线程面试问题
在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分。如果你想获得任何股票投资银行的前台资讯职位,那么你应该准备很多关于多线程的问题。在投资银行业务中多线程和并发是一个非常受欢迎的话题,特别是电子交易发展方面相关的。他们会问面试者很多令人混淆的Java线程问题。面试官只是想确信面试者有足够的Java线程与并发方面的知识,因为候选人中有很多只浮于表面。用于直接面向市场交易的高容量和低延时的电子交易系统在本质上是并发的。下面这些是我在不同时间不同地点喜欢问的Java线程问题。我没有提供答案,但只要可能我会给你线索,有些时候这些线索足够回答问题。现在引用Java5并发包关于并发工具和并发集合的问题正在增多。那些问题中ThreadLocal、Blocking Queue、Counting Semaphore和ConcurrentHashMap比较流行。
15个Java多线程面试题及回答
1)现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。
2)在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?
lock接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。Java线程面试的问题越来越会根据面试者的回答来提问。我强烈建议在你去参加多线程的面试之前认真读一下Locks,因为当前其大量用于构建电子交易终统的客户端缓存和交易连接空间。
3)在java中wait和sleep方法的不同?
通常会在电话面试中经常被问到的Java线程面试问题。最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。
4)用Java实现阻塞队列。
这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测侯选者是否能实际的用Java线程写程序;第二,可以检测侯选者对并发场景的理解,并且你可以根据这个问很多问题。如果他用wait()和notify()方法来实现阻塞队列,你可以要求他用最新的Java 5中的并发类来再写一次。
5)用Java写代码来解决生产者——消费者问题。
与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在Java中怎么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞队列实现的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。
6)用Java编程一个会导致死锁的程序,你将怎么解决?
这是我最喜欢的Java线程面试问题,因为即使死锁问题在写多线程并发程序时非常普遍,但是很多侯选者并不能写deadlock free code(无死锁代码?),他们很挣扎。只要告诉他们,你有N个资源和N个线程,并且你需要所有的资源来完成一个操作。为了简单这里的n可以替换为2,越大的数据会使问题看起来更复杂。通过避免Java中的死锁来得到关于死锁的更多信息。
7) 什么是原子操作,Java中的原子操作是什么?
非常简单的java线程面试问题,接下来的问题是你需要同步一个原子操作。
8) Java中的volatile关键是什么作用?怎样使用它?在Java中它跟synchronized方法有什么不同?
自从Java 5和Java内存模型改变以后,基于volatile关键字的线程问题越来越流行。应该准备好回答关于volatile变量怎样在并发环境中确保可见性。
9) 什么是竞争条件?你怎样发现和解决竞争?
这是一道出现在多线程面试的高级阶段的问题。大多数的面试官会问最近你遇到的竞争条件,以及你是怎么解决的。有些时间他们会写简单的代码,然后让你检测出代码的竞争条件。可以参考我之前发布的关于Java竞争条件的文章。在我看来这是最好的java线程面试问题之一,它可以确切的检测候选者解决竞争条件的经验,or writing code which is free of data race or any other race condition。关于这方面最好的书是《Concurrency practices in Java》。
10) 你将如何使用thread dump?你将如何分析Thread dump?
在UNIX中你可以使用kill -3,然后thread dump将会打印日志,在windows中你可以使用”CTRL+Break”。非常简单和专业的线程面试问题,但是如果他问你怎样分析它,就会很棘手。
11) 为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
这是另一个非常经典的java多线程面试问题。这也是我刚开始写线程程序时候的困惑。现在这个问题通常在电话面试或者是在初中级Java面试的第一轮被问到。这个问题的回答应该是这样的,当你调用start()方法时你将创建新的线程,并且执行在run()方法里的代码。但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码。阅读我之前写的《start与run方法的区别》这篇文章来获得更多信息。
12) Java中你怎样唤醒一个阻塞的线程?
这是个关于线程和阻塞的棘手的问题,它有很多解决方法。如果线程遇到了IO阻塞,我并且不认为有一种方法可以中止线程。如果线程因为调用wait()、sleep()、或者join()方法而导致的阻塞,你可以中断线程,并且通过抛出InterruptedException来唤醒它。我之前写的《How to deal with blocking methods in java》有很多关于处理线程阻塞的信息。
13)在Java中CycliBarriar和CountdownLatch有什么区别?
这个线程问题主要用来检测你是否熟悉JDK5中的并发包。这两个的区别是CyclicBarrier可以重复使用已经通过的障碍,而CountdownLatch不能重复使用。
14) 什么是不可变对象,它对写并发应用有什么帮助?
另一个多线程经典面试问题,并不直接跟线程有关,但间接帮助很多。这个java面试问题可以变的非常棘手,如果他要求你写一个不可变对象,或者问你为什么String是不可变的。
15) 你在多线程环境中遇到的常见的问题是什么?你是怎么解决它的?
多线程和并发程序中常遇到的有Memory-interface、竞争条件、死锁、活锁和饥饿。问题是没有止境的,如果你弄错了,将很难发现和调试。这是大多数基于面试的,而不是基于实际应用的Java线程问题。
补充的其它几个问题:
1) 在java中绿色线程和本地线程区别?
2) 线程与进程的区别?
3) 什么是多线程中的上下文切换?
4)死锁与活锁的区别,死锁与饥饿的区别?
5) Java中用到的线程调度算法是什么?
6) 在Java中什么是线程调度?
7) 在线程中你怎么处理不可捕捉异常?
8) 什么是线程组,为什么在Java中不推荐使用?
9) 为什么使用Executor框架比使用应用创建和管理线程好?
10) 在Java中Executor和Executors的区别?
本条目发布于。属于、、、、、分类。作者是。}

我要回帖

更多关于 java 线程安全 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信