1、ArrayList
arrayList是由一维数组组成的,其 无参时,初始化为length=0的数组
可以通过其源码知道:
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!debugger知道初始化size为0
elementData[size++] = e;
return true;
}
arrayList内部运行方式如下:
add方法 =》 用扩容的方式,来实现ArrayList
0=》10 =》 15 =》 22 =》 33 第一次变为10,每次扩容为原来的1.5倍
扩容原理:
//创建一个新的数组,长度为newCapacity,
//创建完后,把旧的数组,elementData,拷贝进来
elementData = Arrays.copyOf(elementData, newCapacity);
数据这个时候,double了,旧的数组会怎么样? 会被回收如下代码解析
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//如果数组等于默认空数组长度为0赋值最小的容量10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);//扩容
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)//如果最小容量减去数组长度大于0就扩容
grow(minCapacity);
}
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; //旧的数组长度
int newCapacity = oldCapacity + (oldCapacity >> 1);//设置一个新的容量为旧的容量+旧的容量的1/2
if (newCapacity - minCapacity < 0)//初始新值小于最小容量
newCapacity = minCapacity;//赋值新的容量等于最小容量
if (newCapacity - MAX_ARRAY_SIZE > 0)//新的容量大于再打容量
newCapacity = hugeCapacity(minCapacity);//
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//将旧的数组复制给新的数组
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
扩容性能很差,怎么办?
为了避免扩容,你在使用的时候,可以指定一个比较好的capacity
2、CopyOnwrite思想
互斥锁并发度很低
一句话概括读写锁的 作用:提高互斥锁的性能
互斥锁的问题:
1、一个读很多的时候,写容易被饿死。
2、假如拿到写锁?大量的读就被阻塞,不希望看到这样的情况
怎么解决这个问题呢? CopyOnWrite
写的时候,拷贝一份数据出来,修改完后才切换引用,读取新的对象
优点:写时,仍然可以读,并且读到的不是脏数据
问题:数据存在一定的延时,导致内存翻倍,很容易OOM
每次写,都复制,浪费资源吗?
会,以空间换时间。
大量的读,非常少量的写
写的时候要加锁,同一时刻只允许一个写进行
内部原理:
Arrays.copyOf(elements, len + 1);
3、set
Set 实际上是数学概念,集合: 无序的、不重复
有序可以看做无序的特殊情况
HashSet数据到底有没有顺序
实现的时候,没有刻意打乱顺讯,也没有可以保证顺序,顺序没有保障
HashSet 使用 HashMap实现的,怎么实现的呢?
利用HasmMap 的key不重复特性来实现的源代码如下:
private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public Iterator<Map.Entry<K,V>> iterator() {
return getIterator(ENTRIES);
}
public boolean add(Map.Entry<K,V> o) {
return super.add(o);
}
CopyOnWriteArraySet Set不能重复
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);//indexOf返回大于0值就false否则就新增
private static int indexOf(Object o, Object[] elements,
int index, int fence) {
if (o == null) {
for (int i = index; i < fence; i++)
if (elements[i] == null)
return i;
} else {
for (int i = index; i < fence; i++)
if (o.equals(elements[i])) //判断其数组有相同的元素返回该坐标
return i;
}
return -1;
}
CopyOnWriteArrayList list可以重复
起源吗解析同理在这里就不解析了。
al.addIfAbsent(e); //利用这个方法来解决重复问题
ConcurrentSkipListSet Set,数据的顺序,是真的得到了保障
ConcurrentSkipListMap实现的
4、队列queue
Queue (放满元素:添加元素阻塞方法线程就会阻塞,一直等到有空余的位置,把元素放进去,才会结束阻塞 非阻塞的方法,线程直接忽略,如果控制不好,数据就丢了
取元素也会阻塞:取元素时,队列里面无元素时,会阻塞非阻塞的方法,直接返回null )
阻塞方法
取queue的元素有两种:
1、读度列头部,如element、peek 2、读队列头部,并删除它,如remove、take、poll
add 满的时候 IllegalSlabExwceprion
remove 移除并返回队列头部的元素 如果队列为空则抛出一个异常NoSuchElmentException
element如果队列为空返回队列头部元素NoSuchElmentException异常
add、remove、element 脾气暴躁,动不动就抛异常
offer\poll\peek 是非阻塞方法。元素放不进去时,直接忽略;取不出来时,返回null
put\take 是阻塞方法。元素放不进去,或没元素可取时,线程挂起,知道放进去,或取到元素
ArrayBlockingQueue 从名称可判断:数据结构为Array、是阻塞的
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length) //putIndex 到头,putIndex=0
putIndex = 0;
count++;
notEmpty.signal();//唤醒等待中的元素
}
put
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();//挂起 就是阻塞
enqueue(e);
} finally {
lock.unlock();
}
}
如何用数组实现一个queue :循环数组来实现 一个queue, 讲Conditon时讲过
什么是阻塞,condition 把线程挂起
问题:ArrayBlockingQueue 是否线程安全? 线程安全,都加锁了
LinkedBlocingQueue 阻塞 链表 线程安全的
自己回去看LInkedBlockingQuue的代码
ConcurrentLinkedQueue 在并发场景下使用, 我也是线程安全的
CAS 无锁编程
不blocking, 没有阻塞方法 take、put
CAS不能够保障性能绝对的好,可以保障我一个程序把资源占有的更多,单个程序性能一定更好
SychronousQueue 同步队里 很神奇
容量为0
put 没有容量,阻塞, 当有poll、take取元素时,结束阻塞
offer 元素直接丢了
如果有take 在阻塞中,offer也会成功
take 队列中无元素 =》 阻塞,当有put 、offer方法结束阻塞
poll 直接返回null
如果有一个put方法阻塞着,poll不返回null
peek只可能返回null,没有其他情况
PriorityBlockingQueue 通过传入comparator指定优先级规则
PriorityBlockingQueue<Student> queue =
new PriorityBlockingQueue<>(5, new Comparator<Student>() {
@Override
public int compare(Student stu1, Student stu2) {
if (stu1.age > stu2.age)
return -1;
else if (stu1.age == stu2.age)
return 0;
else
return 1;
}
});
5、Semaphore
是一个技术信号量,常用于限制可以访问某些资源(物理或逻辑的)线程数目。 只能访问几个通道
简单说,是一种用来控制并发量的共享锁
本质是一个共享锁,用于限流
并发控制工具类与forkjoin
用法
本质是一个共享锁,用于限流
//创建信号量
// static JamesSemaphore sp = new JamesSemaphore(5);
static Semaphore sp = new Semaphore(5);
public static void main(String args[]){
//1000个线程都想去访问DB
for (int i=0; i<1000; i++){
new Thread(){
@Override
public void run() {
try {
sp.acquire(); //抢到访问通道的,才能够访问DB
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
//queryDB("localhost:3006");
sp.release(); //访问完DB后,要释放通道
}
}.start();
}
}
模仿其源码机制实现Semaphore
//实现一个信号量
//共享锁,有上限
//AQS 技能提供共享锁 又能提供
//acquire release try…
//tryAcqirShared tryReleaseShared…
//不同点在于;有上限的共享锁
//当获取锁的个数,达到n个就不让在获取了,如果没有达到,据可以获取
//如果取锁的个数达到n个就不让在获取了,如果没有达到就可以获取
//如果已经到,就排队AQS
//只能够将值减1,你就是放成功了
//不管阻塞否,都要事先try方法
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class JamesSemaphore {
private Sync sync;
public JamesSemaphore(int permits){
this.sync = new Sync(permits);
}
public void acquire(){
sync.tryAcquireShared(1);
}
public void release(){
sync.tryReleaseShared(1);
}
class Sync extends AbstractQueuedSynchronizer {
private int permits;
public Sync(int permits){
this.permits = permits;
}
@Override
protected int tryAcquireShared(int arg) {
int c = getState();
int nextc = c + arg;
if (nextc <= permits){
if (compareAndSetState(c, nextc))
return 1;
}
return -1;
}
@Override
protected boolean tryReleaseShared(int arg) {
for(;;){
int c = getState();
if (c == 0) return false;
int nextc = c - 1;
if (compareAndSetState(c , nextc)){
return true;
}
}
}
}
}
CountDownLatch
这个类使一个线程等待其他线程各自执行完毕后再执行。
是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。或所有的线程在等待当一个线程在计数器值减1后执行程序。
创建 Test_CountDownLatch
用法
使用场景1:
火箭起飞前,有很多检查需要做,每项检查需要的时间不同,
完成全部10项检查后,火箭才能点火
CountDownLatch ctl = new CountDownLatch(10);
// 10个线程,分别做10 件事
for (int i=0; i<10; i++){
int number = i;
new Thread(){
@Override
public void run() {
//模拟做任务所需要的时间
int randomInt = new Random().nextInt(10);
try {
Thread.sleep(randomInt * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(number+ ">>>>" + "准备完毕");
// 对外通知,我已经准备完毕
ctl.countDown();
}
}.start();
}
//所有线程都昨晚了,才能点火
System.out.println("主线程开始等待。。。");
ctl.await();
System.out.println("点火...");
用法2
运动员在起跑线准备,等待口令,
裁判员:预备,跑!!!
//裁判员
JamesCountDownLatch ctl = new JamesCountDownLatch(1);
//6个运动员,即6个线程
for (int i=0; i<6; i++){
int number = i;
new Thread(){
@Override
public void run() {
System.out.println(number + " is redy...");
ctl.await(); //进入起跑线
System.out.println(String.format("运动员%d起跑", number));
}
}.start();
}
System.out.println("预备");
Thread.sleep(3000);
ctl.countDown(); //鸣枪
System.out.println("跑!!!");
源码模仿实现:
public class JamesCountDownLatch {
private Sync sync;
public JamesCountDownLatch(int count){
sync = new Sync(count);
}
public void countDown(){
sync.releaseShared(1);
}
public void await(){
sync.acquireShared(1);
}
/*
CountDownLatch就可以看做是一个共享锁
初始状态,这个共享锁被获取了n次,
每次countdown,相当于释放一次锁
当锁释放完后,其他线程才能再次获得锁
*/
class Sync extends AbstractQueuedSynchronizer{
public Sync(int count){
setState(count);
}
/*
在state=0时,获取锁成功,否则获取失败
*/
@Override
protected int tryAcquireShared(int arg) {
return getState()==0 ? 1 : -1;
}
@Override
protected boolean tryReleaseShared(int arg) {
for (;;){
int c = getState();
if (c==0)
return false;
int nextc = c-1;
//用CAS操作尝试将state减一
if (compareAndSetState(c, nextc)){
//当state变为0时,释放锁成功
return nextc == 0;
}
}
}
}
}
CyclicBarrier
从字面上的意思可以知道,这个类的中文意思是“循环栅栏”。大概的意思就是一个可循环利用的屏障。
它的作用就是会让所有线程都等待完成后才会继续下一步行动。
用法
//激流探险 验票员
JamesCyclicBarrier barrier = new JamesCyclicBarrier(4);
//100个人在排队,用100个线程代表10个人
for (int i=0; i< 100; i++){
new Thread(){
@Override
public void run() {
//来了一个人后,不能马上上车,要等到4个人满了以后,一起出发
barrier.await();
System.out.println("出发。。。");
}
}.start();
LockSupport.parkNanos(1000 * 1000 * 1000L);
}
源码
public class JamesCyclicBarrier {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private int count = 0;
private final int parties;
private Object generation = new Object();
public JamesCyclicBarrier(int parties) {
if (parties <= 0)
throw new IllegalArgumentException();
this.parties = parties;
}
public void await() {
final ReentrantLock lock = this.lock;
lock.lock(); // 一个线程拿到锁
try {
final Object g = generation;
int index = ++count;
if (index == parties) { // tripped 数量够了
nextGeneration(); // 唤醒等待的线程继续执行。重新计数
return;
}
//计数未到,进入等待
//for循环,防止伪唤醒
for (;;) {
try {
condition.await();
} catch (InterruptedException ie) {
//忽略
}
if (g != generation) //generation不同,标记该线程不需要再等待
return;
}
} finally {
lock.unlock();
}
}
private void nextGeneration() {
condition.signalAll();
count = 0;
generation = new Object();
}
}
*CountDownLatch和CyclicBarrier区别:
1.countDownLatch是一个计数器,线程完成一个记录一个,计数器递减,只能只用一次
2.CyclicBarrier的计数器更像一个阀门,需要所有线程都到达,然后继续执行,计数器递增,提供reset功能,可以多次使用
2个线程的局部遍历无法共享只能时堆中的。
获取线程返回值
Runnable获取返回值
Runnable没有返回值,怎么获取返回值呢?
Thread th = new Thread(new Runnable() {
@Override
public void run() {
}
});
th.start();
传入堆中的一个对象就行了
public class Result {
private volatile Object result;
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
}
public static void main(String args[]) throws InterruptedException {
Result result = new Result();
Thread th = new Thread(new Runnable() {
@Override
public void run() {
result.setResult("Hi James。。。。");
}
});
th.start();
Thread.sleep(3000);
System.out.println(result.getResult());
}
Callable介绍
//创建Callable实现类
class CallableTask implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println(">>>执行任务。。。");
//模拟耗时
LockSupport.parkNanos(1000 * 1000 *1000 * 5L);
return "success";
}
}
public static void main(String args[]) throws InterruptedException, ExecutionException {
//使用:用来包裹一个callab实例,得到的futureTask实例可以传入Thread()
CallableTask task = new CallableTask();
JamesFutureTask<String> future = new JamesFutureTask<>(task);
new Thread(future).start();
String result = future.get(); //get方法会阻塞
System.out.println(result);
//一个futureTask实例,只能使用一次
//同时说明,这个任务,从头到尾只会被一个线程执行
new Thread(future).start();
}
Callable和Runnable明显能看到区别:
1.Callable能接受一个泛型,然后在call方法中返回一个这个类型的值。而Runnable的run方法没有返回值
2.Callable的call方法可以抛出异常,而Runnable的run方法不会抛出异常。
FutureTask
一个可取消的异步计算。FutureTask提供了对Future的基本实现,可以调用方法去开始和取消一个计算,可以查询计算是否完成并且获取计算结果。只有当计算完成时才能获取到计算结果,一旦计算完成,计算将不能被重启或者被取消,除非调用runAndReset方法。一个futureTask实例,只能使用一次。
模仿源码实现
public class JamesFutureTask<T> implements Runnable{
public JamesFutureTask(Callable<T> call){
this.call = call;
}
private Callable<T> call;
T result;
//Runner,用来实现抢执行的权限
AtomicReference<Thread> runner = new AtomicReference<>();
//等待队列
LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();
//任务状态
private volatile int state = NEW;
private static final int NEW = 0;
private static final int RUNNING = 1;
private static final int FINISHED = 2;
@Override
public void run() {
if (state != NEW ||
!runner.compareAndSet(null, Thread.currentThread())){
return;
}
state = RUNNING;
try {
result = call.call();
} catch (Exception e) {
e.printStackTrace();
}finally {
state = FINISHED;
}
while (true){
Thread th = waiters.poll();
if (th == null){
break;
}
LockSupport.unpark(th);
}
}
public T get(){
if (state != FINISHED){
waiters.offer(Thread.currentThread());
}
//挂起线程
while (state!=FINISHED){
LockSupport.park();
}
return result;
}
}
ForkJoin
使用ForkJoin最核心的内容,就是定义 递归任务,定义递归任务,即定义如何对Task进行拆分,对结果进行汇总
定义就放在compute方法中,披着线程池外衣的任务拆分、结果汇总框架
public static void main(String args[]) throws ExecutionException, InterruptedException {
Job job = new Job(urls, 0, urls.size());
ForkJoinTask<String> forkJoinTask = forkJoinPool.submit(job);
String result = forkJoinTask.get();
System.out.println(result);
}
//使用ForkJoin最核心的内容,就是定义 递归任务,
//定义递归任务,即定义如何对Task进行拆分,对结果进行汇总
//定义就放在compute方法中
static class Job extends RecursiveTask<String>{
List<String> urls;
int start;
int end;
public Job(List<String> urls, int start, int end){
this.urls = urls;
this.start = start;
this.end = end;
}
@Override
protected String compute() {
int count = end - start; //计算任务大小
//若任务比较小,就直接执行, // 10
if (count <=10){
String result = "";
for (int i = start; i< end; i++){
String response = doRequest(urls.get(i));
result += response;
}
return result;
}else{
//否则,拆分任务
int x = (start + end) / 2;
Job job1 = new Job(urls, start, x);
job1.fork();
Job job2 = new Job(urls, x, end);
job2.fork();
//汇总结果
String result = "";
result += job1.join();
result +=job2.join();
return result;
}
}
}
注解:本章的内容的代码可参考:https://github.com/fanxishu/java2020peixun/tree/master/subject-1