多线程开发 - 常用的基础类
一,ExecutorService JDK内置的多线程池类
简介:提供管理终止和执行的线程管理工具,用于跟踪一个或多个异步任务。ExecutorService 可以提交继承 Thread 或者是Runable的接口对应的类
上实例
/**
* 多线程Demo实例
* @date :Created in 2019-07-23 19:59
*/
public class DemoExecutor extends Thread {
private Integer demoId;
private String demoMsg;
public DemoExecutor(Integer demoId, String demoMsg) {
this.demoId = demoId;
this.demoMsg = demoMsg;
}
public Integer getDemoId() {
return demoId;
}
public void setDemoId(Integer demoId) {
this.demoId = demoId;
}
public String getDemoMsg() {
return demoMsg;
}
public void setDemoMsg(String demoMsg) {
this.demoMsg = demoMsg;
}
@Override
public String toString() {
return "DemoExecutor{" +
"demoId=" + demoId +
", demoMsg=" + demoMsg +
'}';
}
/**
* 线程执行内容
*/
@Override
public void run() {
try {
System.out.println("[" + this.demoId + "] " + "[ " + this.demoMsg + " ]" + " thread start....");
Thread.sleep(10000);
} catch (Exception e) {
System.out.println("线程执行异常:" + this.toString());
}
}
}
1 import java.util.concurrent.ExecutorService; 2 import java.util.concurrent.Executors; 3 4 /** 5 * 主函数启动 6 * @date :Created in 2019-07-23 20:07
8 */ 9 public class DemoMain { 10 11 public static void main(String[] args) { 12 /** 13 * 线程池不建议这样子创建,原因如下 14 * 15 * 以下这行代码内部实现:创建重用固定数量线程的线程池在共享的无边界队列上操作。
* 在任何时候,多线程将是活动的处理任务。 16 * 如果在所有线程都处于活动状态时提交了其他任务,它们将在队列中等待,直到线程可用。
* 如果任何线程在执行期间因失败而终止 17 * 在关闭之前,如果需要,将更换一个新的。执行后续任务。 18 * 19 * 简单的说,会产生有4个线程的线程池,即使出现错误,也会重新创建,
* 这里就有在大量创建的时候,线程不会自行销毁, 20 * 且线程之前的切换,都是需要消耗资源,长时间运行会产生性能问题,
* 别急,后面类会有就更详细的说明和创建 21 * 那就是ThreadPoolExecutor直接构建参数,后续更新讲解 22 * */ 23 ExecutorService service = Executors.newFixedThreadPool(4); 24 25 //生成50个线程任务 26 for (int i = 1; i <= 50; i++) { 27 //提交线程任务 28 service.submit(new DemoExecutor(i, "I am No." + i + " task")); 29 } 30 31 /** 32 * 等待线程任务全部执行完成关闭 33 * 由于池子只有4个线程,因此其他线程会排队,直到全部线程执行完毕 34 * */ 35 service.shutdown(); 36 } 37 }
实现的效果图如下

小结:上面两个代码利用ExecutorService实现一个线程池,这样子可以极大提高我们日常开发的线程管理,不用手动去通过start() 去启动线程,实现业务。代码内容里面创建线程池不建议这样子创建。后续更新增加创建方式和使用细节😁
二,Semaphore JDK内置的线程共享资源类
简介:该类在JDK中称之为计算信号量,通过调用 acquire() 方法可以获取许可,继续执行业务,若当前没有获得许可,则需要等待,直到获得许可才能继续执行业务。通过调用 release() 方法进行许可证的释放,让其他线程获得许可可以继续执行业务。该类通常用限制执行业务的线程数量,而不是线程的数量,只是线程没有许可。在等待获得许可继续执行。
常见的业务场景:如购票,银行排号,大天朝女生厕所等等。
上买票的demo
import java.util.concurrent.Semaphore; /** * @date :Created in 2019-07-24 19:38 */ public class DemoSemaphore extends Thread { private Integer demoId; private String demoMsg; private Semaphore semaphore; public Integer getDemoId() { return demoId; } public void setDemoId(Integer demoId) { this.demoId = demoId; } public String getDemoMsg() { return demoMsg; } public void setDemoMsg(String demoMsg) { this.demoMsg = demoMsg; } public Semaphore getSemaphore() { return semaphore; } public void setSemaphore(Semaphore semaphore) { this.semaphore = semaphore; } @Override public String toString() { return "DemoSemaphore{" + "demoId=" + demoId + ", demoMsg='" + demoMsg + '\'' + ", semaphore=" + semaphore + '}'; } public DemoSemaphore(Integer demoId, String demoMsg, Semaphore semaphore) { this.demoId = demoId; this.demoMsg = demoMsg; this.semaphore = semaphore; } @Override public void run() { try { //当前线程去获取许可,获取到则继续业务,获取不到则等待许可发放 semaphore.acquire(); System.out.println("顾客[" + this.demoId + "] 获得买票资格"); long startTime = System.currentTimeMillis(); //模拟正常业务执行消耗的时间 Thread.sleep((int) (Math.random() * 10000)); System.out.println("顾客[" + this.demoId + "] 购买完毕, 购买信息[" + this.demoMsg + "], 花费时间:" + (System.currentTimeMillis() - startTime) + "ms"); } catch (Exception e) { System.out.println("业务异常 :" + e.getMessage()); }finally { //执行完毕业务,归还许可,让下面的线程可以继续获得许可执行业务 semaphore.release(); } } }
import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.concurrent.*; /** * @date :Created in 2019-07-24 19:50 */ public class DemoMain { public static void main(String[] args) { //创建线程池 ExecutorService taskExecutor = DemoMain.getThreadPoolTaskExecutor(); //创建3个信号量,即有3个窗口 Semaphore semaphore = new Semaphore(3); //模拟100个人进行排队购票 for (int i = 1; i <= 100; i++) { taskExecutor.submit(new DemoSemaphore(i, "火车票", semaphore)); } taskExecutor.shutdown(); //使等待进入acquire()方法的线程,不允许被中断。 semaphore.acquireUninterruptibly(3); System.out.println("【售票的小姐姐总算处理完毕今天的票了!】"); semaphore.release(3); } /** * 上面一个类说了线程池的创建问题,这次更新提供一下创建的方式, * 后续更新继续更详细的说明 ExecutorService * 以及spring对 ExecutorService 的封装类 ThreadPoolTaskExecutor */ public static ExecutorService getThreadPoolTaskExecutor() { //用google的公共包创建线程工厂工具(负责创建线程) ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build(); /** * 创建线程池 * * @param corePoolSize 核心线程数量 * @param maximumPoolSize 池子最大的线程数量 * @param keepAliveTime 保持活跃的时间(毫秒) * @param timeUnit TimeUnit.MILLISECONDS * @param workQueue 超过最大线程数量时候,堵塞队列 * @param threadFactory 线程工厂 * @param handler 线程拒绝策略 * */ ExecutorService pool = new ThreadPoolExecutor( 3, 20, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), threadFactory, new ThreadPoolExecutor.AbortPolicy()); return pool; } }
最终实现的效果图如下:

小结:通过该实例模拟程序在应对共享资源场景下的解决实际业务问题,简单说明一下线程池合理的创建方式,后续还会继续更新,并且会说明spring相关的多线程。😃
二,ReentrantLock
简介:该类在JDK中线程锁,具备可重入性,即在一个获得锁的线程可以多次获取锁,本节示例未说明该特性,在调用方法lock()进行锁定,其他线程需要等待处理完毕,调用unlock()进行释放,其他线程可以申请获得锁继续处理业务逻辑。
常用的场景:秒杀
上秒杀的示例
1 import java.util.concurrent.locks.ReentrantLock; 2 3 public class DemoReentrantLock extends Thread { 4 5 private static int GOOD_NUM = 3; 6 7 private Integer demoId; 8 9 private String demoMsg; 10 11 private ReentrantLock reentrantLock; 12 13 public DemoReentrantLock(Integer demoId, String demoMsg, ReentrantLock reentrantLock){ 14 this.demoId = demoId; 15 this.demoMsg = demoMsg; 16 this.reentrantLock = reentrantLock; 17 } 18 19 @Override 20 public void run() { 21 try { 22 //增加随机性 23 Thread.sleep((int) (Math.random() * 1000)); 24 System.out.println("ID: " + demoId + "进入秒杀频道!"); 25 26 //获取锁,未获得锁则一直等待,直到其他线程释放该锁,才能进行获取执行业务 27 reentrantLock.lock(); 28 29 //业务处理 秒杀获取,还有奖品,则释放奖品,没有则返回已结束 30 if (DemoReentrantLock.GOOD_NUM > 0){ 31 System.out.println("恭喜ID: " + demoId + ",秒杀到了商品【" + demoMsg + "】"); 32 DemoReentrantLock.GOOD_NUM --; 33 }else { 34 System.out.println("抱歉,我的亲爱的用户,您未秒杀到商品,期待你下一次的参与!"); 35 } 36 Thread.sleep( 1000); 37 } catch (Exception e) { 38 e.printStackTrace(); 39 } finally { 40 System.out.println("释放锁,让下一个用户可以进行获取秒杀机会!"); 41 //释放锁,让下一个线程继续进行秒杀操作 42 reentrantLock.unlock(); 43 } 44 } 45 }
1 import com.google.common.util.concurrent.ThreadFactoryBuilder; 2 3 import java.util.concurrent.*; 4 import java.util.concurrent.locks.ReentrantLock; 5 6 public class DemoMain { 7 8 public static void main(String[] args) { 9 //创建线程池 10 ExecutorService taskExecutor = DemoMain.getThreadPoolTaskExecutor(); 11 12 //创建锁 13 ReentrantLock reentrantLock = new ReentrantLock(); 14 15 //模拟100个人抢3个商品 16 for (int i = 1; i <= 100; i++) { 17 taskExecutor.submit(new DemoReentrantLock(i, "iPhone X 256G 顶配版", reentrantLock)); 18 } 19 taskExecutor.shutdown(); 20 } 21 22 /** 23 * 上面一个类说了线程池的创建问题,这次更新提供一下创建的方式, 24 * 后续更新继续更详细的说明 ExecutorService 25 * 以及spring对 ExecutorService 的封装类 ThreadPoolTaskExecutor 26 */ 27 public static ExecutorService getThreadPoolTaskExecutor() { 28 //用google的公共包创建线程工厂工具(负责创建线程) 29 ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build(); 30 31 /** 32 * 创建线程池 33 * 34 * @param corePoolSize 核心线程数量 35 * @param maximumPoolSize 池子最大的线程数量 36 * @param keepAliveTime 保持活跃的时间(毫秒) 37 * @param timeUnit TimeUnit.MILLISECONDS 38 * @param workQueue 超过最大线程数量时候,堵塞队列 39 * @param threadFactory 线程工厂 40 * @param handler 线程拒绝策略 41 * */ 42 ExecutorService pool = new ThreadPoolExecutor( 43 10, 44 20, 45 0L, 46 TimeUnit.MILLISECONDS, 47 new LinkedBlockingQueue<Runnable>(1024), 48 threadFactory, 49 new ThreadPoolExecutor.AbortPolicy()); 50 return pool; 51 } 52 }
最终实现的效果如下:

小结:通过该实例模拟程序实现在秒杀情况下,控制一个个请求排队,获取锁的即获得了秒杀的产品,其他未获得都提示未秒杀到。

浙公网安备 33010602011771号