天天看点

Java多线程 - JUC

JUC

CountDownLatch原理

CountDownLatch允许count个线程阻塞在一个地方,直至所有线程的任务都执行完毕。

CountDownLatch是共享锁的一种实现,它默认构造AQS的state为count。

当线程使用countDown方法时,其实使用了tryReleaseShared方法以CAS的操作来减少state,

直至state为0就代表所有的线程都调用了countDown方法。当调用await方法的时候,如果state不为0,

就代表仍然有线程没有调用countDown方法,那么就把已经调用过countDown的线程都放入阻塞队列Park,

并自旋CAS判断state == 0,

直至最后一个线程调用了countDown,使得state == 0,

于是阻塞的线程便判断成功,全部往下执行。

Semaphore

Semaphore允许一次性最多(不是同时)permits个线程执行任务。

Semaphore与CountDownLatch一样,也是共享锁的一种实现。

它默认构造AQS的state为permits。

当执行任务的线程数量超出permits,那么多余的线程将会被放入阻塞队列Park,并自旋判断state是否大于0,

只有当state大于0的时候,阻塞的线程才能继续执行,此时先前执行任务的线程继续执行release方法,

release方法使得state的变量会加1,那么自旋的线程便会判断成功。

如此,每次只有固定的线程能自旋成功,便限制了执行任务线程的数量。

所以这也是我为什么说它可能不是permits个线程同时执行,

因为只要state>0,线程就有机会执行.

CycliBarrier

CycliBarrier的功能与CountDownLatch相似,但是CountDownLatch的实现是基于AQS的,

而CycliBarrier是基于ReentrantLock(ReentrantLock也属于AQS同步器)和Condition的.

CountDownLatch虽然可以令线程阻塞,但是CountDownLatch只能await一次就不能使用了,

而CycliBarrier有Generation代的概念,一个代,就代表CycliBarrier的一个循环,

这也是CycliBarrier支持重复await的原因。

ReentrantReadWriteLock如何区分读写锁的?

Sync既有写锁,又有读锁,因此一个state不够用,

所以使用state的高16为表示读锁,低位16表示写锁.

ReentrantReadWriteLock部分源码:

 static final int SHARED_SHIFT   = 16;
 static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
 static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

 /** Returns the number of shared holds represented in count. */
 static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
 /** Returns the number of exclusive holds represented in count. */
 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

           

线程池的好处

http连接池,数据库连接池,线程池等都是利用了池化技术。

如果一个资源需要多次使用并且很昂贵,那么使用new创建的对象或资源,可能会带来较大的消耗。

池化技术的好处在于

  1. 方便资源的管理,无需显示的使用new创建。
  2. 降低了资源的消耗,在池子里的资源可以重复利用
  3. 提供了任务的响应速度,任务可以很快的被分配资源进行处理。

线程池构造参数

new ThreadPoolExecutor
(int corePoolSize,

 int maximumPoolSize, 

 long keepAliveTime,

 TimeUnit unit,

 BlockingQueue<Runnable> workQueue,

 ThreadFactory threadFactory,

 RejectedExecutionHandler handler)
           
corePoolSize
线程池的核心线程数(常驻线程数),也就是线程池的最小线程数,这部分线程不会被回收.
maximumPoolSize
线程池最大线程数,线程池中允许同时执行的最大线程数量
keepAliveTime

当线程池中的线程数量超过corePoolSize,但此时没有任务执行,

那么空闲的线程会保持keepAliveTime才会被回收,corePoolSize的线程不会被回收。

unit
keepAliveTime的时间单位
workQueue

当线程池中的线程达到了corePoolSize的线程数量,

并仍然有新任务,那么新任务就会被放入workQueue。

threadFactory
创建工作线程的工厂,也就是如何创建线程的,一般采用默认的
handler

拒绝策略,当线程池中的工作线程达到了最大数量,

并且阻塞队列也已经满了,那么拒绝策略会决定如何处理新的任务。

ThreadPoolExecutor 提供了四种策略:

1.AbortPolicy(是线程池的默认拒绝策略):

如果使用此拒绝策略,那么将对新的任务抛出RejectedExecutionException异常,来拒绝任务。

2.DiscardPolicy:

如果使用此策略,那么会拒绝执行新的任务,但不会抛出异常。

3.DiscardOldestPolicy:

如果使用此策略,那么不会拒绝新的任务,

但会抛弃阻塞队列中等待最久的那个线程。

4.CallerRunsPolicy:

如果使用此策略,不会拒绝新的任务,但会让调用者执行线程。

也就是说哪个线程发出的任务,哪个线程执行。

阿里巴巴开发者手册不建议开发者使用Executors创建线程池

newFixedThreadPool和newSIngleThreadPoolExecutor都是创建固定线程的线程池,

尽管它们的线程数是固定的,但是它们的阻塞队列的长度却是Integer.MAX_VALUE的,所以,

队列的任务很可能过多,导致OOM。

newCacheThreadPool和newScheduledThreadPool创建出来的线程池的线程数量却是Integer.MAX_VALUE的,

如果任务数量过多,也很可能发生OOM.

继续阅读