concurrenthashmap下面哪些是线程安全的的吗

ConcurrentHashMap需要知道的细节
时间: 18:53:44
&&&& 阅读:1489
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&1、public V get(Object key)不涉及到锁,也就是说获得对象时没有使用锁;2、keySet().iterator()及keys(),获取的Iterator、Enumeration变量是单线程访问安全的,多线程访问时要么生成多个Iterator、Enumeration(通过调用相应的获取方法),要么以ConcurrentHashMap变量为锁进行同步(synchronized该变量);ConcurrentHashMap变量是多线程访问安全的,尽管是多线程访问,多数情况下应该没有锁争用;3、put、remove方法要使用锁,但并不一定有锁争用,原因在于ConcurrentHashMap将缓存的变量分到多个Segment,每个Segment上有一个锁,只要多个线程访问的不是一个Segment就没有锁争用,就没有堵塞,各线程用各自的锁,ConcurrentHashMap缺省情况下生成16个Segment,也就是允许16个线程并发的更新而尽量没有锁争用;4、Iterator、Enumeration获得的对象,不一定是和其它更新线程同步,获得的对象可能是更新前的对象,ConcurrentHashMap允许一边更新、一边遍历,未遍历到的key一般能放映value更新;5、有些情况下这种不一致是允许的,如果需要最大的性能、吞吐量,则正好使用ConcurrentHashMap。&
ConcurrentHashMap的简要总结:
1、public V get(Object key)不涉及到锁,也就是说获得对象时没有使用锁;
2、put、remove方法要使用锁,但并不一定有锁争用,原因在于ConcurrentHashMap将缓存的变量分到多个Segment,每个Segment上有一个锁,只要多个线程访问的不是一个Segment就没有锁争用,就没有堵塞,各线程用各自的锁,ConcurrentHashMap缺省情况下生成16个Segment,也就是允许16个线程并发的更新而尽量没有锁争用;
3、Iterator对象的使用,不一定是和其它更新线程同步,获得的对象可能是更新前的对象,ConcurrentHashMap允许一边更新、一边遍历,也就是说在Iterator对象遍历的时候,ConcurrentHashMap也可以进行remove,put操作,且遍历的数据会随着remove,put操作产出变化,所以希望遍历到当前全部数据的话,要么以ConcurrentHashMap变量为锁进行同步(synchronized该变量),要么使用CopiedIterator包装iterator,使其拷贝当前集合的全部数据,但是这样生成的iterator不可以进行remove操作。
Hashtable和ConcurrentHashMap的不同点:
1、Hashtable对get,put,remove都使用了同步操作,它的同步级别是正对Hashtable来进行同步的,也就是说如果有线程正在遍历集合,其他的线程就暂时不能使用该集合了,这样无疑就很容易对性能和吞吐量造成影响,从而形成单点。而ConcurrentHashMap则不同,它只对put,remove操作使用了同步操作,get操作并不影响,详情请看以上第1,2点,当前ConcurrentHashMap这样的做法对一些线程要求很严格的程序来说,还是有所欠缺的,对应这样的程序来说,如果不考虑性能和吞吐量问题的话,个人觉得使用Hashtable还是比较合适的;
2、Hashtable在使用iterator遍历的时候,如果其他线程,包括本线程对Hashtable进行了put,remove等更新操作的话,就会抛出ConcurrentModificationException异常,但如果使用ConcurrentHashMap的话,就不用考虑这方面的问题了,详情请看以上第3点;
在开始之前,先介绍下Map是什么?
javadoc中对Map的解释如下:
An object that&maps keys to values&. A map&cannot co each key can map to at most one value.This interface takes the place of the Dictionary class, which was a totally abstract class rather than an interface.The Map interface provides three collection views, which allow a map‘s contents to be viewed as a set of keys, collection of values, or set of key-value mappings.
&从上可知,Map用于存储&key-value&元素对,它将一个key映射到一个而且只能是唯一的一个value。
Map可以使用多种实现方式,HashMap的实现采用的是hash表;而TreeMap采用的是红黑树。
1. Hashtable 和 HashMap
这两个类主要有以下几方面的不同:
&&&&Hashtable和HashMap都实现了Map接口,但是Hashtable的实现是基于Dictionary抽象类。
&&&&在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。&当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。而在Hashtable中,无论是key还是value都不能为null&。
&&&这两个类最大的不同在于Hashtable是线程安全的,它的方法是同步了的,可以直接用在多线程环境中。而HashMap则不是线程安全的。在多线程环境中,需要手动实现同步机制。因此,在Collections类中提供了一个方法返回一个同步版本的HashMap用于多线程的环境:
public&static&&K,V&&Map&K,V&&synchronizedMap(Map&K,V&&m)&{ &&
&&&&return&new&SynchronizedMap&K,V&(m); &&
该方法返回的是一个SynchronizedMap&的实例。SynchronizedMap类是定义在Collections中的一个静态内部类。它实现了Map接口,并对其中的每一个方法实现,通过synchronized&关键字进行了同步控制。
2. 潜在的线程安全问题
上面提到Collections为HashMap提供了一个并发版本SynchronizedMap。这个版本中的方法都进行了同步,但是这并不等于这个类就一定是线程安全的。在某些时候会出现一些意想不到的结果。
如下面这段代码:
//&shm是SynchronizedMap的一个实例&&&
if(shm.containsKey(‘key‘)){ &&
&&&&&&&&shm.remove(key); &&
&这段代码用于从map中删除一个元素之前判断是否存在这个元素。这里的containsKey和reomve方法都是同步的,但是整段代码却不是。考虑这么一个使用场景:线程A执行了containsKey方法返回true,准备执行remove操作;这时另一个线程B开始执行,同样执行了containsKey方法返回true,并接着执行了remove操作;然后线程A接着执行remove操作时发现此时已经没有这个元素了。要保证这段代码按我们的意愿工作,一个办法就是对这段代码进行同步控制,但是这么做付出的代价太大。
在进行迭代时这个问题更改明显。Map集合共提供了三种方式来分别返回键、值、键值对的集合:
Set&K&&keySet(); &&
Collection&V&&values(); &&
Set&Map.Entry&K,V&&&entrySet();&&
&在这三个方法的基础上,我们一般通过如下方式访问Map的元素:
Iterator&keys&=&map.keySet().iterator(); &&
while(keys.hasNext()){ &&
&&&&&&&&map.get(keys.next()); &&
在这里,有一个地方需要注意的是:得到的keySet和迭代器都是Map中元素的一个&视图&,而不是&副本&&。问题也就出现在这里,当一个线程正在迭代Map中的元素时,另一个线程可能正在修改其中的元素。此时,在迭代元素时就可能会抛出&ConcurrentModificationException异常。为了解决这个问题通常有两种方法,一是直接返回元素的副本,而不是视图。这个可以通过
集合类的&toArray()&方法实现,但是创建副本的方式效率比之前有所降低,特别是在元素很多的情况下;另一种方法就是在迭代的时候锁住整个集合,这样的话效率就更低了。
3. 更好的选择:ConcurrentHashMap
java5中新增了ConcurrentMap接口和它的一个实现类ConcurrentHashMap。ConcurrentHashMap提供了和Hashtable以及SynchronizedMap中所不同的锁机制。Hashtable中采用的锁机制是一次锁住整个hash表,从而同一时刻只能由一个线程对其进行操作;而ConcurrentHashMap中则是一次锁住一个桶。ConcurrentHashMap默认将hash表分为16个桶,诸如get,put,remove等常用操作只锁当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。
上面说到的16个线程指的是写线程,而读操作大部分时候都不需要用到锁。只有在size等操作时才需要锁住整个hash表。
在迭代方面,ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据&,iterator完成后再将头指针替换为新的数据&,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&原文:/zhenxi/p/4180471.html
教程昨日排行
&&国之画&&&& &&&&&&
&& &&&&&&&&&&&&&&
鲁ICP备号-4
打开技术之扣,分享程序人生!登录以解锁更多InfoQ新功能
获取更新并接收通知
给您喜爱的内容点赞
关注您喜爱的编辑与同行
966,690 十月 独立访问用户
语言 & 开发
架构 & 设计
文化 & 方法
您目前处于:
聊聊并发(四)——深入分析ConcurrentHashMap
聊聊并发(四)——深入分析ConcurrentHashMap
26&他的粉丝
日. 估计阅读时间:
:Facebook、Snapchat、Tumblr等背后的核心技术
相关厂商内容
相关赞助商
final HashMap&String, String& map = new HashMap&String, String&(2);
Thread t = new Thread(new Runnable() {
public void run() {
for (int i = 0; i & 10000; i++) {
new Thread(new Runnable() {
public void run() {
map.put(UUID.randomUUID().toString(), &&);
}, &ftf& + i).start();
}, &ftf&);
t.start();
效率低下的HashTable容器
HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。
锁分段技术
HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。
ConcurrentHashMap的结构
我们通过ConcurrentHashMap的类图来分析ConcurrentHashMap的结构。
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
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。
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);
上面代码中的变量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等于零。
定位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操作
Segment的get操作实现非常简单和高效。先经过一次再哈希,然后使用这个哈希值通过哈希运算定位到segment,再通过哈希算法定位到元素,代码如下:
public V get(Object key) {
int hash = hash(key.hashCode());
return segmentFor(hash).get(key, hash);
get操作的高效之处在于整个get过程不需要加锁,除非读到的值是空的才会加锁重读,我们知道HashTable容器的get方法是需要加锁的,那么ConcurrentHashMap的get操作是如何做到不加锁的呢?原因是它的get方法里将要使用的共享变量都定义成volatile,如用于统计当前Segement大小的count字段和用于存储值的HashEntry的value。定义成volatile的变量,能够在线程之间保持可见性,能够被多线程同时读,并且保证不会读到过期的值,但是只能被单线程写(有一种情况可以被多线程写,就是写入的值不依赖于原值),在get操作里只需要读不需要写共享变量count和value,所以可以不用加锁。之所以不会读到过期的值,是根据java内存模型的happen before原则,对volatile字段的写入操作先于读操作,即使两个线程同时修改和获取volatile变量,get操作也能拿到最新的值,这是用volatile替换锁的经典应用场景。
volatile V
在定位元素的代码里我们可以发现定位HashEntry和定位Segment的哈希算法虽然一样,都与数组的长度减去一相与,但是相与的值不一样,定位Segment使用的是元素的hashcode通过再哈希后得到的值的高位,而定位HashEntry直接使用的是再哈希后的值。其目的是避免两次哈希后的值一样,导致元素虽然在Segment里散列开了,但是却没有在HashEntry里散列开。
hash &&& segmentShift) & segmentMask//定位Segment所使用的hash算法
int index = hash & (tab.length - 1);// 定位HashEntry所使用的hash算法
ConcurrentHashMap的Put操作
由于put方法里需要对共享变量进行写入操作,所以为了线程安全,在操作共享变量时必须得加锁。Put方法首先定位到Segment,然后在Segment里进行插入操作。插入操作需要经历两个步骤,第一步判断是否需要对Segment里的HashEntry数组进行扩容,第二步定位添加元素的位置然后放在HashEntry数组里。
是否需要扩容。在插入元素前会先判断Segment里的HashEntry数组是否超过容量(threshold),如果超过阀值,数组进行扩容。值得一提的是,Segment的扩容判断比HashMap更恰当,因为HashMap是在插入元素后判断元素是否已经到达容量的,如果到达了就进行扩容,但是很有可能扩容之后没有新元素插入,这时HashMap就进行了一次无效的扩容。
如何扩容。扩容的时候首先会创建一个两倍于原容量的数组,然后将原数组里的元素进行再hash后插入到新的数组里。为了高效ConcurrentHashMap不会对整个容器进行扩容,而只对某个segment进行扩容。
ConcurrentHashMap的size操作
如果我们要统计整个ConcurrentHashMap里元素的大小,就必须统计所有Segment里元素的大小后求和。Segment里的全局变量count是一个volatile变量,那么在多线程场景下,我们是不是直接把所有Segment的count相加就可以得到整个ConcurrentHashMap大小了呢?不是的,虽然相加时可以获取每个Segment的count的最新值,但是拿到之后可能累加前使用的count发生了变化,那么统计结果就不准了。所以最安全的做法,是在统计size的时候把所有Segment的put,remove和clean方法全部锁住,但是这种做法显然非常低效。 因为在累加count操作过程中,之前累加过的count发生变化的几率非常小,所以ConcurrentHashMap的做法是先尝试2次通过不锁住Segment的方式来统计各个Segment大小,如果统计的过程中,容器的count发生了变化,则再采用加锁的方式来统计所有Segment的大小。
那么ConcurrentHashMap是如何判断在统计的时候容器是否发生了变化呢?使用modCount变量,在put , remove和clean方法里操作元素前都会将变量modCount进行加1,那么在统计size前后比较modCount是否发生变化,从而得知容器的大小是否发生变化。
JDK1.6源代码。
《Java并发编程实践》。
方腾飞,花名清英,淘宝资深开发工程师,关注并发编程,目前在广告技术部从事无线广告联盟的开发和设计工作。个人博客: 微博: 欢迎通过我的微博进行技术交流。
感谢对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至。也欢迎大家通过新浪微博()或者腾讯微博()关注我们,并与我们的编辑和其他读者朋友交流。
Author Contacted
语言 & 开发
64 他的粉丝
架构 & 设计
223 他的粉丝
0 他的粉丝
0 他的粉丝
1320 他的粉丝
告诉我们您的想法
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
Re: 腾飞,再来一节介绍 多线程程序的定位手段。
Re: 腾飞,再来一节介绍 多线程程序的定位手段。
Re: 腾飞,再来一节介绍 多线程程序的定位手段。
介绍下happens-before吧
写得很详细
Re: 写得很详细
Re: 腾飞,再来一节介绍 多线程程序的定位手段。
Re: 介绍下happens-before吧
示范代码重构下就不会把CPU打到100%
1.7 中 count 并不是 volatile 的
Re: 1.7 中 count 并不是 volatile 的
Re: 示范代码重构下就不会把CPU打到100%
Re: 示范代码重构下就不会把CPU打到100%
还可以再多讲一些的嘛
Re: 还可以再多讲一些的嘛
Re: 1.7 中 count 并不是 volatile 的
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
赞助商链接
InfoQ每周精要
订阅InfoQ每周精要,加入拥有25万多名资深开发者的庞大技术社区。
架构 & 设计
文化 & 方法
<及所有内容,版权所有 &#169;
C4Media Inc.
服务器由 提供, 我们最信赖的ISP伙伴。
北京创新网媒广告有限公司
京ICP备号-7
找回密码....
InfoQ账号使用的E-mail
关注你最喜爱的话题和作者
快速浏览网站内你所感兴趣话题的精选内容。
内容自由定制
选择想要阅读的主题和喜爱的作者定制自己的新闻源。
设置通知机制以获取内容更新对您而言是否重要
注意:如果要修改您的邮箱,我们将会发送确认邮件到您原来的邮箱。
使用现有的公司名称
修改公司名称为:
公司性质:
使用现有的公司性质
修改公司性质为:
使用现有的公司规模
修改公司规模为:
使用现在的国家
使用现在的省份
Subscribe to our newsletter?
Subscribe to our industry email notices?
我们发现您在使用ad blocker。
我们理解您使用ad blocker的初衷,但为了保证InfoQ能够继续以免费方式为您服务,我们需要您的支持。InfoQ绝不会在未经您许可的情况下将您的数据提供给第三方。我们仅将其用于向读者发送相关广告内容。请您将InfoQ添加至白名单,感谢您的理解与支持。怎么使用 ConcurrentHashMap 才能是线程安全的? - ITeye问答
public class test {
public static Map chm = new ConcurrentHashMap();
public static void main(String[] args)
test.chm.put("test", 1);
tht t1 = new tht();
t1.start();
for(int i=1;i&1000;i++)
th t = new th(i);
t.start();
class th extends Thread
private int number = 0;
public th(int _number)
number = _
public void run()
boolean bo =
while(bo){
int state = (Integer)test.chm.get("test");
if(state == 9 )
System.out.println("线程:"+number+",停止!");
System.out.println("线程:"+number+",state="+state+",time:"+System.currentTimeMillis());
class tht extends Thread
public tht()
public void run()
boolean bo =
while(bo){
int state = (int)(Math.random() * 1000000);
test.chm.put("test", state);
if(state == 9)
System.out.println("线程:-1,停止,time="+System.currentTimeMillis()+",--------------------------------------------");
运行多次运行后出现结果:
线程:237,state=178474,time:1线程:-1,停止,time=1,--------------------------------------------线程:236,state=178474,time:1
线程:337,state=178474,time:1
线程:876,state=178474,time:1
ConcurrentHashMap不是线程安全的么,那么多个线程同时读和改的时候,从运行结果看改的线程获取到锁以后,读的线程还是可以继续执行的.到底怎么使用ConcurrentHashMap才能保证数据一致?
问题补充:正常应该是,改state赋值为9后,所有读线程停止工作.但改的线程put时,读的线程并没有等待放锁后在读呢?
ConcurrentHashMap 读操作是不加锁的,写操作也是分segment加锁的;
不过楼主你这个测试代码不能说明修改时读操作获取了锁,即使读和写都加同一个锁,一样是这个结果
我仔细研究研究了一下你的需求,以下两处需要同步执行 即修改时不能获取& 获取时不能修改
&&&&&&&&&& synchronized (ConcurrentTest.chm) {
&&&&&&&&&&&&&&& state = (Integer)ConcurrentTest.chm.get("test");
&&&&&&&&&&& }
&&&&&&&&&&& synchronized (ConcurrentTest.chm) {
&&&&&&&&&&&&&&& state = (int)(Math.random() * 1000000);
&&&&&&&&&&&&&&& ConcurrentTest.chm.put("test", state);
&&&&&&&&&&& }
你之前问 结果为什么是这样的& 为什么写停止了 读还继续
线程:237,state=178474,time:1
线程:-1,停止,time=1,--------------------------------------------
线程:236,state=178474,time:1
线程:337,state=178474,time:1
线程:876,state=178474,time:1
这是因为线程调度问题,,即并发执行引起的,只要不是线性肯定会有这种问题。线程调度是基于时间片的 如果时间片用完则会把cpu周期让给其他线程
1、10:00 读线程1读到1 然后时间分片归还并暂停
2、10:01 读线程2读到2 然后时间分片归还并暂停
3、10:02 写线程 写9 然后state改成9了
4、此时读线程1 读线程2得到时间片 还得把1/2输出啊
import java.util.HashM
import java.util.M
import java.util.concurrent.ConcurrentHashM
public class ConcurrentTest {
public static Map chm = new HashMap();
public static void main(String[] args) {
ConcurrentTest.chm.put("test", 1);
tht t1 = new tht();
t1.start();
for(int i=1;i&1000;i++) {
th t = new th(i);
t.start();
class th extends Thread
private int number = 0;
public th(int _number) {
number = _
public void run()
boolean bo =
while(bo){
int state = 0;
synchronized (ConcurrentTest.chm) {
state = (Integer)ConcurrentTest.chm.get("test");
if(state == 9) {
System.out.println("线程:"+number+",state="+state+",time:"+System.currentTimeMillis());
class tht extends Thread {
public tht() {
public void run() {
boolean bo =
while(bo){
int state = 0;
synchronized (ConcurrentTest.chm) {
state = (int)(Math.random() * 1000000);
ConcurrentTest.chm.put("test", state);
if(state == 9) {
System.out.println("线程:-1,停止,time="+System.currentTimeMillis()+",--------------------------------------------");
我感觉兄弟的问题不是put的问题,你看看ConcurrentMap的replace方法
另外说一下,锁很影响性能,put两个不同的key,没必要加锁的。
----------------------------------------------------------------
偶的小域名,求被收购:[url] [/url]
这类复合操作,需要将涉及的操作都同步起来,才能做到线程安全。
ConcurrentHashMap类似于HashMap 可以线程安全的并发访问,你可以先了解下
ConcurrentHashMap保证在同一时刻只有一个读/写& 同一时刻
1、th
int state = (Integer)test.chm.get("test");& //此时间读 别人不会写
if(state == 9 )&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& // 但这里就不保证了
2、tht
test.chm.put("test", state);&&&&&&&&&&&&&&&&&&&& //写的时候别人得等待
正常应该是,改state赋值为9后,所有读线程停止工作.但改的线程put时,读的线程并没有等待放锁后在读呢?
只保证chm的并发访问& 线程还是没有同步的 执行顺序是不确定的。
已解决问题
未解决问题

我要回帖

更多关于 下列哪些线程是安全的 的文章

 

随机推荐