hashmap是一个非线程安全的集合。
他的线程不安全出现在,并发情况下可能会出现链表成环的问题,导致程序在执行get操作时形成死循环。
hashmap成环原因的代码出现在transfer代码中,也就是扩容之后的数据迁移部分
解决问题:
使用synchronize ,或者使用collection.synchronizeXXX方法。或者使用concurrentHashmap来解决。
为什么Hashtable是线程安全的,因为它的remove,put,get做成了同步方法,保证了Hashtable的线程安全性。
每个操作数据的方法都进行同步控制之后,由此带来的问题任何一个时刻只能有一个线程可以操纵Hashtable,所以其效率比较低。
HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度。
利用CAS+Synchronized来保证并发更新的安全,底层依然采用"数组+链表+红黑树"的存储结构
CAS: 全称Compare and swap,字面意思:”比较并交换“,一个 CAS 涉及到以下操作:
我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。 比较 A 与 V 是否相等。(比较) 如果比较相等,将 B 写入 V。否则什么都不做(交换) 返回操作是否成功。 当多个线程尝试使用CAS同时更新同一个变量的时候,只有其中一个线程能够更新变量的值。当其他线程失败后,不会像获取锁一样被挂起,而是可以再次尝试,或者不进行任何操作,这种灵活性就大大减少了锁活跃性风险。 我们知道采用锁对共享数据进行处理的话,当多个线程竞争的时候,都需要进行加锁,没有拿到锁的线程会被阻塞,以及唤醒,这些都需要用户态到核心态的转换,这个代价对阻塞线程来说代价还是蛮高的,那cas是采用无锁乐观方式进行竞争,性能上要比锁更高些才是,为何不对锁竞争方式进行替换? 在高度竞争的情况下,锁的性能将超过cas的性能,但在中低程度的竞争情况下,cas性能将超过锁的性能。多数情况下,资源竞争一般都不会那么激烈。 复制代码
JVM中的堆和方法区主要用来存放对象(方法区中也储存了一些静态变量和全局变量等信息),那么我们要使用GC算法对其进行回收时首先要考虑的就是 该对象是否应该被回收 。即判断 该对象是否还有其他的引用或者关联使得该对象处于存活状态 ,我们需要将不在存活状态的对象 标记出 ,以便GC回收。
在对象头处维护一个counter,每增加一次对该对象的引用计数器自加,如果对该对象的引用失联,则计数器自减。当counter为0时,表明该对象已经被废弃,不处于存活状态。这种方式一方面无法区分软、虛、弱、强引用类别。另一方面,会造成死锁,假设两个对象相互引用始终无法释放counter,永远不能GC
一.通过一系列为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明该对象是不可用的。如果对象在进行可行性分析后发现没有与GC Roots相连的引用链,也不会理解死亡。
二、finalize()方法最终判定对象是否存活 即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历再次标记过程。 标记的前提是对象在进行可达性分析后发现没有与GC Roots相连接的引用链。
Finalize()方法是对象脱逃死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象要在finalize()中成功拯救自己---- 只要重新与引用链上的任何的一个对象建立关联即可 ,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了。 。
在Java中,可作为GC Root的对象包括以下几种:
该算法分为标记和清除两个阶段。标记就是把所有活动对象都做上标记的阶段;清除就是将没有做上标记的对象进行回收的阶段。如下图所示。 复制代码
复制算法就是将内存空间按容量分成两块。当这一块内存用完的时候,就将还存活着的对象复制到另外一块上面,然后把已经使用过的这一块一次清理掉。这样使得每次都是对半块内存进行内存回收。内存分配时就不用考虑内存碎片等复杂情况,只要移动堆顶的指针,按顺序分配内存即可,实现简单,运行高效。
标记-压缩算法与标记-清理算法类似,只是后续步骤是让所有存活的对象移动到一端,然后直接清除掉端边界以外的内存。 复制代码
引用分为以下四类:
3.1 强引用 强引用就是指在程序代码中普遍存在的,类似Object obj = new Object()这类似的引用, 只要强引用在,垃圾搜集器永远不会搜集被引用的对象 。也就是说, 宁愿出现内存溢出,也不会回收这些对象 。
3.2 软引用 软引用是用来描述一些有用但并不是必需的对象,在Java中用java.lang.ref.SoftReference类来表示。对于软引用关联着的对象, 只有在内存不足的时候JVM才会回收该对象 。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存:比如网页缓存、图片缓存等。
3.3 弱引用 弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时, 无论内存是否充足,都会回收被弱引用关联的对象 。在java中,用java.lang.ref.WeakReference类来表示。下面是使用示例:
import java.lang.ref.WeakReference;
WeakReference<String> sr = new WeakReference<String>(new String("hello")); System.out.println(sr.get()); System.gc(); //通知JVM的gc进行垃圾回收 System.out.println(sr.get()); 复制代码
3.4 虚引用 虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。 如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。 要注意的是,虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
CREATE TABLE `sys_comp` ( `id_` bigint(32) NOT NULL, `name` varchar(50) DEFAULT NULL, PRIMARY KEY (`id_`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; insert into sys_comp valus(1,"小明") insert into sys_comp valus(5,"伯安") insert into sys_comp valus(10,"小李") 复制代码
这是一个很简单的表,id是主键,name是普通的列,现在一个事务要读取name= '伯安'的值,并且用的当前读,
select * from sys_comp where name = '伯安' for update ,这句话锁住了哪一行呢,id = 5的肯定锁住了,1和10有没有锁住呢,假设没有锁住,其他事务执行 update sys_comp set name = '伯安' where id = 10 ,这样子name = '伯安'的就有两行了,就出现了幻读,那把1 和10这两行也锁住把,这样这两行就不能更改了,但是还是不行,我再执行insert into sys_comp valus(15,"伯安"),第二次读依旧出现了幻行15,把所有的行都锁住,幻读依旧没有解决,
于是引入了一个新的锁,叫间隙锁,顾名思义,它把间隙也锁住了, ____ 1 ____ 5____10___,横线就是间隙锁, 间隙锁和间隙锁是不冲突的,他们都锁间隙,防止数据的插入 ,这样 执行插入就会block住,进入等待 ,从而解决了幻读问题,这也说明你平时遇到的死锁不单单是因为行锁造成的,间隙锁的范围可大多了,所以如果业务允许,你可以将你的mysql隔离级别设计成读已提交。