hashmap效率高单线程不安全,hashTable效率低但线程安全html
由于hashTable使用synchronized来保证线程安全,因此效率十分低,好比线程1使用put插入数据时,线程2既不能使用put插入,也不能使用get获取。java
锁分段技术
HashTable容器在竞争激烈的并发环境下表现出效率低下的缘由,是由于全部访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不一样数据段的数据时,线程间就不会存在锁竞争,从而能够有效的提升并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分红一段一段的存储,而后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其余段的数据也能被其余线程访问。有些方法须要跨段,好比size()和containsValue(),它们可能须要锁定整个表而而不只仅是某个段,这须要按顺序锁定全部段,操做完毕后,又按顺序释放全部段的锁。这里“按顺序”是很重要的,不然极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,而且其成员变量实际上也是final的,可是,仅仅是将数组声明为final的并不保证数组成员也是final的,这须要实现上的保证。这能够确保不会出现死锁,由于得到锁的顺序是固定的。算法
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap相似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每一个HashEntry是一个链表结构的元素, 每一个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先得到它对应的Segment锁。数据库
concurrentHashMap转自https://www.cnblogs.com/ITtangtang/p/3948786.html编程
并发编程的缺点
时间片是CPU分配给各个线程的时间,由于时间很是短,因此须要CPU不断地切换线程,让咱们以为多个线程是同时运行的。并且每次切换时都须要把当前的状态保存起来,以便下次切换过来时能够恢复到先前执行时的状态,而这个切换很是损耗性能,过于频繁反而没法发挥出多线程的优点。一般减小上下文切换能够采用无锁并发编程,CAS算法,使用最少的线程和使用协程。数组
无锁并发编程:能够参照concurrentHashMap锁分段的思想,不一样的线程处理不一样段的数据,这样在多线程竞争的条件下,能够减小上下文切换的时间。安全
CAS算法,利用Atomic下使用CAS算法来更新数据,使用了乐观锁,能够有效的减小一部分没必要要的锁竞争带来的上下文切换多线程
使用最少线程:避免建立不须要的线程,好比任务不多,可是建立了不少的线程,这样会形成大量的线程都处于等待状态并发
协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换ide
因为上下文切换也是个相对比较耗时的操做,因此在"java并发编程的艺术"一书中有过一个实验,并发累加未必会比串行累加速度要快。 可使用Lmbench3测量上下文切换的时长 vmstat测量上下文切换次数
线程安全
多线程编程中最难以把握的就是临界区线程安全问题,稍微不注意就会出现死锁的状况,一旦产生死锁就会形成系统功能不可用。
public class DeadLockDemo { private static String resource_a = "A"; private static String resource_b = "B"; public static void main(String[] args) { deadLock(); } public static void deadLock() { Thread threadA = new Thread(new Runnable() { @Override public void run() { synchronized (resource_a) { System.out.println("get resource a"); try { Thread.sleep(3000); synchronized (resource_b) { System.out.println("get resource b"); } } catch (InterruptedException e) { e.printStackTrace(); } } } }); Thread threadB = new Thread(new Runnable() { @Override public void run() { synchronized (resource_b) { System.out.println("get resource b"); synchronized (resource_a) { System.out.println("get resource a"); } } } }); threadA.start(); threadB.start(); } }
在上面的这个demo中,开启了两个线程threadA, threadB,其中threadA占用了resource_a, 并等待被threadB释放的resource _b。threadB占用了resource _b正在等待被threadA释放的resource _a。所以threadA,threadB出现线程安全的问题,造成死锁。一样能够经过jps,jstack证实这种推论:
"Thread-1": waiting to lock monitor 0x000000000b695360 (object 0x00000007d5ff53a8, a java.lang.String), which is held by "Thread-0" "Thread-0": waiting to lock monitor 0x000000000b697c10 (object 0x00000007d5ff53d8, a java.lang.String), which is held by "Thread-1" Java stack information for the threads listed above: =================================================== "Thread-1": at learn.DeadLockDemo$2.run(DeadLockDemo.java:34) - waiting to lock <0x00000007d5ff53a8(a java.lang.String) - locked <0x00000007d5ff53d8(a java.lang.String) at java.lang.Thread.run(Thread.java:722) "Thread-0": at learn.DeadLockDemo$1.run(DeadLockDemo.java:20) - waiting to lock <0x00000007d5ff53d8(a java.lang.String) - locked <0x00000007d5ff53a8(a java.lang.String) at java.lang.Thread.run(Thread.java:722) Found 1 deadlock.
如上所述,彻底能够看出当前死锁的状况。
那么,一般能够用以下方式避免死锁的状况:
- 避免一个线程同时得到多个锁;
- 避免一个线程在锁内部占有多个资源,尽可能保证每一个锁只占用一个资源;
- 尝试使用定时锁,使用lock.tryLock(timeOut),当超时等待时当前线程不会阻塞;
- 对于数据库锁,加锁和解锁必须在一个数据库链接里,不然会出现解锁失败的状况
因此,如何正确的使用多线程编程技术有很大的学问,好比如何保证线程安全,如何正确理解因为JMM内存模型在原子性,有序性,可见性带来的问题,好比数据脏读,DCL等这些问题(在后续篇幅会讲述)。而在学习多线程编程技术的过程当中也会让你收获颇丰。