《实战Java高并发程序设计》读书笔记三
第三章 JDK并发包
1、同步控制
重入锁:重入锁使用java.util.concurrent.locks.ReentrantLock类来实现,这种锁可以反复使用所以叫重入锁。
重入锁和synchronized的比较:
- Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现。
- Lock有着显示的操作过程,由开发自己决定加锁和释放锁的时间,更为灵活。synchronized的获取和释放锁由JVM实现。
- Lock线程在获取锁的时候可以响应中断,synchronized不可以。
- Lock可以设置是否为公平锁默认是不公平,synchronized是非公平锁。
- synchronized当线程尝试获取锁的时候,如果获取不到锁会一直阻塞。重入锁可以设置等待时间。
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁。
重入锁重要方法:
- lock()获得锁如果所被占用则等待。
package com.ecut.lock;import java.util.concurrent.locks.ReentrantLock; public class LockTest { private static ReentrantLock lock = new ReentrantLock(); private static class ReentrantLockThread implements Runnable { @Override public void run() { lock.lock(); for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "得到了锁 i = " + i); } lock.unlock(); } } public static void main(String[] args) { ReentrantLockThread runnable = new ReentrantLockThread(); Thread t1 = new Thread(runnable, "t1"); Thread t2 = new Thread(runnable, "t2"); Thread t3 = new Thread(runnable, "t3"); Thread t4 = new Thread(runnable, "t4"); t1.start(); t2.start(); t3.start(); t4.start(); } }
运行结果如下:
t1得到了锁 i = 0 ....... t1得到了锁 i = 4 t3得到了锁 i = 0 ....... t3得到了锁 i = 4 t4得到了锁 i = 0 ....... t4得到了锁 i = 4 t2得到了锁 i = 0 ....... t2得到了锁 i = 4
- lockInterruptibly()获得锁优先响应中断。
package com.ecut.lock;import java.util.concurrent.locks.ReentrantLock; public class LockInterruptiblyTest { private static ReentrantLock lock = new ReentrantLock(); private static class ReentrantLockThread implements Runnable { @Override public void run() { try { lock.lockInterruptibly(); for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "得到了锁 i = " + i); } } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + "被打断了"); } finally { //查询当前线程是否保持此锁。 if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } } public static void main(String[] args) { ReentrantLockThread runnable = new ReentrantLockThread(); Thread t1 = new Thread(runnable, "t1"); Thread t2 = new Thread(runnable, "t2"); Thread t3 = new Thread(runnable, "t3"); t1.start(); t2.start(); t3.start(); t2.interrupt(); } }
运行结果如下:
t1得到了锁 i = 0 t1得到了锁 i = 1 t1得到了锁 i = 2 t1得到了锁 i = 3 t1得到了锁 i = 4 t2被打断了 t3得到了锁 i = 0 t3得到了锁 i = 1 t3得到了锁 i = 2 t3得到了锁 i = 3 t3得到了锁 i = 4
- tryLock()尝试获得锁如果成功返回true,失败返回false
- tryLock(long time , TimeUnit unit)给定时间内尝试获得锁
package com.ecut.lock;import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; public class TryLockTest { private static ReentrantLock lock = new ReentrantLock(); private static class ReentrantLockThread implements Runnable { @Override public void run() { try { if (lock.tryLock(1, TimeUnit.MICROSECONDS)) { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "得到了锁 i = " + i); } lock.unlock(); } else { System.out.println(Thread.currentThread().getName() + "尝试获取锁失败"); } } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { ReentrantLockThread runnable = new ReentrantLockThread(); Thread t1 = new Thread(runnable, "t1"); Thread t2 = new Thread(runnable, "t2"); Thread t3 = new Thread(runnable, "t3"); t1.start(); t2.start(); t3.start(); } }
运行结果如下:
t2得到了锁 i = 0 t2得到了锁 i = 1 t2得到了锁 i = 2 t2得到了锁 i = 3 t2得到了锁 i = 4 t3尝试获取锁失败 t1尝试获取锁失败
- unlock()释放锁
condition条件
- 需要通过lock接口生成一个与当前重入锁绑定的condition实例,lock.new Condition()。
- await()方法会使当前线程等待,同时释放当前锁
- singnal()方法用于唤醒一个在等待中的线程
- 两个方法调用时都要求线程获得相关的锁
package com.ecut.condition; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class ConditionTest { private static ReentrantLock lock = new ReentrantLock(); private static Condition condition = lock.newCondition(); public static class ConditionThread implements Runnable{ @Override public void run() { try { lock.lock(); condition.await(); System.out.println("going on"); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } } public static void main(String[] args) throws InterruptedException { ConditionThread conditionThread = new ConditionThread(); Thread thread = new Thread(conditionThread); thread.start(); Thread.sleep(5000); lock.lock(); condition.signal(); lock.unlock(); } }
信号量Semaphore
- 指定同时可以有多少个线程访问某一个资源。
- Semaphore可以传入一个int型整数n,表示某段代码最多只有n个线程可以访问,另一个参数表示是否公平。
- acquire()用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。
- release()用来释放许可。注意,在释放许可之前,必须先获获得许可。
package com.ecut.semaphore; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; public class SemaphoreTest { private static Semaphore semaphore = new Semaphore(3); private static class SemphoreThread implements Runnable { @Override public void run() { try { semaphore.acquire(); System.out.println(Thread.currentThread().getName() + "获得一个许可"); Thread.sleep(1); System.out.println(Thread.currentThread().getName() + "完成,开始释放许可"); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { SemphoreThread semphoreThread = new SemphoreThread(); ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 12; i++) { executorService.submit(semphoreThread); } } }
运行结果如下:
pool-1-thread-1获得一个许可 pool-1-thread-2获得一个许可 pool-1-thread-4获得一个许可 pool-1-thread-4完成,开始释放许可 pool-1-thread-1完成,开始释放许可 pool-1-thread-4获得一个许可 pool-1-thread-1获得一个许可 pool-1-thread-2完成,开始释放许可 pool-1-thread-5获得一个许可 pool-1-thread-5完成,开始释放许可 pool-1-thread-6获得一个许可 pool-1-thread-1完成,开始释放许可 pool-1-thread-10获得一个许可 pool-1-thread-4完成,开始释放许可 pool-1-thread-8获得一个许可 pool-1-thread-6完成,开始释放许可 pool-1-thread-10完成,开始释放许可 pool-1-thread-9获得一个许可 pool-1-thread-3获得一个许可 pool-1-thread-3完成,开始释放许可 pool-1-thread-9完成,开始释放许可 pool-1-thread-7获得一个许可 pool-1-thread-8完成,开始释放许可 pool-1-thread-7完成,开始释放许可
ReadWriteLock读写锁
- 在读操作次数远大于写操作的时候使用,读写分离锁有利于减少锁竞争,读写互斥、写写也互斥。
- 获得读锁:reentrantReadWriteLock.readLock(),获得写锁:reentrantReadWriteLock.writeLock()。
package com.ecut.readwritelock; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReentrantReadWriteLockTest { private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private static Lock readLock = lock.readLock(); private static Lock writeLock = lock.writeLock(); private static class ReadThread implements Runnable { @Override public void run() { System.out.println("开始读操作"); try { System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName()); readLock.lock(); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } finally { readLock.unlock(); } } } private static class WriteThread implements Runnable { @Override public void run() { System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName()); writeLock.lock(); System.out.println("开始写操作"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { readLock.unlock(); } } } public static void main(String[] args) { ReadThread readThread = new ReadThread(); WriteThread writeThread = new WriteThread(); ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 20; i++) { executorService.submit(readThread); executorService.submit(writeThread); } for (int i = 0; i < 20; i++) { executorService.submit(readThread); } for (int i = 0; i < 20; i++) { executorService.submit(writeThread); } } }
倒计时器CountDownLatch
- CountDownLatch可以让某个线程一直等待直到倒计时结束再开始执行。作用类似于join,join用于让当前执行线程等待join线程执行结束。其实现原理是不停检查join线程是否存活,如果join线程存活则让当前线程永远wait。
- countDownLatch.countDown()可以使计数器减一,countDownLatch.await()会让当前线程一直阻塞直到计数器为0。
package com.ecut.countdownlatch; import java.util.concurrent.CountDownLatch; public class CountDownLatchTest { private static CountDownLatch countDownLatch = new CountDownLatch(5); private static class CountDownLatchThread implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + "开始报道"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "报道成功"); countDownLatch.countDown(); } } public static void main(String[] args) { CountDownLatchThread countDownLatchThread = new CountDownLatchThread(); Thread thread1 = new Thread(countDownLatchThread, "学生1"); Thread thread2 = new Thread(countDownLatchThread, "学生2"); Thread thread3 = new Thread(countDownLatchThread, "学生3"); Thread thread4 = new Thread(countDownLatchThread, "学生4"); Thread thread5 = new Thread(countDownLatchThread, "学生5"); thread1.start(); thread2.start(); thread3.start(); thread4.start(); thread5.start(); try { System.out.println("等待学生报道"); countDownLatch.await(); System.out.println("所有学生报道成功"); } catch (InterruptedException e) { e.printStackTrace(); } } }
运行结果如下:
学生2开始报道 学生5开始报道 等待学生报道 学生3开始报道 学生1开始报道 学生4开始报道 学生1报道成功 学生5报道成功 学生3报道成功 学生2报道成功 学生4报道成功 所有学生报道成功
循环栅栏CyclicBarrier
- CyclicBarrier可以反复使用的计数器,构造函数中第一个参数int parties 指定计数总数,第二个参数Runnable barrierAction 指定了每次计数结束后要执行的动作。
- CyclicBarrier的await方法会等待计数完成,可能抛出两个异常InterruptedException和BrokenBarrierException。
package com.ecut.cyclicbarrier; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierTest { private static boolean flag; private static class PrepareThread implements Runnable { private CyclicBarrier cyclicBarrier; PrepareThread(CyclicBarrier cyclicBarrier) { this.cyclicBarrier = cyclicBarrier; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "准备好了"); try { cyclicBarrier.await(); running(); cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } } private static void running() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "到达终点"); } private static class BarrierAction implements Runnable { @Override public void run() { if (flag) { System.out.println("比赛结束!"); } else { System.out.println("开始比赛!"); flag = true; } } } public static void main(String[] args) { BarrierAction barrierAction = new BarrierAction(); CyclicBarrier cyclicBarrier = new CyclicBarrier(5, barrierAction); PrepareThread prepareThread = new PrepareThread(cyclicBarrier); Thread thread1 = new Thread(prepareThread, "运动员1"); Thread thread2 = new Thread(prepareThread, "运动员2"); Thread thread3 = new Thread(prepareThread, "运动员3"); Thread thread4 = new Thread(prepareThread, "运动员4"); Thread thread5 = new Thread(prepareThread, "运动员5"); thread1.start(); thread2.start(); thread3.start(); thread4.start(); thread5.start(); } }
运行结果如下:
运动员1准备好了 运动员2准备好了 运动员3准备好了 运动员5准备好了 运动员4准备好了 开始比赛! 运动员2到达终点 运动员1到达终点 运动员5到达终点 运动员4到达终点 运动员3到达终点 比赛结束!
线程阻塞工具类LockSupport
- LockSupport可以在任意位置让线阻塞,LockSupport的静态方法park()可以阻塞当前线程,和Object.wait()相比,它不需要先获得某个对象的锁,也不会抛出InterruptedException。
- 和suspend相比,它不会造成死锁,是因为LockSupport内部是基于信号量实现的,它为每一个线程都准备了一个许可,如果许可可用,park()函数会立即返回。
- LockSupport支持中断影响,但是不抛出中断异常,需要通过Thread.interrupted()进行判断。
package com.ecut.locksupport; import java.util.concurrent.locks.LockSupport; public class LockSupportTest { private static class LockSupportThread implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + "线程阻塞"); LockSupport.park(); for (int i = 0; i < 5; i++) { System.out.println(i); } } } public static void main(String[] args) throws InterruptedException { LockSupportThread lockSupportThread = new LockSupportThread(); Thread thread = new Thread(lockSupportThread,"t1"); thread.start(); Thread.sleep(5000); //唤醒阻塞线程 System.out.println("main唤醒阻塞线程"); LockSupport.unpark(thread); } }
运行结果如下:
t1线程阻塞 main唤醒阻塞线程 0 1 2 3 4
2、线程池
线程池的概念:
- 线程池中有几个活跃的线程,当你需要使用线程的时候,可以从池子中随便拿一个空闲线程,当完成工作的时候,将线程退回到池子中,方便其他人使用。
- 创建线程变成了从池子中获取空闲线程,销毁线程变成了向线程归还线程。
线程池产生的原因:
- 大量的线程的创建和销毁会消耗很多时间。
- 线程本身也要占据内存空间,大量的线程会抢占宝贵的内存资源。
- 大量的线程回收也会给GC带来很大的负担,延长了GC停顿时间。
JDK对线程池的支持:
- Executors类扮演这线程池工厂的角色,主要有以下工厂方法。
- newFixedThreadPool():返回固定线程数量的线程池,该线程池中的线程数量始终不变。
package com.ecut.threadpool; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class FixedThreadPoolTest { public static class FixedThreadPoolTask implements Runnable { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public void run() { try { Thread.sleep(1000); String time = simpleDateFormat.format(new Date(System.currentTimeMillis())); System.out.println("fixedThreadPoolTask----" + time + ":" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { FixedThreadPoolTask fixedThreadPoolTask = new FixedThreadPoolTask(); ExecutorService executorService = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { executorService.submit(fixedThreadPoolTask); } } }
运行结果如下:
fixedThreadPoolTask----2019-03-02 16:43:50:pool-1-thread-4 fixedThreadPoolTask----2019-03-02 16:43:50:pool-1-thread-3 fixedThreadPoolTask----2019-03-02 16:43:50:pool-1-thread-2 fixedThreadPoolTask----2019-03-02 16:43:50:pool-1-thread-1 fixedThreadPoolTask----2019-03-02 16:43:50:pool-1-thread-5 fixedThreadPoolTask----2019-03-02 16:43:51:pool-1-thread-2 fixedThreadPoolTask----2019-03-02 16:43:51:pool-1-thread-4 fixedThreadPoolTask----2019-03-02 16:43:51:pool-1-thread-1 fixedThreadPoolTask----2019-03-02 16:43:51:pool-1-thread-3 fixedThreadPoolTask----2019-03-02 16:43:51:pool-1-thread-5
- new SingleThreadExecutorTask():返回只有一个线程的线程池,多余的任务会被暂存在一个等待队列中,按照先入先出的原则顺序执行队列中的任务。
package com.ecut.threadpool; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SingleThreadExecutorTest { public static class SingleThreadExecutorTask implements Runnable { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public void run() { try { Thread.sleep(1000); String time = simpleDateFormat.format(new Date(System.currentTimeMillis())); System.out.println("singleThreadExecutorTask----" + time + ":" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { SingleThreadExecutorTask singleThreadExecutorTask = new SingleThreadExecutorTask(); ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { executorService.submit(singleThreadExecutorTask); } } }
运行结果如下:
singleThreadExecutorTask----2019-03-02 16:50:58:pool-1-thread-1 singleThreadExecutorTask----2019-03-02 16:50:59:pool-1-thread-1 singleThreadExecutorTask----2019-03-02 16:51:00:pool-1-thread-1 singleThreadExecutorTask----2019-03-02 16:51:01:pool-1-thread-1 singleThreadExecutorTask----2019-03-02 16:51:02:pool-1-thread-1 singleThreadExecutorTask----2019-03-02 16:51:03:pool-1-thread-1 singleThreadExecutorTask----2019-03-02 16:51:04:pool-1-thread-1 singleThreadExecutorTask----2019-03-02 16:51:05:pool-1-thread-1 singleThreadExecutorTask----2019-03-02 16:51:06:pool-1-thread-1 singleThreadExecutorTask----2019-03-02 16:51:07:pool-1-thread-1
- new CachedThreadPoolTask():返回可根据实际情况调整线程数的线程池,线程池的数量不确定。多余的任务会创建新的线程处理任务。
package com.ecut.threadpool; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class CachedThreadPoolTest { public static class CachedThreadPoolTask implements Runnable { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public void run() { try { Thread.sleep(1000); String time = simpleDateFormat.format(new Date(System.currentTimeMillis())); System.out.println("cachedThreadPoolTask----" + time + ":" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { CachedThreadPoolTask cachedThreadPoolTask = new CachedThreadPoolTask(); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { executorService.submit(cachedThreadPoolTask); } } }
运行结果如下:
cachedThreadPoolTask----2019-03-02 16:52:43:pool-1-thread-1 cachedThreadPoolTask----2019-03-02 16:52:43:pool-1-thread-5 cachedThreadPoolTask----2019-03-02 16:52:43:pool-1-thread-9 cachedThreadPoolTask----2019-03-02 16:52:43:pool-1-thread-4 cachedThreadPoolTask----2019-03-02 16:52:43:pool-1-thread-8 cachedThreadPoolTask----2019-03-02 16:52:43:pool-1-thread-3 cachedThreadPoolTask----2019-03-02 16:52:43:pool-1-thread-7 cachedThreadPoolTask----2019-03-02 16:52:43:pool-1-thread-6 cachedThreadPoolTask----2019-03-02 16:52:43:pool-1-thread-2 cachedThreadPoolTask----2019-03-02 16:52:43:pool-1-thread-10
- newScheduledThreadPool():返回ScheduledExecutorService对象,ScheduledExecutorService接口在ExecutorService接口上拓展了在给定时间执行某个任务的功能,如在某个固定的延迟之后再执行或者周期性执行某个任务。
package com.ecut.threadpool; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ScheduledThreadPoolTest { public static void main(String[] args) { ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String time = simpleDateFormat.format(new Date(System.currentTimeMillis())); System.out.println(time + ":" + Thread.currentThread().getName()); /** * 第一个任务将在10秒后执行,相邻任务的开始时间之间相差2秒,period如果小于任务的执行时间则下一个任务会立即执行 */ scheduledExecutorService.scheduleAtFixedRate(() -> { try { String t = simpleDateFormat.format(new Date(System.currentTimeMillis())); System.out.println("scheduleAtFixedRate----" + t + ":" + Thread.currentThread().getName()); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }, 10, 2, TimeUnit.SECONDS); /** * 第一个任务将在10秒后执行,任务每隔2秒执行一次,因为线程睡了1秒,所以相邻任务的开始时间之间相差3秒 */ scheduledExecutorService.scheduleWithFixedDelay(() -> { { String t = simpleDateFormat.format(new Date(System.currentTimeMillis())); System.out.println("scheduleWithFixedDelay----" + t + ":" + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }, 10, 2, TimeUnit.SECONDS); } }
- newSingleThreadScheduledExecutor():返回ScheduledExecutorService对象,线程池大小为1。
package com.ecut.threadpool; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class SingleThreadScheduledExecutorTest { public static void main(String[] args) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String time = simpleDateFormat.format(new Date(System.currentTimeMillis())); System.out.println(time + ":" + Thread.currentThread().getName()); ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); /** * 在10秒后,会执行一次任务 */ scheduledExecutorService.schedule(() -> { try { String t = simpleDateFormat.format(new Date(System.currentTimeMillis())); System.out.println("SingleThreadScheduledExecutor----" + t + ":" + Thread.currentThread().getName()); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }, 10, TimeUnit.SECONDS); } }
ThreadPoolExecutor构造函数参数含义:
- int corePoolSize指定了线程池中的线程数量。
- int maximumPoolSize指定了线程池中的最大线程数量。
- long keepAliveTime当线程池线程数量超过了corePoolSize时多余空闲线程的存活时间。
- TimeUnit unit存活时间的单位。
- BlockingQueue<Runnable> workQueue任务队列被提交但尚未执行的任务。
- ThreadFactory threadFactory线程工厂用于创建线程,一般默认的即可。
- RejectedExecutionHandler handler拒绝策略,当任务太多来不及处理,如何拒绝任务。
队列分类:
- 直接提交队列该功能由SynchronousQueue对象提供,SynchronousQueue没有容量,插入操作要等待相应的删除操作,删除操作要等待对应插入操作,提交的任务不会被真实的保存下来,如果没有空闲线程则会创建线程,线程数量若超过最大线程数执行拒绝策略。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
没有任务时该线程内无线程当任务被提交的时候会使用空闲线程执行任务,若无空闲线程则将任务加入到到队列中,队列又是立即提交的队列则会创建新的线程执行,任务执行完后会在60秒内回收。
- 有界的任务队列可以使用ArrayBlockingQueue实现其构造函数必须带容量,表示该队列的最大容量。
- 无界的任务队列可以通过LinkedBlockingQueue类实现。无界的任务队列除非系统资源耗尽否则不存在入队失败的情况。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
corePoolSize和maximumPoolSize大小一样的并且使用了无界队列,当任务提交非常频繁的时候该队列迅速膨胀从而耗尽资源。
- 优先任务队列实带有执行优先级的队列通过PriorityBlockingQueue实现,是一个特殊的无界队列可以根据任务自身的优先级顺序先后执行。
- 线程池核心调度逻辑任务提交小于corePoolSize核心线程数则分配线程执行,大于corePoolSize则提交到等待队列,提交成功就等待执行,如果队列满了,判断是否达到了最大线程数,如果小于就分配线程执行,大于就执行拒绝策略。
拒绝策略:
- AbortPolicy策略会直接抛出异常阻止系统正常工作。
- CallerRunsPolicy策略直接在调用者线程中运行当前被丢弃的任务。
- DiscardOldestPolicy策略丢弃最老的一个请求也就是即将被执行的一个任务,并尝试再次提交当前任务。
- DiscardPolicy策略丢弃无法处理的任务不予任何处理。
自定义线程的创建ThreadFactory:
- ThreadFactory是一个接口,它只有一个方法,用来创建线程。
package com.ecut.threadpool; import java.util.concurrent.*; public class ThreadFactoryTest { public static class ThreadFactoryTask implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()+":"+System.currentTimeMillis()); } } public static void main(String[] args) throws InterruptedException { ThreadFactoryTask threadFactoryTask = new ThreadFactoryTask(); ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(), new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setDaemon(true); System.out.println("create"+thread); return thread; } }); for(int i = 0 ; i < 5 ; i++){ executorService.submit(threadFactoryTask); } Thread.sleep(2000); } }
- ThreadPoolExecutor也是一个可以拓展的线程池,可以来监控任务执行的时间。
package com.ecut.threadpool; import java.util.concurrent.ExecutorService; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadPoolExecutorTest { public static class ThreadPoolExecutorTask implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis()); } } public static void main(String[] args) { ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>()) { @Override protected void beforeExecute(Thread t, Runnable r) { System.out.println("准备执行" + Thread.currentThread().getName()); } @Override protected void afterExecute(Runnable r, Throwable t) { System.out.println("执行结束" + Thread.currentThread().getName()); } @Override protected void terminated() { System.out.println("线程退出"); } }; ThreadPoolExecutorTask threadPoolExecutorTask = new ThreadPoolExecutorTask(); for (int i = 0; i < 5; i++) { executorService.submit(threadPoolExecutorTask); } executorService.shutdown(); } }
运行结果如下:
准备执行pool-1-thread-1 pool-1-thread-1:1551530510144 执行结束pool-1-thread-1 准备执行pool-1-thread-2 pool-1-thread-2:1551530510160 执行结束pool-1-thread-2 准备执行pool-1-thread-4 pool-1-thread-4:1551530510160 执行结束pool-1-thread-4 准备执行pool-1-thread-5 准备执行pool-1-thread-3 pool-1-thread-3:1551530510160 执行结束pool-1-thread-3 pool-1-thread-5:1551530510160 执行结束pool-1-thread-5 线程退出
Fork/Join框架:
- 使用fork()后系统多一个执行分支,所以需要等待这个执行分支完毕后才可以得到最终结果,因此join表示等待。
- JDK提供了ForkJoinPool线程池来节省资源,可以向线程池提交任务ForkJoinTask,ForkJoinTask有两个子类有返回值的RecursiveTask和无返回值的RecursiveAction。
3、JDK并发容器
ConcurrentHashMap:
- Collections.synchronizedMap()会生成一个SynchronizedMap,它将所有功能交个map实现,而自己主要负责线程安全。首先SynchronizedMap包装了一个map,然后通过mutex实现了对这个map的互斥操作。
SynchronizedMap(Map<K,V> m) { this.m = Objects.requireNonNull(m); mutex = this; } public int size() { synchronized (mutex) {return m.size();} } public boolean isEmpty() { synchronized (mutex) {return m.isEmpty();} } public boolean containsKey(Object key) { synchronized (mutex) {return m.containsKey(key);} } public boolean containsValue(Object value) { synchronized (mutex) {return m.containsValue(value);} } public V get(Object key) { synchronized (mutex) {return m.get(key);} } public V put(K key, V value) { synchronized (mutex) {return m.put(key, value);} } public V remove(Object key) { synchronized (mutex) {return m.remove(key);} } public void putAll(Map<? extends K, ? extends V> map) { synchronized (mutex) {m.putAll(map);} } public void clear() { synchronized (mutex) {m.clear();} }
在高并发环境中Collections.synchronizedMap()存在无论是读取还是写都要或得mutex锁,会使map操作全部处于等待状态的问题。
- ConcurrentHashMap是高效并发线程安全的HashMap。jdk 1.7中,它通过减小锁粒度的方式来提高性能,它内部会细分成16个段(segement),增加表项时先根据hashcode来得到该表项应该存在那个段中,然后对该段加锁并完成put操作。jdk1.8源码分析
CopyOnWriteArrayList:
- 在读多写少的场合,这个List性能非常好。因为CopyOnWriteArrayList读取完全不加锁,写入也不会阻塞读取操作,只有写入和写入之间需要进行同步等待。
- 写入操作使用锁来用于写写的情况,然后通过内部复制生成一个新的数组,在用新数组替换老数组,修改完成后,读线程会察觉到volatile修饰的array被修改了。整个修改过程并不会影响读。
public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }
ConcurrentLinkedQueue:
高效的并发队列,基于链表实现可以看成是线程安全的LinkedList。
- BlockingQueue:是一个接口,JDK内部通过链表、数组等方式来实现这个接口,表示阻塞队列适合用作数据共享的通道。
- 队列为空,take操作时当前线程会等待在Condition notEmpty上,知道插入新的元素会通知等待线程,同理队列满了,put操作会一直等待在notFull上,直到有空位会通知等待线程。
ConcurrentSkipListMap:
- 使用跳表的数据结构进行快速查找。跳表数据结构与平衡树类似,但是插入和删除操作无需和平衡树一样要对全局调整,只要部分调整即可。并发的情况下要对平衡树全局加锁,当跳表只需要部分加锁。
- 跳表维持了多个链表,最底层链表维持了所有元素,上一层是下一层的子集,采用空间换时间的算法来提高查找速度。
源码地址:
https://github.com/SaberZheng/concurrent-test
转载请于明显处标明出处