关于java集合线程安全的一个问题

zhangkun4884 2012-07-10 03:43:13

package thread;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapTest
{
Map<String, String> map = new ConcurrentHashMap<String, String>();

/**
* 添加
*/
public void initMap()
{
for (int i = 0; i < 10000; i++)
{
map.put("" + i, "" + i);
}
}

/**
* 查询
*/
public Map<String, String> getMap()
{
try
{
Thread.sleep(1);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return map;
}

public String getValue(String key)
{
try
{
Thread.sleep(1);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return map.get(key);
}

public static void main(String[] args)
{
ConcurrentHashMapTest mapTest = new ConcurrentHashMapTest();
// 初始化
new TestThread(mapTest).start();
// 查询
// System.out.println(mapTest.getValue("9999"));
// 查询
System.out.println(mapTest.getMap().size());
}
}

/**
* 测试线程类
*/
class TestThread extends Thread
{
ConcurrentHashMapTest mapTest = null;

public TestThread(ConcurrentHashMapTest _mapTest)
{
this.mapTest = _mapTest;
}

/**
*
*/
@Override
public void run()
{
mapTest.initMap();
}
}

class TestThread2 extends Thread
{
ConcurrentHashMapTest mapTest = null;

public TestThread2(ConcurrentHashMapTest _mapTest)
{
this.mapTest = _mapTest;
}

/**
*
*/
@Override
public void run()
{
mapTest.getMap();
}
}

代码如上:这里有一个问题一直纠结我很久了,想问下.
这里用的线程安全的集合ConcurrentHashMap,按理说它提供的所有方法都是线程安全的,在操作ConcurrentHashMap不需要做其它线程安全的处理了。
但是在测试过程中发现假如有两个线程同时操作map,一个执行initMap操作,一个执行getMap,这个时候预期System.out.println(mapTest.getMap().size());这段代码会返回10000,但是执行的结果发现这个返回值会每次都不一样。

这样的话是不是需要在两个方法中将map加上同步代码块?我测试过加上同步代码块后

/**
* 添加
*/
public void initMap()
{
synchronized (map)
{
for (int i = 0; i < 10000; i++)
{
map.put("" + i, "" + i);
}
}
}

/**
* 查询
*/
public Map<String, String> getMap()
{
try
{
// 让其它线程先持有map的锁
Thread.sleep(1);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
synchronized (map)
{
return map;
}

}

System.out.println(mapTest.getMap().size());这段代码会始终返回10000.

综上所述:在多线程的环境中使用了线程安全的集合类型,是否还需要在方法中对集合进行同步?

P.S:附上jdk中对于线程安全集合(CopyOnWriteArraySet)的一个示例用法,请大鸟释疑,谢谢!

示例用法。 以下代码使用一个写时复制(copy-on-write)的 set,以维护在状态更新时执行某项操作的一组 Handler 对象。

class Handler { void handle(); ... }

class X {
private final CopyOnWriteArraySet<Handler> handlers = new CopyOnWriteArraySet<Handler>();
public void addHandler(Handler h) { handlers.add(h); }

private long internalState;
private synchronized void changeState() { internalState = ...; }

public void update() {
changeState();
for (Handler handler : handlers)
handler.handle();
}
}

...全文
636 20 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
Damn_boy 2012-07-11
  • 打赏
  • 举报
回复
map貌似要自己来做同步 hashmap就不要。
看看这个
http://www.jdon.com/17133
RDroid 2012-07-10
  • 打赏
  • 举报
回复
ConcurrentHashMap 是为多线程并发而设计的类
如果仅仅是为了多线程间的同步,而不考虑并发的话,还是老老实实用synchronized吧
zhangkun4884 2012-07-10
  • 打赏
  • 举报
回复
[Quote=引用 16 楼 的回复:]
引用 14 楼 的回复:

我的意思是假如map中的get方法和put方法都是有synchronized关键字的话,我是不是就不存在这样的顾虑了呢?


你的顾虑指的是初始化没完成就被使用吗?
如果是的话,还是要有顾虑。。

用代码来讲,
>put方法都是有synchronized关键字的话相当于这样:

Java code


for (...) {
sync……
[/Quote]

恩,是这样的。那最终我还是需要在我的方法中这样

synchronized(...){
for (...) {
put(...);
}
}
zhangkun4884 2012-07-10
  • 打赏
  • 举报
回复
[Quote=引用 15 楼 的回复:]
引用 13 楼 的回复:

我想我是不是必须在方法中来锁住map呢,因为它这种是同步块,不能锁住整个map对象,所以会存在多个线程之间数据不合理的情况。或者还有其它什么办法达到我的要求???


达到你的需要很容易。
比如说,
1.你在主线程中先将你的map初始化好之后再启动其他的工作线程来对map操作。(修改时序)
2. 你自己封装一个带init的容器。 用一个Boolean变量……
[/Quote]

我这个map是有可能会有多个线程操作的,然后会定时加载其中的数据的。比如说后台在加载map,这个时候前台用户在访问map中的数据。
lw_China 2012-07-10
  • 打赏
  • 举报
回复
[Quote=引用 14 楼 的回复:]

我的意思是假如map中的get方法和put方法都是有synchronized关键字的话,我是不是就不存在这样的顾虑了呢?
[/Quote]

你的顾虑指的是初始化没完成就被使用吗?
如果是的话,还是要有顾虑。。

用代码来讲,
>put方法都是有synchronized关键字的话相当于这样:


for (...) {
synchronized(...){
put(...);
}
}



但是你的需求是这样:


synchronized(...){
for (...) {
put(...);
}
}

lw_China 2012-07-10
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 的回复:]

我想我是不是必须在方法中来锁住map呢,因为它这种是同步块,不能锁住整个map对象,所以会存在多个线程之间数据不合理的情况。或者还有其它什么办法达到我的要求???
[/Quote]

达到你的需要很容易。
比如说,
1.你在主线程中先将你的map初始化好之后再启动其他的工作线程来对map操作。(修改时序)
2. 你自己封装一个带init的容器。 用一个Boolean变量来标识是否已初始化完。。。。(专用容器)

等等。。
zhangkun4884 2012-07-10
  • 打赏
  • 举报
回复
[Quote=引用 12 楼 的回复:]
引用 9 楼 的回复:

那我觉得容器应该就要做到我需求这个样的同步啊(按常理来说的话应该是集合没有初始化完成的时候是不能进行其他操作的),这样才是真正的安全啊。现在同步容器不能解决我的问题,貌似还需要我自己去同步map.不然还是线程不安全。


你这么想,容器不知道你在初始化啊。
初始化是对你的程序而言的,不是对容器而言的。
容器只知道你在put.你在get,你在size...
……
[/Quote]

我的意思是假如map中的get方法和put方法都是有synchronized关键字的话,我是不是就不存在这样的顾虑了呢?
zhangkun4884 2012-07-10
  • 打赏
  • 举报
回复
[Quote=引用 11 楼 的回复:]
引用 10 楼 的回复:

同步应该就两种吧,一种是同步方法(范围是this),一种是同步块(范围是synchronized后面括号中的对象)。范围仅仅是方法的没听说过?


可能是我没表达清楚。
我这句话说的仅仅是针对具体某一种情况。
你说的没错。同步范围一般就是你说的两种,(xxxx.lock/xxxx.unlock包住的范围也也可以理解为你说的第二种。)
[/Quote]

我想我是不是必须在方法中来锁住map呢,因为它这种是同步块,不能锁住整个map对象,所以会存在多个线程之间数据不合理的情况。或者还有其它什么办法达到我的要求???
lw_China 2012-07-10
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 的回复:]

那我觉得容器应该就要做到我需求这个样的同步啊(按常理来说的话应该是集合没有初始化完成的时候是不能进行其他操作的),这样才是真正的安全啊。现在同步容器不能解决我的问题,貌似还需要我自己去同步map.不然还是线程不安全。
[/Quote]

你这么想,容器不知道你在初始化啊。
初始化是对你的程序而言的,不是对容器而言的。
容器只知道你在put.你在get,你在size...

lw_China 2012-07-10
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 的回复:]

同步应该就两种吧,一种是同步方法(范围是this),一种是同步块(范围是synchronized后面括号中的对象)。范围仅仅是方法的没听说过?
[/Quote]

可能是我没表达清楚。
我这句话说的仅仅是针对具体某一种情况。
你说的没错。同步范围一般就是你说的两种,(xxxx.lock/xxxx.unlock包住的范围也也可以理解为你说的第二种。)
zhangkun4884 2012-07-10
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 的回复:]
不需要等到线程结束,调用的方法一结束锁就释放了。
就是说同步范围仅仅是方法。
[/Quote]
同步应该就两种吧,一种是同步方法(范围是this),一种是同步块(范围是synchronized后面括号中的对象)。范围仅仅是方法的没听说过?
zhangkun4884 2012-07-10
  • 打赏
  • 举报
回复
[Quote=引用 8 楼 的回复:]
引用 7 楼 的回复:

是的,假如我的方法中没有同步,一个线程访问initMap方法,一个线程访问getValue方法,就有可能map中还没有初始化完毕,就被拿去getValue了,导致结果不准确.现在这个同步容器貌似不能满足我的需求。


没错,你知道原因所在了。
所以这并不是容器的问题,
是你的需求问题: 必须在初始化完了之后再进行其他操作。
[/Quote]

那我觉得容器应该就要做到我需求这个样的同步啊(按常理来说的话应该是集合没有初始化完成的时候是不能进行其他操作的),这样才是真正的安全啊。现在同步容器不能解决我的问题,貌似还需要我自己去同步map.不然还是线程不安全。
lw_China 2012-07-10
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 的回复:]

是的,假如我的方法中没有同步,一个线程访问initMap方法,一个线程访问getValue方法,就有可能map中还没有初始化完毕,就被拿去getValue了,导致结果不准确.现在这个同步容器貌似不能满足我的需求。
[/Quote]

没错,你知道原因所在了。
所以这并不是容器的问题,
是你的需求问题: 必须在初始化完了之后再进行其他操作。
zhangkun4884 2012-07-10
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 的回复:]
没初始化完成?
[/Quote]
是的,假如我的方法中没有同步,一个线程访问initMap方法,一个线程访问getValue方法,就有可能map中还没有初始化完毕,就被拿去getValue了,导致结果不准确.现在这个同步容器貌似不能满足我的需求。
  • 打赏
  • 举报
回复
没初始化完成?
zhangkun4884 2012-07-10
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 的回复:]
ConcurrentHashMap 采用的是分离锁的机制,锁定时只是锁定 Map.Entry 表的一部分,默认情况下是 1/16。

相当于这样的代码:


Java code
Object[] locks = new Object[0xf];

initialize locks...

synchronized(locks[keyHashCode & 0xf]) {
……
[/Quote]

V put(K key, int hash, V value, boolean onlyIfAbsent) {
lock();
try {
int c = count;
if (c++ > threshold) // ensure capacity
rehash();
HashEntry<K,V>[] tab = table;
int index = hash & (tab.length - 1);
HashEntry<K,V> first = tab[index];
HashEntry<K,V> e = first;
while (e != null && (e.hash != hash || !key.equals(e.key)))
e = e.next;

V oldValue;
if (e != null) {
oldValue = e.value;
if (!onlyIfAbsent)
e.value = value;
}
else {
oldValue = null;
++modCount;
tab[index] = new HashEntry<K,V>(key, hash, first, value);
count = c; // write-volatile
}
return oldValue;
} finally {
unlock();
}
}
刚看了ConcurrentHashMap的源码put是使用ReentrantLock来实现同步的。


假如我的方法中没有同步,有可能map中还没有初始化完毕,就被拿去用了,导致结果不准确.现在这个同步容器貌似不能满足我的需求。
  • 打赏
  • 举报
回复
ConcurrentHashMap 采用的是分离锁的机制,锁定时只是锁定 Map.Entry 表的一部分,默认情况下是 1/16。

相当于这样的代码:

Object[] locks = new Object[0xf];

initialize locks...

synchronized(locks[keyHashCode & 0xf]) {
....
}


对于并发容器返回的 size() 由于是并发处理的,所以只是一个时间点上的 size 的拷贝,其数值在高并发下并不是可靠的,这是并发容器的一个缺陷。
lw_China 2012-07-10
  • 打赏
  • 举报
回复
不需要等到线程结束,调用的方法一结束锁就释放了。
就是说同步范围仅仅是方法。
zhangkun4884 2012-07-10
  • 打赏
  • 举报
回复
不太明白,能不能详细解释下呢?我的理解是,因为集合是线程安全的,那么在一个线程调用集合的方法比如put方法时,其它线程应该是处于阻塞状态的。需要等调用put方法的线程结束后返回对象的锁后才能get。
lw_China 2012-07-10
  • 打赏
  • 举报
回复
你的问题在于没有理解同步的范围。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

其实你跟进jdk的源码就可以发现了。

你所谓的线程安全的集合类型只是在对其的put/get方法的调用上进行了同步。
但你的put一结束,正在循环时,是可以调用get的。。这也就是你每次get的结果不同的原因。

明白没?

62,635

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧