HashMap、Hashtable、ConcurrentHashMap线程安全性分析

先看代码:

HashMap

package com.hash;

import java.util.HashMap;
import java.util.Map;

public class HashMapTest {
    /**
     * NUMBER = 50,表示 50 个线程分别执行 put 方法 50 次 线程安全的情况下因该 map size 应该为 2500
     */
    public static final int NUMBER = 50;

    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        for (int i = 0; i < NUMBER; i++) {
            new Thread(new HashMapTask(map)).start();
        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("map size = " + map.size());
    }
}

class HashMapTask implements Runnable {

    Map<String, String> map;

    public HashMapTask(Map<String, String> map) {
        this.map = map;
    }

    @Override
    public void run() {
        for (int i = 0; i < HashMapTest.NUMBER; i++) {
            map.put(i + "-" + Thread.currentThread().getName(), "test");
        }
    }
}

运行结果:

开了 50 个线程往 HashMap 中添加元素,每个线程执行 50 次 put 方法,在线程安全的情况下,map 中应该有 2500 个键值对,但是执行的结果大都是小与 2500 的(并不会产生死循环)。

Hashtable

package com.hash;

import java.util.Hashtable;

public class HashTableTest {
    /**
     * NUMBER = 50,表示 50 个线程分别执行 put 方法 50 次
     * 线程安全的情况下因该 table size 应该为 2500
     */
    public static final int NUMBER = 50;
    public static void main(String[] args) {
        Hashtable<String, String> table = new Hashtable<>();
        for (int i = 0; i < NUMBER; i++) {
            new Thread(new HashTableTask(table)).start();
        }
        
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("table size = " + table.size());
    }
}

class HashTableTask implements Runnable {

    Hashtable<String, String> table;

    public HashTableTask(Hashtable<String, String> table) {
        this.table = table;
    }

    @Override
    public void run() {
        for (int i = 0; i < HashTableTest.NUMBER; i++) {
            table.put(i + "-" + Thread.currentThread().getName(), "test");
        }
    }
}

运行结果:

无论运行多少次,结果都是size=2500。

ConcurrentHashMap

package com.hash;

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

public class ConcurrentHashMapTest {
    /**
     * NUMBER = 50,表示 50 个线程分别执行 put 方法 50 次 线程安全的情况下因该 map size 应该为 2500
     */
    public static final int NUMBER = 50;

    public static void main(String[] args) {
        Map<String, String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < NUMBER; i++) {
            new Thread(new ConcurrentHashMapTask(map)).start();
        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("map size = " + map.size());
    }
}

class ConcurrentHashMapTask implements Runnable {

    Map<String, String> map;

    public ConcurrentHashMapTask(Map<String, String> map) {
        this.map = map;
    }

    @Override
    public void run() {
        for (int i = 0; i < ConcurrentHashMapTest.NUMBER; i++) {
            map.put(i + "-" + Thread.currentThread().getName(), "test");
        }
    }
}

运行结果:

无论运行多少次,结果都是size=2500。

总结:

1.HashMap是线程不安全的。

2.Hashtable和ConcurrentHashMap是线程安全的。

我想线程安全但是我又想效率高?

使用 ConcurrentHashMap,其底层采用分段的数组+链表实现,线程安全,通过把 Map 分为 N 个 Segment(部分),可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。

Hashtable 之所以效率低主要是使用了 synchronized 关键字对 put 等操作进行加锁,而 synchronized 关键字加锁是对整张 Hash 表的,即每次锁住整张表让线程独占,致使效率低下,而 ConcurrentHashMap 在对象中保存了一个 Segment 数组,即将整个Hash表划分为多个分段;而每个Segment元素,即每个分段则类似于一个Hashtable;这样,在执行put操作时首先根据hash算法定位到元素属于哪个Segment,然后对该Segment加锁即可,因此, ConcurrentHashMap 在多线程并发编程中可是实现多线程put操作。

猜你喜欢

转载自www.cnblogs.com/gaopengpy/p/11916932.html