- 浏览: 468472 次
- 性别:
- 来自: 杭州
文章分类
- 全部博客 (146)
- Maven (3)
- Quartz (10)
- Hessian (3)
- JDK (42)
- eclipse (4)
- 设计思想 (6)
- XML (8)
- JavaMail (1)
- Spring (11)
- mina (1)
- HsqlDb (1)
- Cache (2)
- Tool (6)
- 心情 (5)
- JQuery (0)
- Hadoop (5)
- Hbase (3)
- 自动构建 (7)
- JNDI (0)
- 代码赏析 (5)
- Oracle (1)
- Excel (4)
- Effective Java (5)
- JAXB (4)
- fdafasdf (1)
- ccc (0)
- web (3)
- concurrent (1)
- CVS (1)
- eclipse plugin (2)
- Apache (10)
最新评论
-
chxiaowu:
nice!
Quartz实现固定执行次数 -
zxjlwt:
学习了。http://surenpi.com
自定义ClassLoader -
kadlly:
public static final Logger log ...
Hessian 权限认证 -
spring_springmvc:
java程序语言学习教程 地址http://www.zuida ...
Java-Final -
liushuiwuyan:
[img][/img]
设计模式-单例
ConcurrentHashMap是线程安全的概念已经深入人心,让我们在使用的时候有些大意了,我也懒得动脑子,直接使用,结果碰到钉子了.
这个问题让我很郁闷,程序逻辑全是对的,但是问题却明明摆在那边,最后怀疑是HashMap的问题。
执行10000次,多执行几次,或许你会发现,真的一般情况下是线程安全的,但是在大量并发的时候,线程就变得不那么安全了.
输出结果如下:
为什么出现这种情况,我在第一个地方设置值,然后取值,第二个地方再设置值,然后取值,两个值应该不同的,判断相同的时候,既然出现了。有人怀疑是ConcurrentHashMap,那你可以换成HashMap试试.结果一样.
为什么是2,2不是1,1;当然一般情况下是1:2,并发情况下就变成2,2了.
有人怀疑是初始化widgetCacheMap的问题,那么改代码如下:
真是不改不知道,一改吓一跳,这回出现刚才说的情况1,1
而且改了之后其并发问题更严重了,因为这里每一次put都需要加行锁,其并发的概念也就上升了.
推荐写法还是按第一次方法,对象的覆盖是原子的,最好加一把锁,否则你第一次覆盖了,第二次又被别人覆盖了.
于是代码如下:
保持widgetCacheMap的变更成原子状态。当然还会出现上面的情况,这是为什么呢。
因为每一个线程获取的时候,可能取的是原子1,也可能是原子2,如果在多线程获取的时候加一把锁,那么获取的就是原子X,但至少是一个原子,要么1,要么2.
于是代码如下:
结果又出现如上现象,这是为什么呢,因为锁里面还加着锁,锁最好是原子化,尽量保持最小范围,不能价懒,像我一样就悲剧了.
还是出现上面这种情况,通阅全码,发现每一次都是原子了,应该没问题了。
但是还需要考虑run方法是多线程的,只有一个线程进入test,那就算原子了.如下:
唉,这是为什么呢,syn不起作用?
开始怀疑,于是去掉所有的syn,只添加run方法中的如下:
整个进行原子操作,结果让人晕死。还是出现在,最后想了想,原来Hash或者CurrentHashMap也一样,在中间change了一下,而syn锁定的是一个不变的东西。
于如改代码如下:
这回你怎么执行都是原子操作了。
总结:ConcurrentHashMap是线程安全的,那是在他们的内部操作,其外部操作还是需要自己来保证其同步的,特别是静态的ConcurrentHashMap,其有更新和查询的过程,要保证其线程安全,需要syn一个不可变的参数才能保证其原子性
这个测试用例是有问题的,有事务的意途,但没有事务的约束,
你测的不是ConcurrentHashMap,而是两个set和change的事务性保证
是的
这个问题让我很郁闷,程序逻辑全是对的,但是问题却明明摆在那边,最后怀疑是HashMap的问题。
package com.taobao.mmp.test; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.taobao.mmp.dataobject.ServiceDO; public class TTTT { private static Map<Long, ServiceDO> widgetCacheMap = new ConcurrentHashMap<Long, ServiceDO>(); /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub for(int i=0;i<10000;i++){ Thread tt = new Thread(new Rund()); tt.start(); } } static class Rund implements Runnable{ public void run() { // TODO Auto-generated method stub test(); } /** * 1W次,总有那么几次线程不安全 */ public void test(){ TTTT tt = new TTTT(); tt.set(); int s1 = widgetCacheMap.get(1L).getStatus(); tt.change(); int s2 = widgetCacheMap.get(1L).getStatus(); if(s1==s2){ System.out.println(s1+":"+s2); } } } public void set() { Map mm= new HashMap(); ServiceDO ss = new ServiceDO(); ss.setStatus(1); mm.put(1L, ss); widgetCacheMap = mm; } public void change(){ Map mm= new HashMap(); ServiceDO ss = new ServiceDO(); ss.setStatus(2); mm.put(1L, ss); widgetCacheMap = mm; } }
执行10000次,多执行几次,或许你会发现,真的一般情况下是线程安全的,但是在大量并发的时候,线程就变得不那么安全了.
输出结果如下:
2:2 2:2 2:2
为什么出现这种情况,我在第一个地方设置值,然后取值,第二个地方再设置值,然后取值,两个值应该不同的,判断相同的时候,既然出现了。有人怀疑是ConcurrentHashMap,那你可以换成HashMap试试.结果一样.
为什么是2,2不是1,1;当然一般情况下是1:2,并发情况下就变成2,2了.
有人怀疑是初始化widgetCacheMap的问题,那么改代码如下:
public void set() { //Map mm= new HashMap(); ServiceDO ss = new ServiceDO(); ss.setStatus(1); widgetCacheMap.put(1L, ss); //widgetCacheMap = mm; } public void change(){ //Map mm= new HashMap(); ServiceDO ss = new ServiceDO(); ss.setStatus(2); widgetCacheMap.put(1L, ss); //widgetCacheMap = mm; }
真是不改不知道,一改吓一跳,这回出现刚才说的情况1,1
1:1 2:2 2:2 2:2 2:2
而且改了之后其并发问题更严重了,因为这里每一次put都需要加行锁,其并发的概念也就上升了.
推荐写法还是按第一次方法,对象的覆盖是原子的,最好加一把锁,否则你第一次覆盖了,第二次又被别人覆盖了.
于是代码如下:
public void set() { synchronized (widgetCacheMap) { Map mm= new HashMap(); ServiceDO ss = new ServiceDO(); ss.setStatus(1); mm.put(1L, ss); widgetCacheMap = mm; } } public void change(){ synchronized (widgetCacheMap) { Map mm= new HashMap(); ServiceDO ss = new ServiceDO(); ss.setStatus(2); mm.put(1L, ss); widgetCacheMap = mm; } }
保持widgetCacheMap的变更成原子状态。当然还会出现上面的情况,这是为什么呢。
因为每一个线程获取的时候,可能取的是原子1,也可能是原子2,如果在多线程获取的时候加一把锁,那么获取的就是原子X,但至少是一个原子,要么1,要么2.
于是代码如下:
public void test(){ synchronized (widgetCacheMap) { TTTT tt = new TTTT(); tt.set(); int s1 = widgetCacheMap.get(1L).getStatus(); tt.change(); int s2 = widgetCacheMap.get(1L).getStatus(); if(s1==s2){ System.out.println(s1+":"+s2); } } }
结果又出现如上现象,这是为什么呢,因为锁里面还加着锁,锁最好是原子化,尽量保持最小范围,不能价懒,像我一样就悲剧了.
/** * 1W次,总有那么几次线程不安全 */ public void test(){ TTTT tt = new TTTT(); tt.set(); int s1 = -1; synchronized (widgetCacheMap) { s1 = widgetCacheMap.get(1L).getStatus(); } tt.change(); int s2 = -2; synchronized (widgetCacheMap) { s2 = widgetCacheMap.get(1L).getStatus(); } if(s1==s2){ System.out.println(s1+":"+s2); } }
还是出现上面这种情况,通阅全码,发现每一次都是原子了,应该没问题了。
但是还需要考虑run方法是多线程的,只有一个线程进入test,那就算原子了.如下:
唉,这是为什么呢,syn不起作用?
开始怀疑,于是去掉所有的syn,只添加run方法中的如下:
/** * 1W次,总有那么几次线程不安全 */ public void test(){ synchronized (widgetCacheMap) { TTTT tt = new TTTT(); tt.set(); int s1 = -1; s1 = widgetCacheMap.get(1L).getStatus(); tt.change(); int s2 = -2; s2 = widgetCacheMap.get(1L).getStatus(); if(s1==s2){ System.out.println(s1+":"+s2); } } }
整个进行原子操作,结果让人晕死。还是出现在,最后想了想,原来Hash或者CurrentHashMap也一样,在中间change了一下,而syn锁定的是一个不变的东西。
于如改代码如下:
/** * 1W次,总有那么几次线程不安全 */ public void test(){ synchronized ("") { TTTT tt = new TTTT(); tt.set(); int s1 = -1; s1 = widgetCacheMap.get(1L).getStatus(); tt.change(); int s2 = -2; s2 = widgetCacheMap.get(1L).getStatus(); if(s1==s2){ System.out.println(s1+":"+s2); } } }
这回你怎么执行都是原子操作了。
总结:ConcurrentHashMap是线程安全的,那是在他们的内部操作,其外部操作还是需要自己来保证其同步的,特别是静态的ConcurrentHashMap,其有更新和查询的过程,要保证其线程安全,需要syn一个不可变的参数才能保证其原子性
评论
10 楼
james_lover
2014-08-07
看标题还以为是jdk发现Bug了。 博主太坑了。
9 楼
793059909
2014-06-01
xinyiwust 写道
今天无意中看到你这篇博客,我觉得有问题:
1.widgetCacheMap = mm
你这样一来,widgetCacheMap指向了一个HashMap,它已经不是一个ConcurrentHashMap了;
2.ConcurrentHashMap本身确实是一个线程安全的数据结构,但它的线程安全是有条件的,你的这个test方法: public void test(){
TTTT tt = new TTTT();
tt.set();
int s1 = widgetCacheMap.get(1L).getStatus();
tt.change();
int s2 = widgetCacheMap.get(1L).getStatus();
if(s1==s2){
System.out.println(s1+":"+s2);
}
}
本身是有问题的,假如某一个线程刚执行完tt.set()中的 widgetCacheMap.put(1L,ss);然后另个线程立刻开始执行tt.change()中的 widgetCacheMap.put(1L,ss);并且执行完毕,那么第一个数就变成了2,同理第二个数也可能变成1.
1.widgetCacheMap = mm
你这样一来,widgetCacheMap指向了一个HashMap,它已经不是一个ConcurrentHashMap了;
2.ConcurrentHashMap本身确实是一个线程安全的数据结构,但它的线程安全是有条件的,你的这个test方法: public void test(){
TTTT tt = new TTTT();
tt.set();
int s1 = widgetCacheMap.get(1L).getStatus();
tt.change();
int s2 = widgetCacheMap.get(1L).getStatus();
if(s1==s2){
System.out.println(s1+":"+s2);
}
}
本身是有问题的,假如某一个线程刚执行完tt.set()中的 widgetCacheMap.put(1L,ss);然后另个线程立刻开始执行tt.change()中的 widgetCacheMap.put(1L,ss);并且执行完毕,那么第一个数就变成了2,同理第二个数也可能变成1.
这个测试用例是有问题的,有事务的意途,但没有事务的约束,
你测的不是ConcurrentHashMap,而是两个set和change的事务性保证
8 楼
793059909
2014-06-01
是set方法没有同步:
public synchronized void run() {
// TODO Auto-generated method stub
test();
}
public synchronized void run() {
// TODO Auto-generated method stub
test();
}
7 楼
风驰电掣ljp
2014-05-23
有意思吗?
6 楼
wx65
2013-03-06
这个和concurrentmap没有关系,类安全的范围都只有他自己提供的方法,这段代码要对,本来就需要同步
5 楼
a123159521
2013-03-05
di1984HIT 写道
这个内部put和get是原子的,就是说肯定不会出现内部数据结构错误吧。
至于put和get的对象,这个状态的变化的同步可能还是需要我们自己来做。
至于put和get的对象,这个状态的变化的同步可能还是需要我们自己来做。
是的
4 楼
di1984HIT
2013-03-05
这个内部put和get是原子的,就是说肯定不会出现内部数据结构错误吧。
至于put和get的对象,这个状态的变化的同步可能还是需要我们自己来做。
至于put和get的对象,这个状态的变化的同步可能还是需要我们自己来做。
3 楼
a123159521
2012-08-24
@2楼
这是我随意草稿帖。你说的有理。
就像我的标题说的:ConcurrentHashMap内部是线程安全的,但是ConcurrentHashMap.put(xx,xx),与ConcurrentHashMap get(xx),也就是
这里是两步,如果是多线程的话,可能这里的s1的值,并不是你set()的值。可能是别的线程set的,也可能是别的线程change().所以s1的值不定的,如果在多线程开发,这里需要加个锁,就会是你预想的值.
如下
这是我随意草稿帖。你说的有理。
就像我的标题说的:ConcurrentHashMap内部是线程安全的,但是ConcurrentHashMap.put(xx,xx),与ConcurrentHashMap get(xx),也就是
tt.set() int s1 = widgetCacheMap.get(1L).getStatus();
这里是两步,如果是多线程的话,可能这里的s1的值,并不是你set()的值。可能是别的线程set的,也可能是别的线程change().所以s1的值不定的,如果在多线程开发,这里需要加个锁,就会是你预想的值.
如下
synchronized ("") { tt.set(); s1 = widgetCacheMap.get(1L).getStatus(); }
2 楼
xinyiwust
2012-08-21
今天无意中看到你这篇博客,我觉得有问题:
1.widgetCacheMap = mm
你这样一来,widgetCacheMap指向了一个HashMap,它已经不是一个ConcurrentHashMap了;
2.ConcurrentHashMap本身确实是一个线程安全的数据结构,但它的线程安全是有条件的,你的这个test方法: public void test(){
TTTT tt = new TTTT();
tt.set();
int s1 = widgetCacheMap.get(1L).getStatus();
tt.change();
int s2 = widgetCacheMap.get(1L).getStatus();
if(s1==s2){
System.out.println(s1+":"+s2);
}
}
本身是有问题的,假如某一个线程刚执行完tt.set()中的 widgetCacheMap.put(1L,ss);然后另个线程立刻开始执行tt.change()中的 widgetCacheMap.put(1L,ss);并且执行完毕,那么第一个数就变成了2,同理第二个数也可能变成1.
1.widgetCacheMap = mm
你这样一来,widgetCacheMap指向了一个HashMap,它已经不是一个ConcurrentHashMap了;
2.ConcurrentHashMap本身确实是一个线程安全的数据结构,但它的线程安全是有条件的,你的这个test方法: public void test(){
TTTT tt = new TTTT();
tt.set();
int s1 = widgetCacheMap.get(1L).getStatus();
tt.change();
int s2 = widgetCacheMap.get(1L).getStatus();
if(s1==s2){
System.out.println(s1+":"+s2);
}
}
本身是有问题的,假如某一个线程刚执行完tt.set()中的 widgetCacheMap.put(1L,ss);然后另个线程立刻开始执行tt.change()中的 widgetCacheMap.put(1L,ss);并且执行完毕,那么第一个数就变成了2,同理第二个数也可能变成1.
1 楼
loveyfore
2012-02-28
我也做了一下试验,貌似和你说的不太一致。。。旺旺我:五和
发表评论
-
Java Application Cache
2016-09-27 19:25 884Application Cache is used very ... -
Java 字符串分词
2015-01-02 14:43 1749在Java的世界里有个类型 ... -
jdk 1.6 新特性,集成Groovy, 性能很差
2014-04-02 14:27 1277性能都是相对的,如果调用量不是很大的话,可以忽略,毕竟使用为主 ... -
Fake Code easy implements
2014-04-01 15:41 1028package org.miniframe.modules ... -
JDK regex 用法及用途
2014-03-31 15:48 1215查找 Boolean flag = pattern.mat ... -
生产者消费者(四)
2014-03-04 12:32 1148需求: 多个生产者不断的生产产品,多个消费者不断的消费产品,仓 ... -
生产者消费者(三)
2014-03-04 10:59 961需求: 多个生产者不断的生产产品,多个消费者不断的消费产品,仓 ... -
生产者消费者(二)
2014-03-03 15:40 695需求: 多个生产者不断的生产产品,多个消费者不断的消费产品,仓 ... -
生产者消费者模式(一)
2014-02-28 14:30 1031需求: 多个生产者不断的生产产品,多个消费者不断的消费产品,仓 ... -
查看Class文件使用的JDK版本
2013-10-30 14:17 1116由于JDK一般是向下兼容的,所以有时候本地的JDK版本比类库的 ... -
Java源代码转码
2012-12-20 17:22 1323现在中国的项目很多,编码无非是UTF-8,GBK,GB2312 ... -
Tomcat集成OSGI,并通过JNDI开放Web调用
2012-12-03 11:22 3135Tomcat集成OSGi,首先要选择OSGI服务器,我这里采用 ... -
JDK的Logging
2012-11-07 15:49 1685jdk自带有一个log日志,对于一般的使用,仅够了. 代码如下 ... -
java.util.*
2012-11-06 14:23 1378java.util 工具包,灰常的有用,有机会一定要研读源码。 ... -
java.util.concurrent.*
2012-11-02 10:38 17781. java.util.concurrent.ArrayBl ... -
java.util.rt.*
2012-10-31 13:51 11141. java.util.HashMap 散列表,主要是以离散 ... -
巧秒设计方法,不返回null
2016-09-27 19:32 723/** * {@inheritDoc} * ... -
java doc 代码文档
2012-07-13 13:58 1330对于代码规范不解释了,网上很多。 在编写代码的时候,有一点灰 ... -
接口与抽象类
2012-07-11 16:53 11241. 接口设计必谨慎,除非业务变更,否则打死不能动接口。[不变 ... -
JVM优化机制好诡异
2012-04-20 08:43 1467long i[] = new long[1000000]; ...
相关推荐
4. **线程优先级与调度**:Java的`Thread`类提供了设置线程优先级的方法,如`setPriority(int priority)`,但实际线程调度依赖于操作系统的策略,优先级并不保证绝对的执行顺序。 5. **守护线程(Daemon)**:守护...
4. 使用并发集合:Java并发包(java.util.concurrent)提供了线程安全的集合,如ConcurrentHashMap、CopyOnWriteArrayList等,它们内部实现了线程同步,无需额外的同步控制。 5. 原子操作(Atomic):AtomicInteger...
1. 线程优先级:每个线程都有一个优先级,优先级高的线程更容易被调度执行,但并不保证绝对优先。 2.守护线程(Daemon Thread):后台运行的线程,当所有非守护线程结束时,守护线程也会自动结束。 六、线程池 Java...
但是,优先级并不是绝对的,实际的线程调度还受到操作系统的制约,因此不应过度依赖线程优先级。 七、死锁 死锁是指两个或多个线程相互等待对方释放资源,导致都无法继续执行的状态。避免死锁的关键在于合理设计...
Java的并发集合库(java.util.concurrent包)提供了线程安全的集合类,如ConcurrentHashMap、CopyOnWriteArrayList和ConcurrentLinkedQueue等,它们在多线程环境下能确保数据的一致性和安全性。 十、异常处理 在多...
8. **并发集合**:Java的并发包(java.util.concurrent)提供了线程安全的集合,如ConcurrentHashMap、CopyOnWriteArrayList等,它们在内部实现了高效的并发控制。 9. **线程中断**:通过Thread.interrupt()方法...
但在现代操作系统中,线程优先级并不总是能确保调度的绝对顺序。 通过学习和实践这个“pb多线程实现”的例程,开发者可以深入理解多线程编程的原理,掌握如何在实际项目中运用多线程提高程序效率,以及如何处理线程...
线程安全是指在多线程环境下,代码依然能正确执行。Java提供了多种线程安全的数据结构,如`ConcurrentHashMap`、`AtomicInteger`等,并提供了`synchronized`、`volatile`等关键字来保证线程可见性和数据一致性。 ...
6. **线程优先级**:Java的`Thread`类提供了设置线程优先级的方法,但实际的调度很大程度上依赖于操作系统的策略,因此优先级并不保证绝对的执行顺序。 7. **守护线程**:通过`setDaemon(true)`可以将线程标记为...
Java并发库提供了一些线程安全的集合,如ConcurrentHashMap、CopyOnWriteArrayList等,它们内部实现了锁或其他同步机制,可以在多线程环境下高效安全地使用。 八、线程间的通信 Java提供了wait()、notify()和...
9. **线程优先级**:Java线程有三个优先级:`MIN_PRIORITY`, `NORM_PRIORITY`, `MAX_PRIORITY`,但优先级并不保证绝对的执行顺序,因为线程调度由JVM决定。 10. **守护线程(Daemon Thread)**:守护线程不会阻止...
10. **并发集合类**:Java并发包(`java.util.concurrent`)提供了一系列线程安全的集合类,如`ConcurrentHashMap`、`CopyOnWriteArrayList`等,它们在多线程环境下性能更优。 11. **Future和Callable接口**:`Future...
但线程优先级并不保证绝对的执行顺序,而是影响调度策略。 5. **守护线程(Daemon)** 守护线程是为其他线程提供服务的线程,比如垃圾回收线程就是守护线程。当所有非守护线程结束时,程序会退出,即使还有守护...
5. **线程的优先级**:Java中的线程优先级范围是1(Thread.MIN_PRIORITY)到10(Thread.MAX_PRIORITY),但优先级并不绝对保证执行顺序,只是影响调度概率。 6. **线程的中断和异常**:通过调用interrupt()方法可...
6. **线程优先级**:每个线程都有一个优先级,较高的优先级线程可能比低优先级线程优先获得CPU执行时间,但Java线程的优先级并不保证绝对的执行顺序。 7. **守护线程(Daemon Thread)**:守护线程不会阻止程序的...
- **线程安全的集合**:如`ConcurrentHashMap`, `CopyOnWriteArrayList`, `BlockingQueue`等,它们内部实现了线程同步机制,避免了数据竞争问题。 7. **死锁、活锁与饥饿** - **死锁**:两个或多个线程互相等待...
Java线程具有优先级,可以影响调度策略,但并不保证绝对的执行顺序。优先级较高的线程可能会比优先级较低的线程先执行,但不总是如此。 9. **线程安全的类** Java提供了一些线程安全的集合类,如`...
但优先级并不保证绝对的执行顺序,具体取决于JVM和操作系统。 6. **线程中断**: `Thread.interrupt()`方法用于中断线程,线程可通过检查`Thread.currentThread().isInterrupted()`或`Thread.interrupted()`来响应...
优先级高的线程更容易获得执行机会,但并不保证绝对优先执行。 4. **同步机制** - synchronized关键字:用于控制对共享资源的访问,可以修饰方法或代码块。 - volatile关键字:保证变量在多线程环境中的可见性和...
9. **线程中断**:`Thread.interrupt()`方法用于中断线程,但并不能立即停止线程,而是设置一个中断标志。线程需在运行中检查`isInterrupted()`或`interrupted()`方法,以响应中断请求。 10. **死锁(Deadlock)**...