自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+

anlian523的博客

今天不学习,明天变垃圾,后天垃圾分类

  • 博客(268)
  • 资源 (1)
  • 收藏
  • 关注

原创 JUC集合类 PriorityBlockingQueue源码解析 JDK8

PriorityBlockingQueue是一个无界阻塞队列,它的出队方式不再是FIFO,而是优先级高的先出队。其内部实现是最小堆,即堆顶元素是逻辑上最小的那个元素,也是最先出队的那个元素。简单的说,如果a.compareTo(b) < 0的话,那么a将先出队。

2020-08-04 22:03:07 522

原创 从小顶堆到堆排序——超详细图解——Python3实现

在简单选择排序中,每次选择会从待排序元素中找到最小值,但每次选择都需要遍历完剩余所有元素,而且在遍历时没有记录起来有用信息,这显得很浪费。而堆排序则利用了最小堆(或最大堆)的性质,每次选择最小值都会利用堆的数据结构来保存有用信息,即总是使得整个堆是一个最小堆,以便下一次选择时直接选择索引为0的节点就可以了。注意,本文使用到一个打印完全二叉树的算法方便我们观察整个堆的样子。具体做法是,新建一个printHeap.py,把这个算法除了测试代码都放进去。(不要误会,我绝对不是因为嫌画图麻烦才来写这个算法的

2020-08-01 23:54:54 1868

原创 打印一颗基于数组的完全二叉树——Python3实现

最近在复习堆排序的内容,发现基于数组的堆虽然用起来很方便,但打印不方便。所以本文实现了一个简单美观的打印一颗基于数组的完全二叉树的算法(堆就是一种完全完全二叉树嘛,但实现最小堆一般是基于数组的)。

2020-08-01 17:37:39 667

原创 JUC集合类 LinkedBlockingDeque源码解析 JDK8

和ArrayBlockingQueue一样,使用一个Lock和两个Condition来控制并发和阻塞。因为两端都可以入队出队,所以用一个锁才能保证正确。和LinkedBlockingQueue不同的是,我们初始化时,连dummy node也没有。从first端移除的节点,next指针指向自身,以区别于first指向节点(next指针不会指向自身,可能为真实节点或null)。从last端移除的节点同理。

2020-07-30 23:55:45 717 3

原创 JUC集合类 LinkedBlockingQueue源码解析 JDK8

LinkedBlockingQueue是一种FIFO(first-in-first-out 先入先出)的有界阻塞队列,底层是单链表,也支持从内部删除元素。并发操作依赖于加锁的控制,支持阻塞式的入队出队操作。相比ArrayBlockingQueue的一个Lock,LinkedBlockingQueue使用了两个Lock,分别对应入队动作和出队动作,这便提高了并发量。

2020-07-29 23:58:02 926

原创 JDK8 ArrayBlockingQueue迭代器 源码解析

ArrayBlockingQueue的迭代器的整体设计很复杂,主要在于它需要去保证自己总是能保持 队首队尾索引,并以队首队尾索引为基础进行下一次的遍历。迭代器使用了一种“队首快照”的办法,用于发现 返回元素 相较与上一次快照时,实际上它已经被删除的情况。使得lastRet为REMOVE,从而让Itr#remove不会执行删除动作。还考虑内部删除节点的情景,这种情况需要 索引左移。

2020-07-26 19:59:14 609

原创 JUC集合类 ArrayBlockingQueue源码解析 JDK8

ArrayBlockingQueue是一种FIFO(first-in-first-out 先入先出)的有界阻塞队列,底层是数组,也支持从内部删除元素。并发操作依赖于加锁的控制,支持阻塞式的入队出队操作。正因为有界,所以才会阻塞。加锁实现完全依赖于AQS,需要读者比较熟悉AQS 独占锁的获取过程和AQS Condition接口的实现。对ArrayBlockingQueue的源码解析,更像是了解一次AQS的最佳实践。

2020-07-25 17:44:29 968 2

原创 JUC集合类 ConcurrentLinkedDeque源码解析 JDK8

ConcurrentLinkedDueue是一个无界的双端队列,它的并发操作是基于CAS的无锁实现,所以不会产生阻塞。要想理解ConcurrentLinkedDueue,先去理解了ConcurrentLinkedQueue才是快捷方式,因为它们二者使用的概念和函数实现的套路实际上都很类似。

2020-07-25 11:46:38 721

原创 JUC集合类 ConcurrentLinkedQueue源码解析 JDK8

ConcurrentLinkedQueue是一种FIFO(first-in-first-out 先入先出)的队列,底层是单链表,一般来说,队列只支持队尾入队、队头出队,但此类还支持从内部删除某个特定的节点。使用非阻塞算法来处理并发操作,这也意味着实现里充满了CAS和自旋。

2020-07-22 00:44:59 1151 5

原创 JDK8 ConcurrentHashMap的Bug集锦

JDK8的ConcurrentHashMap并不是完美的,从https://bugs.openjdk.java.net/projects/JDK/issues上也可以看到JDK的很多Bug,当然,通过给concurrency-interest发邮件也可以和Doug Lea直接对话。最重要的是,知道了这些bug的存在,可以避免我们去分析这些不正确的代码的“正确性”。

2020-07-19 11:02:50 1586 7

原创 JUC集合类 ConcurrentHashMap源码解析 JDK8

ConcurrentHashMap是一种支持并发操作的HashMap,并发控制方面,它使用更小粒度的锁——对每个哈希桶的头节点加锁。虽然这样使得效率更高,能让读写操作最大程序的并发执行,但也造成了读写操作的一致性很弱,比如size()返回的大小可能已经与真实大小不一样,比如clear()调用返回后Map中却拥有着元素。JDK8的ConcurrentHashMap并不是完美,在阅读源码之前,建议先看看ConcurrentHashMap有些什么bug,以免在这些地方钻牛角尖。

2020-07-19 10:58:27 2215 1

原创 ThreadLocalRandom#getProbe #advanceProbe浅析

简单的说,一个线程的probe探针值,是一个hash值,它不会和其他线程重复(一定情况下)。ThreadLocalRandom的probe机制在线程数比较少,且probe移动次数也比较小时,是可以保证探针不重复的。但是不重复的情况指的是在int的范围内不重复,在有限的桶位面前,由于只取probe值的后几位bit,那么很大概率也会重复。

2020-07-12 14:02:02 2468

原创 JUC集合类 ConcurrentSkipListMap源码解析 JDK8

ConcurrentSkipListMap的实现是完全的无锁编程lock free。为了保证并发删除和插入正常执行,加入了marker机制,以及删除过程的三个步骤。base层和index层的头节点都是dummy节点,如果没有dummy节点,实现将会变得复杂。每个节点插入后,将新建的index层是完全随机的。如果新建层数超过了现有最大层数,那么新建层数则为最大层数+1。SkipList跳表使用空间换取时间,以得到查找效率。正因为插入节点的新建index层数是完全随机的,所以也使得实现更简单,整个跳表从

2020-07-06 22:38:10 848 1

原创 JUC集合类 CopyOnWriteArraySet源码解析 JDK8

但CopyOnWriteArraySet的底层实现完全依赖了CopyOnWriteArrayList,它持有了一个CopyOnWriteArrayList类型的成员,很多方法的实现都是直接调用CopyOnWriteArrayList的同名方法。这意味着CopyOnWriteArraySet的底层实现不再是散列表的实现,而只是一个普通数组,只不过现在CopyOnWriteArraySet表现地像一个HashSet而已,不过这对于使用者来说已经足够了。

2020-06-27 17:46:58 653

原创 JUC集合类 CopyOnWriteArrayList源码解析 JDK8

volatile + return操作/赋值操作,保证了数组成员的可见性和原子性。读操作不用加锁,直接获取数组成员。写操作需要加锁,并拷贝原数组成员。优点:保留了读操作的高性能。避免了并发修改抛出的异常。缺点:写操作太多时,将产生高内存消耗。因为需要拷贝出新数组。读操作不能保证看到最新的数据,即使写操作已经开始执行了。因为直到写操作执行setArray之前,读操作都无法看到最新数据。场景:读操作多,写操作少的场景。读操作允许看到非最新数据的场景。

2020-06-27 15:19:44 934 2

原创 JUC框架 ReentrantReadWriteLock源码解析 JDK8

同步器的state被划分为两个部分,分别记录被拿走的读锁和写锁的总数。分别记录各个线程拿走的读锁的工作交给了各个线程自己,通过ThreadLocal实现。不仅写锁可以重入(这类似于ReentrantLock),读锁也可以重入。尝试获取写锁时,会因为其他写锁或任意读锁(包括自己)的存在,而进入阻塞等待的过程,抛入sync queue中去。尝试获取读锁时,会因为其他写锁(不包括自己的写锁)的存在,而进入阻塞等待的过程,抛入sync queue中去。读锁的非公平获取中,apparentlyFirstQu

2020-06-26 12:07:06 1275 3

原创 ReentrantReadWriteLock的readerShouldBlock与apparentlyFirstQueuedIsExclusive 深入理解读锁的非公平实现

readerShouldBlock的非公平实现,并不是完全的非公平实现(即直接返回false)。readerShouldBlock的不完全的非公平实现,是为了防止写锁无限等待。readerShouldBlock在一定概率下,防止了new reader的读锁获取动作,转而让new reader去sync queue中排队。

2020-06-26 09:48:35 1929 4

原创 JUC框架 Semaphore源码解析 JDK8

Semaphore的AQS子类是一个很标准的共享锁实现。获得信号量 == 减小AQS的state。释放信号量 == 增加AQS的state。共享锁的AQS子类实现方法需要自旋,这一点在Semaphore和CountDownLatch都有体现。独占锁的AQS子类实现方法不需要自旋。获得信号量失败一定是因为信号量已经减到0了,且获得失败就会阻塞。

2020-06-24 23:07:55 749

原创 JUC框架 CyclicBarrier源码解析 JDK8

CyclicBarrier类似于CountDownLatch但又不同,在CyclicBarrier里,每个线程既要CountDown减小计数器,也要阻塞等待直到count为0(即等待线程到齐)。CyclicBarrier基于独占锁和条件队列而实现。CyclicBarrier可以重复使用,每有parties个线程到达barrier,开启新的一代。然后旧的一代线程就会相继从CyclicBarrier#await退出。CyclicBarrier实现了all-or-none breakage model的原则

2020-06-21 18:14:18 931 1

原创 JUC框架 CountDownLatch源码解析 JDK8

CountDownLatch的使用场景:当某个量化为数字的条件被满足后,几个线程任务才可以同时开始执行。调用CountDownLatch#await的线程将等待条件被满足,条件满足后,调用CountDownLatch#await的若干线程将从同一个时间点继续执行。调用CountDownLatch#countDown,让量化为数字的条件减一。调用CountDownLatch#await的线程,和调用CountDownLatch#countDown的线程,是两类线程,并无关系。唤醒阻塞在await的若干

2020-06-20 11:23:06 1030

原创 interrupt()中断对LockSupport.park()的影响

park调用后一定会消耗掉permit,无论unpark操作先做还是后做。如果中断状态为true,那么park无法阻塞。unpark会使得permit为1,并唤醒处于阻塞的线程。interrupt()会使得中断状态为true,并调用unpark。sleep() / wait() / join()调用后一定会消耗掉中断状态,无论interrupt()操作先做还是后做。

2020-06-15 22:32:55 4637 17

原创 AQS深入理解系列(四)Condition接口的实现

一个新设计的出现,总是为了替换现有的略有不足的设计。而Condition接口的出现,是为了代替监视器锁的wait/notify机制,提供更强大的功能。先简单说明一下它们之间的相同之处,以便之后更好地理解Condition接口的实现:调用wait()的线程必须已经处于同步代码块中,换言之,调用wait()的线程已经获得了监视器锁;调用await()的线程则必须是已经获得了lock锁。执行wait()时,当前线程会释放已获得的监视器锁,进入到该监视器的等待队列中;执行await()时,当前线程会释放已获

2020-06-14 18:59:43 2098

原创 AQS深入理解系列(三)共享锁的获取与释放

独占锁是线程独占的,同一时刻只有一个线程能拥有独占锁,AQS里将这个线程放置到exclusiveOwnerThread成员上去。共享锁是线程共享的,同一时刻能有多个线程拥有共享锁,但AQS里并没有用来存储获得共享锁的多个线程的成员。如果一个线程刚获取了共享锁,那么在其之后等待的线程也很有可能能够获取到锁。但独占锁不会这样做,因为锁是独占的。当然,如果一个线程刚释放了锁,不管是独占锁还是共享锁,都需要唤醒在后面等待的线程。

2020-06-08 10:26:13 3038 9

原创 AQS深入理解系列(二) 独占锁的释放过程

首先会尝试设置状态从小于0变成0。一般可以这样认为,如果head的状态为0,代表head后继线程即将被唤醒,或者已经被唤醒。如果遇到s == null,说明我们遇到一种中间状态,next指针还没有指好。如果遇到s.waitStatus > 0,说明head后继刚取消了。这两种情况,都需要从队尾的prev往前找。关于prev的有效性,已经在上一篇博客讲过了。注意循环条件t != null && t != node,它会从队尾一直往前找,直到t是null或t已经到达了node。一般情况下,不会出现t !=

2020-06-04 23:44:02 1585 1

原创 AQS深入理解系列(一) 独占锁的获取过程

对于独占锁,AQS的state代表代表锁的状态,为0代表没有线程持有锁,非0代表有线程持有了锁。获得了锁的线程会将自己设置为exclusiveOwnerThread。addWaiter负责new出一个包装当前线程的node,enq负责将node添加到队尾,如果队尾为空,它还负责添加dummy node。acquireQueued是整个获取锁过程的核心,这里是指它的那个死循环。一般情况下,每次循环做的事就是:尝试获取锁,获取锁失败,阻塞,被唤醒。如果某一次循环获取锁成功,那么之后会返回到用户代码调用处。

2020-06-03 00:03:19 4132 7

原创 AQS深入理解 shouldParkAfterFailedAcquire源码分析 状态为0或PROPAGATE的情况分析

检测到0后,一定要设置成SIGNAL的原因:设置前驱状态为SIGNAL,以便当前线程阻塞后,前驱能根据SIGNAL状态来唤醒自己。(具体看章节释放锁后,唤醒head后继的条件)设置成SIGNAL后会返回false的原因:返回false后,下一次循环开始,会重新获取node的前驱,前驱如果就是head,那么还会重新尝试获取锁。这次重新尝试是有必要的,某些场景下,重新尝试获取锁会成功。

2020-05-31 16:28:37 8552 10

原创 AQS深入理解 doReleaseShared源码分析 JDK8

doReleaseShared会尝试唤醒 head后继的代表线程,如果线程已经唤醒,则仅仅设置PROPAGATE状态。上一条的“尝试唤醒 head后继的代表线程”和“设置PROPAGATE状态”都是CAS操作,如果CAS失败,循环会马上continue并再次尝试。当检测到局部变量h与当前最新head是不同对象时,说明有acquire thread刚获取了锁,那下一个等待线程也很可能可以获取锁,所以不会break,循环继续。

2020-05-24 22:29:42 6106 12

原创 AQS深入理解 setHeadAndPropagate源码分析 JDK8

如果propagate > 0不成立,而h.waitStatus < 0成立。这说明旧head的status<0。但如果你看doReleaseShared的逻辑,会发现在unparkSuccessor之前就会CAS设置head的status为0的,在unparkSuccessor也会进行一次CAS尝试,因为head的status为0代表一种中间状态(head的后继代表的线程已经唤醒,但它还没有做完工作),或者代表head是tail。而这里旧head的status<0,只能是由于doReleaseShared

2020-05-24 19:12:14 5834 24

原创 Java并发 volatile可见性的验证

加了同步块,子线程会退出。这是因为synchronized具体过程是:获得同步锁;清空工作内存;从主内存拷贝对象副本到工作内存;执行代码(计算或者输出等);刷新主内存数据;释放同步锁。简单的说,synchronized进入时,会将 主内存中最新的变量,拷贝进 自己线程 的工作内存。synchronized退出时,会把 自己线程的工作内存的变量 弄进 主内存中。

2020-05-17 21:35:42 1105 1

原创 AQS深入理解 hasQueuedPredecessors源码分析 JDK8

分析hasQueuedPredecessors的返回判断。首先要知道,hasQueuedPredecessors返回true代表有别的线程在CHL队列中排了当前线程之前;返回false代表当前线程处于CHL队列的第一个线程。分析h != t返回false的情况。此时hasQueuedPredecessors返回false。当h和t都为null,返回false。此时说明队列为空,还从来没有Node入过队。当h和t都指向同一个Node,也返回false。此时说明队列中只有一个dummy node,那说

2020-05-17 18:13:35 4540 15

原创 JUC AtomicIntegerArray源码解析 JDK8

相比其他原子类,原子数组类AtomicIntegerArray,它的成员不再是volatile的,而是final的。从设计上来讲,作为原子数组类的数组成员初始化后,确实也不应该被修改引用了。计算数组元素的地址偏移时,需要两个值,base和元素索引。get/set函数不能像其他原子类一样,get只有return成员,set直接赋值成员。只能借助unsafe的getIntVolatile / putIntVolatile。

2020-05-10 21:21:33 598

原创 JUC AtomicStampedReference源码解析 JDK8

最终调用Unsafe的compareAndSwapObject方法时,是不关心版本号的。compareAndSwapObject只关心是不是同一个对象。(但这样不会造成问题)虽然根据上一条,感觉可能会有问题。但是由于静态内部类Pair每次都会新构造对象出来,即使T reference, int stamp两个参数完全一样,也会构造出两个不同的对象,所以最终调用的compareAndSwapObject不关心版本号也没有关系。综上,版本号是在Unsafe的CAS操作上进行的附加判断,准备的说,是先判断版本

2020-05-10 18:08:51 615 1

原创 JUC AtomicInteger源码解析 JDK8

AtomicInteger类通过volatile语义加上CAS操作,使得对AtomicInteger的操作实现了一种非阻塞同步,从而保证了线程安全。非阻塞在于它没有使用synchronized或者Lock,而是循环加CAS,既然是循环执行,那么肯定没有阻塞线程,也就没有切换线程所带来的消耗。

2020-05-10 15:52:05 1305

原创 Java编程思想 并发 新类库中的构件demo浅析

每个Horse线程都会阻塞在await处,但每阻塞一次 CyclicBarrier对象的计数器都会减少1,当7个Horse线程都执行了await,这个计数器便减小到了0。然后会执行CyclicBarrier构造器中的匿名线程,当匿名线程执行完毕后,7个Horse线程才会重新从await处唤醒。同样,阻塞的顺序和唤醒的顺序不一样。执行下面代码以观察效果:

2020-05-08 23:13:50 328

原创 听说你看过ThreadLocal源码,来面试下这几个问题

ThreadLocal的用途ThreadLocal用来给各个线程提供线程隔离的局部变量。使用很简单,通过调用同一个ThreadLocal对象的get/set方法来读写这个ThreadLocal对象对应的value,但是线程A set值后,不会影响到线程B之后get到的值。ThreadLocal对象通常是static的,因为它在map里作为key使用,所以在各个线程中需要复用。简单说下Th...

2020-04-19 22:54:00 1178

原创 JUC ThreadLocal源码行级解析 JDK8

ThreadLocal看类名就是线程本地变量的意思。从使用上来说,如果定义了一个ThreadLocal,那么各个线程针对这个ThreadLocal进行get/set都是线程独立的,也就是说,是线程隔离的本地变量。从实现上来说,每个线程在运行过程中都可以通过Thread.currentThread()获得与之对应的Thread对象,而每个Thread对象都有一个ThreadLocalMap类型的成员,ThreadLocalMap是一种hashmap,它以ThreadLocal作为key。所以,通过Thre

2020-04-19 20:00:40 1573 3

原创 Java 生产者消费者案例——等待唤醒机制、虚假唤醒

wait() \ notify() \ notifyAll()这三方法必须在synchronized方法或synchronized同步代码块中执行,因为这样就说明currentThread已经获得了对象的monitor。wait()使得当前线程释放已获得的对象monitor,并陷入一种等待。这种等待必须依靠别的获得同一个对象monitor的线程来调用notify() \ notifyAll()才会重新唤醒,但重新唤醒后需要继续执行没执行完的同步代码,而执行同步代码的前提是获得被调用成员方法的对象的moni

2020-04-11 19:53:39 793

原创 【LeetCode.53】 最大子序和——以及变种 返回开始结束索引

不用新建一个dp数组了,直接在nums数组上覆盖就好,因为无后效性,当前元素遍历后,就可以覆盖为dp[i]的值。dp[0]的值,初始时,就是确定的,因为只有一个元素的数组,它的唯一的子序列就是它自己。从递推公式dp[i+1] = max(nums[i+1], dp[i]+nums[i+1])可以看出,其实关键在于上一次递推结果是否大于0,如果大于0,那么当前递推结果就得加上 上一次递推结果。maxNum在遍历过程,保存最大的那个递归结果。

2020-04-09 22:58:28 1969

原创 你真的会写二分查找吗——分析二分查找变种代码

我们已经知道循环条件是left <= right,所以循环结束条件是left > right,且肯定是right = left -1。而且在key存在的情况下,left 和 right 肯定有一个落在边缘key上(边缘key是指,多个key时的最左key或最右key);如果key不存在,那么left 和 right 肯定right在 刚好小于key的元素上,left在 刚好大于key的元素上。

2020-04-05 00:16:31 400

原创 【LeetCode.167】 两数之和 II - 输入有序数组

双端指针如果是用来遍历单个元素时,肯定没有问题的,最终每个元素都能遍历到。但此题实际是,要求我们去遍历元素的组合,即两个元素的组合,那么双端指针在遍历过程中,肯定不能遍历到所有组合情况,或者说,在遍历过程中,它会选择方向,适当排除掉某些情况。

2020-04-04 13:07:49 305

ArcGIS 10.1 破解文件.rar

ArcGIS 10.1 破解文件。在你安装完ArcGIS 10.1 for Desktop.iso后使用的。

2019-05-25

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除