0903(046天 线程集合01)
每日一狗(田园犬西瓜瓜)
线程集合01
文章目录
1. 迭代器
**意图:**提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。
**主要解决:**不同的方式来遍历整个整合对象。
**何时使用:**遍历一个聚合对象。
实现
package com.yang1;
import java.util.ArrayList;
import java.util.Arrays;
public class Test03 {
public static void main(String[] args) {
MyContaniner mc = new MyContaniner();
Iterator it = mc.getIterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
/*
1
2
3
5
6
8
*/
// 定义迭代器接口
interface Iterator {
public boolean hasNext();
public Object next();
}
// 定义集合容器接口
interface Contaniner {
public Iterator getIterator();
}
// 构建容器实现类
class MyContaniner implements Contaniner {
private ArrayList<Integer> data;
public MyContaniner() {
data = new ArrayList<>(Arrays.asList(1, 2, 3, 5, 6, 8));
}
@Override
public Iterator getIterator() {
return new Iter();
}
private class Iter implements Iterator {
private int index;
@Override
public boolean hasNext() {
return index < data.size();
}
@Override
public Object next() {
return data.get(index++);
}
}
}
2. 并发编程和集合框架 总结 问:
2.1 并发编程
并发编程的三种性质
- 可见性
- 原子性
- 有序性
并发编程的三个问题
- 线程安全安全
- 活跃性:死锁、活锁、饥饿
- 性能
- 无锁
- 降低锁颗粒度
volatile
Java中会第一时间将自己的数据同步到内存中去,而且还会防止Java编译器的重排优化,保证有序性。
可见性
有序性
无法保证原子性
预防死锁
死锁的必要条件有四个,只需要破坏其中一项就可以解决死锁
- 互斥:synchronized就互斥呀,没法改,
- 占有且等待:同时申请多把锁
- 不可抢占:synchronized没辙,Lock可以
- 循环等待:为资源设定id,你得有这个才能申请那个,按照顺序申请
同步方法锁使用对象头实现,被synchronized修饰的方法会在方法区有一个标识ACC_SYCHRONIZED来进行标识;同步代码块使用了两个机器码来进行进入monitorenter和退出:monitorexit(有俩退出,一个异常一个正常)。
2.2 锁的分类
常见的锁
-
阻塞:需要进行状态转换
-
自旋:轻量级锁,忙等锁
-
可重入锁:自己拿着锁,自己再去申请锁直接拿到。(一定程度避免死锁)
-
读写锁:主要提高数据读写的并发性。
- 读读不互斥
- 读被申请走了,写锁无法被其他线程申请;写锁被申请走了,读锁无法被其他线程申请。
- 有读拿写锁不行;有写拿读锁可以
-
独享锁、共享锁 通过AQS来进行实现
- 独享锁。就是排它锁
- 共享锁。读锁就是共享锁
-
乐观、悲观:是面对线程安全问题的一种态度
-
乐观:轻易不出现安全问题,出现了我自己补偿(CAS),适合大量的读操作
Java搞了很多的原子类来应对CAS操作
-
悲观:肯定有问题,先上互斥锁了再说,适合大量的写操作
-
-
无锁、偏向锁、轻量级锁、重量级锁(会升级,但是不会退化)
-
自旋锁:忙等状态。
-
自适应自旋锁:忙等次数过多会被阻塞。由前一个线程阻塞的次数和锁的拥有者的状态进行动态
锁消除:1.6+引入的逃逸分析
就是我拿着锁但是就根本没去操作过公共数据
操作系统会影响点东西
- 线程加锁和释放锁,由于操作系统的原因,这中间需要在用户态和核心态之间来回切换,消耗巨大
- 线程优先级,尽可能拉大点,不同系统拥有的优先级等级不一定可以一一对应。
公平锁与非公平锁
公平:会比非公平锁多判定一个阻塞队列中是否有被阻塞的队列,队列有线程阻塞这就会把自己加到队列中
非公平锁:看锁没人用,没人就加锁,有才会去阻塞。
2.3 并发集合
两种解决并发集合安全方法
加锁:给这几个桶加一把锁,另几个桶加一把锁,不同桶之间可以并发,桶内互斥。可以降低锁的颗粒度。
List<Integet> list =
Collections.synchronizedList(new ArrayList());
拷贝后写入:搞一份备份,改的时候改这个备份,改完了改一个引用就行。
List<Integer> list =
new CopyOnWriteArrayList<Integer>();
ConcurrentHashMap
一开始的锁数组无法扩容,然后这个锁数组中存储的那个引用数组,这个数组才是可以扩容的。
1.7+:数组+Segment分段锁+单项链表。这种颗粒度太大了。
1.8+:数组+链表+红黑树,不分段了,直接给桶加锁,频繁加锁解锁影响性能,这里使用CAS保证数据的安全性。 Node:保存key,value及key的hash值的数据结构。其中value和next都用volatile修饰,保证并发的可见性。
JDK8中放弃了分段锁。
键值都不允许为null
扰动:(h ^ (h >>> 16)) & 0x7fffffff,HashMap中只有(h ^ (h >>> 16))
在操作节点的时候会使用头节点为锁对象进入同步代码块。
2.4 编程模型CAS
为了以不加锁的方式实现加锁的效果。
CAS,是Compare and Swap(对比和交换)的简称,在这个机制中有三个核心的参数:
- 主内存中存放的共享变量:V
- 工作内存中共享变量的备份值,也叫预期值:A
- 需要将共享变量更新到的最新值:B
工作流程就是,先把主内存中的V拷贝到工作内存中的A中,然后再算出自己想要更新的最新值B,在写回到主内存中时,使用A和V进行对比,如果不同那就说明在这期间值被别的线程修改了,就不能在覆盖写回了。
但是在这期间的对比写回不也不是原子的嘛,但其实这个对比与写回并不是使用Java程序保证的,而是从指令层面保证的CAS这一步就是原子的。
优点
- 保证变量操作的原子性
- 在并发量不是很多的情况下,在保证线程安全的前提CAS要比使用锁机制效率更高
- 在线程对共享资源占用时间较短的情况下,使用CAS效率也会更高
缺点
- ABA问题:三个线程,其中两个线程改来改去没改,第三个线程无法判定到底改没改
- 在并发量比较高的时候,使用CAS能成功修改的可能性比较低,那你修改失败了就不改了嘛,不能够呀,该干的事还是得干呀,不一样那我就把值拿出来再算一遍被,那你修改的次数=1/p。显然p越小越修改次数越多,这中间就浪费了好多CPU资源
扩展小芝士
- 多线程常用于频繁进行用户交互的时候;计算密集型不适合用多线程
- CAS的判定写会的原子性从机器指令上进行保证