【问题标题】:final hashmap are thread safe最终的 hashmap 是线程安全的
【发布时间】:2016-12-27 09:35:39
【问题描述】:
public class MapDem {

 final HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();

 public HashMap<Integer,Integer> getMap(){
     return map;
 }
 public void putValue(int key,int value){
     map.put(key,value);
 }

 public static void main(String args[]){
    MapDem demo = new MapDem();
    new Thread(new Runnable(){

        @Override
        public void run() {
                demo.putValue(1, 10);

        }

    }).start();

    new Thread(new Runnable(){

        @Override
        public void run() {
            demo.putValue(1, 10);

        }

    }).start();

System.out.println(demo.getMap().size());

}

}

final 字段本身是线程安全的吗?在上面的代码中map变量被标记为final,这是否意味着它是线程安全的?

如果变量不是线程安全的,我希望main-方法的输出应该是“2”,但我得到的是“1”或“0”

编辑

如果我使用 volatile 关键字声明变量,即

volatile HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();

map 变量似乎不是线程安全的,这是为什么呢?但是下面的方法似乎有效,这是为什么呢?

public  synchronized void putValue(int key,int value){
    if(map.isEmpty()){
        System.out.println("hello");
        map.put(key,value);     
}

使用Collections.unmodifiableMap(map) 会起作用吗?

【问题讨论】:

标签: java multithreading synchronization thread-safety final


【解决方案1】:

您的测试有问题。如果两个values 存储了相同的keyHashMap.put(K key, V value) will overwrite the former value with the later。因此,即使没有并发,您的“测试”也会返回1 的大小。

代码:

import java.util.HashMap;

public class MapDem {

    final HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();

    public HashMap<Integer, Integer> getMap() {
        return map;
    }

    public void putValue(int key, int value) {
        map.put(key, value);
    }

    public static void main(String args[]) {
        MapDem demo = new MapDem();

        demo.putValue(1, 10);
        demo.putValue(1, 10);

        System.out.println(demo.getMap().size());
    }
}

输出(Ideone demo):

1

有时可以看到0 的大小是由于缺少阻塞结构。您应该等待Threads 都完成,然后再调用join() on your Thread-objects 查询您的地图大小。

            Thread t1 = new Thread(new Runnable() {

                @Override
                public void run() {
                    demo.putValue(1, 10);

                }

            });
            t1.start();

            Thread t2 = new Thread(new Runnable() {

                @Override
                public void run() {
                    demo.putValue(1, 10);

                }

            });
            t2.start();

            try {
                t1.join();
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(demo.getMap().size());

As mentioned by @SachinSarawgi, final 不会使您的代码成为线程安全的,as further explained by @assylias, volatile 在这种情况下不会削减它。

如果您需要线程安全的 Hashmap,请使用 ConcurrentHashMap

如果您决心编写自己的 Map interface 的线程安全实现,我建议先使用 Oracle's Lesson on Concurrency,然后是 Brian Goetz's "Java Concurrency in Practice",也许还有一点点 Javier Fernández González' "Mastering Concurrency Programming with Java 8"

【讨论】:

    【解决方案2】:

    对您的问题的直接回答是:不,final 关键字不会使字段线程安全

    该关键字仅告诉编译器它必须确保为该字段分配了确切的一个值(而不是零或多个分配)。

    你知道,获得多线程代码 正确被认为是困难是有原因的。

    正确的多线程的本质是:当一些状态可以被一个线程更新,但被其他线程使用(或更新)..确保你只得到那些状态你想看到的变化。

    长话短说:你有很多事情要做;一个好的起点是here

    【讨论】:

      【解决方案3】:

      线程安全是访问map 变量:读取该变量的所有线程都将看到相同的对象引用。

      然而 HashMap (get/put) 上的操作不是线程安全的,这与 map 是否是最终的事实无关。

      因此,除非您在 putValue 方法周围添加一些并发控制,否则您的代码不是线程安全的 - 例如将其设置为 synchronized

      【讨论】:

      • 我应该得到答案 2 为什么 1 或 0 即将到来并将其声明为 final .Final 是线程安全的。
      • 为什么你认为你应该得到 2? HashMap 不是线程安全的——这意味着地图上并发操作的结果是未定义的。 0、1、2 或 -13 都是合法的输出。
      • @coder25 你的假设是错误的。正如 assylias 已经解释的那样,对象本身是线程安全的,但不是它的字段。所以 hashmap 对象(引用)是线程安全的,但是你对它执行的任何操作都不是线程安全的,除非你使用 synchronized 块。 final 不是将对象转换为线程安全对象的魔法关键字。
      • 如果我使用 volatile 变量,那么它也不工作。同步它工作
      • @coder25 volatile 和 final 一样,修改它所应用的变量的语义,而不是底层对象。
      【解决方案4】:

      将引用变量设为final,确保引用变量不能改变它被分配到的对象引用。

      但是对象的值可能会改变。同样的事情发生在这里你的对象值可能会改变。现在需要同步更改值的代码。 您可以使用ConcurrentHashMap 摆脱所有同步问题。你可以阅读它here

      使用ConcurrentHashMap 确保对象上的每个写操作都应由一个线程一次处理。它还优化了HashMap 的阅读。它将HashMap分成块,不同的线程可以从不同的块中读取。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-25
        • 2013-12-01
        • 1970-01-01
        • 2011-02-10
        相关资源
        最近更新 更多