泛型
T和E可以是任何类型
class People<T,E>{
T age;//String
E name;//int
public T getAge() {
return age;
}
public void setAge(T age) {
this.age = age;
}
public E getName() {
return name;
}
public void setName(E name) {
this.name = name;
}
}
进行调用:
下面两种都行
People<Integer,String> p=new People();
People<String,Integer> p=new People();
List
Arraylist
ArrayList<String > a=new ArrayList();
//等效方式:List list =new ArrayList();
a.add("a");
a.add("a");
a.add("a");
a.add("b");//向集合里增加元素
List list =new CopyOnWriteArrayList();//写时复制
a.set(2,"mama");//将集合里第二个元素替换为"mama"
list.remove("a");//将特定的元素("a")删除
//迭代器
Iterator it=a.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
//集合中的a.size相当于a.length
for(int i=0;i<a.size();i++){
System.out.println(a.get(i));
}
//foreach循环/遍历
for(Object i:a){
System.out.println(i);
}
//将集合转换为数组
Object o[]=a.toArray(); //将集合转换为数组
//List list = Arrays.asList(a); 将数组转换为集合
for(int i=0;i<o.length;i++){
System.out.println(o[i]);
}
//lambda
a.forEach(c->System.out.println(c));
-
ArrayList并不是线程安全的,在读线程在读取ArrayList的时候如果有写线程在写数据的时候,基于fast-fail机制,会抛出ConcurrentModificationException异常,也就是说ArrayList并不是一个线程安全的容器。CopyOnWriteArrayList就是通过Copy-On-Write(COW),即写时复制的思想来通过延时更新的策略来实现数据的最终一致性,并且能够保证读线程间不阻塞。
-
COW通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。对CopyOnWrite容器进行并发的读的时候,不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,延时更新的策略是通过在写的时候针对的是不同的数据容器来实现的,放弃数据实时性达到数据的最终一致性。
set:
Student s1 = new Student("小龙女", 23);
Student s2 = new Student("任盈盈", 24);
Student s3 = new Student("小龙女", 23);
Student s4 = new Student("东方不败", 25);
Student s5 = new Student("伊琳", 29);
Student s6 = new Student("周芷若", 30);
HashSet<Student> hashSet = new HashSet<>();
hashSet.add(s1);
hashSet.add(s2);
hashSet.add(s3);
hashSet.add(s4);
hashSet.add(s5);
hashSet.add(s6);//增加数据
hashSet.remove("东方不败");//删除"东方不败"
for (Student student : hashSet) {
System.out.println(student.getName()+"=="+student.getAge());
}
}
map
Map < String,Integer >map=new ConcurrentHashMap();
map.put("bab",1);
map.put("mam",2);//增加元素
map.remove(1);//删除索引1指向的元素
map.keySet().forEach(c->System.out.println(c));//只输出键
map.values().forEach(c->System.out.println(c));//只输出值
map.entrySet().forEach(c->System.out.println(c));//都输出
不用lambda输出:
Set <Map.Entry<Integer,String>>set=map.entrySet();
Iterator <Map.Entry<Integer,String>> it=set.iterator();
while(it.hasNext()){
Map.Entry<Integer,String> a=it.next();
System.out.println(a.getKey()+a.getValue());
}
-
HashMap:每个隔间都没锁门,有人想上厕所,管理员指给他一个隔间,里面没人的话正常用,里面有人的话把这个人赶出来然后用。
优点,每个人进来不耽误都能用;缺点,每一个上厕所的人都有被中途赶出来的危险。 -
Hashtable:在卫生间外面安装一个大门,有人想上厕所,问管理员要一个钥匙进门,把门反锁用,用完后出来,把钥匙交换给管理员。在这个人上厕所期间,其他所有人都必须在外面排号。
优点,每个人都能安心上完厕所;缺点,卫生间外面可能已经出了人命。 =_= -
ConcurrentHashMap:在卫生间每个隔间安装门锁,有人想上厕所,管理员指给他一个隔间,进来后这个隔间如果没人在用则直接用,如果有人正在用,则排号。在这期间其他人会按规则分到不同的隔间,重复上述行为。
优点:每个人都能安心上厕所,外面排队的也被均匀分摊。 -
ConcurrentHashMap的原理:
ConcurrentHashMap把Map分成了N个Segment(默认16),其中 Segment是线程同步的, 相当于分成了N个Hashtable。当实现Put方法时,在key值经过正常的hash后,还要再经过一次segmentForHash算法,用来分配具体防盗哪个Segment 。后来的线程如果经过计算也是放在这个 Segment下,则需要先获取锁,如果计算得出应该放在其他的Segment,则正常执行,不会影响效率,以此实现线程安全。 ConcurrentHashMap 使用锁分离技术,只要多个修改操作不发生在同一个 Segment上,它们就可以并发进行。
有些方法需要跨段,比如size()和containsValue(),需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。