说说常见的并发组件。
基础中的基础---Lock。
Lock是一个接口,最常见的实现就是ReentrantLock。顾名思义是一个可重入锁。他的功能和synchroized比较类似,都是锁定资源的。如果需要锁定资源的话,老实说synchroized跟ReentrantLock并没有啥区别。除非你需要用到读写锁。
那先说说ReentrantLock底层机制吧。他里面有个Sync组件,然后有俩实现类,公平的和非公平的。从名字也能看出区别,至于非公平是啥意思,可以举个栗子,如果现在有一个线程在执行,三个线程在阻塞着,现在有一个线程又来了,你上来就要去抢锁,失败了肯定就排队,万一成功了呢?对其他几个老早就排着队的公平吗~ 或者咱们直接看图,更为直接。公平锁只不过多个一个判断,如果队列没人才会抢占锁。
然后点进去我们就会发现一个重要的组件AbstractQueuedSynchronizer,简称aps,这个是啥?我们从现实出发,你现在定义了一个锁的概念,ok,你直接说,好,现在我规定,凡是超过一个线程来抢占这个资源的,都要被锁住。嗯,跟synchroized差不多嘛。你觉得设计的很nice。这个时候突然来了一个人,他说大佬,我这有个需求,最多只能有两个线程并发。问你那玩意能不能复用啊?你尴尬了,你发现大家好像除了对锁的定义不一样,其他比如锁住了其他线程就没法执行的,释放了就要唤醒等待的这些步骤好像都一样啊。你痛定思痛,熬夜把这些通用的锁资源,释放资源等通用的逻辑抽出来了,搞了一个抽象类,名字叫AbstractQueuedSynchronizer。姑且就叫aps吧。你把你之前写的那个锁叫ReentrantLock。如果有其他类型的锁,比如有人认为同时有n个线程并发,超过的就要锁住,他核心用的你的aqs,但是关于什么叫锁的设计,他实现了自己的想法,他起了一个类名,叫semaphore。这个时候又有一个人来了,他说你们俩搞得什么玩意,都要求并发n个,如果你的n变成1,不就跟ReentrantLock一样吗。你俩尴尬一笑,嘿嘿嘿,确实确实。你反问,那你有啥花哨的锁设计?他说,我现在不要求最多有几个并发,我只要求我这n个线程都执行完了才能解锁,否则一直都是锁状态!我起了个名字叫countdownlunch。你俩听完,嘲讽了起来:你这个也太敷衍了,我们默认是可执行状态,你默认是不可执行。。。你初始化参数设为n,解锁的时候看参数为0才行,每个线程执行完了就减1,啥时候任务都执行完了就变成了0了,你这个也太简单了。而且万一你这有一个线程出点毛病没-1,再也不可能为0了,不得卡死?他说,简单是简单,挺有用啊,你看很多人想去用多线程做事,最后要汇总的时候,不就正好需要主线程在那等着,啥时候所有线程执行完了再往下走吗?要不我再升级一下?现在这个countlunch是一次性的,我再设计一个可以循环的?看到红绿灯了吗,攒够一拨人才走,我这么设计的,我也不用aps了,直接用现成的reentrantLock,设置个初始化的n,一个线程来-1的时候,不到0直接await直接阻塞。大家互相等待,啥时候人都齐了,就到了触发点,可以执行一个事,而且还能循环使用~
再来说下比较常用的线程池用法