多线程与JUC
线程的创建
- 创建线程使用thread(不推荐)
@Slf4j(topic = "c.t")
public class Demo2 {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
log.info("子线程");
}
};
thread.start();
log.info("主线程");
}
}
- 创建线程使用runnable(推荐)
@Slf4j(topic = "c.t3")
public class Demo3 implements Runnable {
@Override
public void run() {
log.info("子线程");
}
public static void main(String[] args) {
Demo3 demo3 = new Demo3();
Thread thread = new Thread(demo3);
thread.start();
log.info("主线程");
}
}
java8之后支持函数式编程
可实现多线程为lambda表达式
@Slf4j(topic = "c.t1") public class Demo1 { public static void main(String[] args) { new Thread(() -> {log.info("子线程");}).start(); log.info("主线程"); } }
- 创建线程FutureTask
@Slf4j(topic = "c.t4")
public class Demo4 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.info("子线程");
Thread.sleep(1000);
return 100;
}
});
Thread thread = new Thread(task);
thread.start();
Integer integer = task.get();//阻塞
log.info("主线程获取{}", integer);
}
}
⚠️ Futuretask间接实现了Runnable方法与Future接口支持子线程数据的返回
线程中的方法
锁
synchronize
死锁相互等待 循环饮用
定位死锁 jps jstack
JUC中主要的工具类
CountDownLatch
public class Demo1 {
/**
* 需求 开器多个线程时需要主线程等待线程全部结束后再继续执行
* 1. join 简单实现是线程顺序执行
* 2. @CountDownLatch 实现
* <p>
* 代码实现 保安最后关门离开
*/
public static void main(String[] args) throws InterruptedException {
int p = 10;
CountDownLatch countDownLatch = new CountDownLatch(p);
for (int i = 1; i <= p; i++) {
new Thread(() -> {
String name = Thread.currentThread().getName();
log.info(name + "已经离开商场");
countDownLatch.countDown();
}, i + "线程").start();
}
countDownLatch.await();
log.info("保安离开 并且关闭商场的门");
}
}
CyclicBarrier
public class Demo2 {
/**
* 需求 每运行N次线程程序之后 进行主线程的执行
* <p>
* P人开会
*/
public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
int p = 10;
CyclicBarrier cyclicBarrier = new CyclicBarrier(p, () -> log.info("人已经到齐 开始会议"));
for (int i = 1; i <= p; i++) {
final int temp = i;
new Thread(() -> {
String name = Thread.currentThread().getName();
log.info("{}已经到位,{}号人来了", name, temp);
try {
int await = cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}, i + "线程").start();
}
}
}
Semaphore
public class Demo3 {
/**
* java 信号量 著名哲学家问题 停车场问题
*/
public static void main(String[] args) {
int p = 10;
Semaphore semaphore = new Semaphore(2);
for (int i = 1; i <= p; i++) {
new Thread(() -> {
try {
semaphore.acquire();
String name = Thread.currentThread().getName();
log.info("{}号🚗进去停车场", name);
Thread.sleep(6000);
log.info("{}号🚗🚗🚗🚗🚗🚗🚗🚗🚗🚗🚗出停车场", name);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}, String.valueOf(i)).start();
}
semaphore.release();
}
}
线程池创建
Executors
Executors: 对 ThreadPoolExecutor
和ScheduledThreadPoolExecutor
封装的工具类,方便创建线程池。但是不推荐使用。应该使用ThreadPoolExecutor
线程池提交任务的种类
往线程池中提交任务,主要有两种方法,execute()
和submit()
。
- execute 提交的线程无返回结果
- submit 提交的线程对象有返回结果 可以通过
Future
接收返回 通过get
方法获取 但是get
方法是阻塞的方法get(long timeout, TimeUnit unit)
的重载对象获取,时间超过会抛出TimeoutException
关闭线程池
在线程池使用完成之后,我们需要对线程池中的资源进行释放操作,这就涉及到关闭功能。我们可以调用线程池对象的shutdown()
和shutdownNow()
方法来关闭线程池。
这两个方法都是关闭操作,又有什么不同呢?
shutdown()
会将线程池状态置为SHUTDOWN
,不再接受新的任务,同时会等待线程池中已有的任务执行完成再结束。shutdownNow()
会将线程池状态置为SHUTDOWN
,对所有线程执行interrupt()
操作,清空队列,并将队列中的任务返回回来。
另外,关闭线程池涉及到两个返回boolean的方法,isShutdown()
和isTerminated
,分别表示是否关闭和是否终止。
线程池监控
如果系统中大量用到了线程池,那么我们有必要对线程池进行监控。利用监控,我们能在问题出现前提前感知到,也可以根据监控信息来定位可能出现的问题。
首先,ThreadPoolExecutor
自带了一些方法。
long getTaskCount()
,获取已经执行或正在执行的任务数long getCompletedTaskCount()
,获取已经执行的任务数int getLargestPoolSize()
,获取线程池曾经创建过的最大线程数,根据这个参数,我们可以知道线程池是否满过int getPoolSize()
,获取线程池线程数int getActiveCount()
,获取活跃线程数(正在执行任务的线程数)
其次,ThreadPoolExecutor
留给我们自行处理的方法有3个,它在ThreadPoolExecutor
中为空实现(也就是什么都不做)。
protected void beforeExecute(Thread t, Runnable r)
// 任务执行前被调用protected void afterExecute(Runnable r, Throwable t)
// 任务执行后被调用protected void terminated()
// 线程池结束后被调用
四种基本线程池
-
newFixedThreadPool
public class Demo1 { public static void main(String[] args) { //创建固定大小的线程池 Executor executor = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { executor.execute(() -> { log.info("线程运行{}", Thread.currentThread().getName()); try { Thread.sleep(4000L); } catch (InterruptedException e) { e.printStackTrace(); } }); } } } public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
⚠️创建了线程固定的线程池 不推荐使用 线程浪费与线程OOM同时存在 存在风险
-
newSingleThreadExecutor
public class Demo2 { public static void main(String[] args) { //创建单线程池 Executor executor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 11; i++) { executor.execute(() -> { log.info("线程运行{}", Thread.currentThread().getName()); try { Thread.sleep(4000L); } catch (InterruptedException e) { e.printStackTrace(); } }); } } } public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); //最大长度为int类型最大值 }
⚠️创建单线称池 累计大量任务造成堆溢出
-
newCachedThreadPool
public class Demo3 { public static void main(String[] args) { //创建缓存线程池 Executor executor = Executors.newCachedThreadPool(); for (int i = 0; i < 1111; i++) { executor.execute(() -> { log.info(Thread.currentThread().getName()); }); } } } public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
创建一个有60s缓存的线程池。该线程可以根据需要智能的创建新的线程,或者重用空闲但未过缓存期的线程。
如果线程池中有超过缓存期的线程(60s不执行任务),该闲置的线程将会被终止并从线程池中移除。不过,该线程池的最大线程数量是
Integer.MAX_VALUE
,极端情况下,可能会推积大量请求,从而导致OOM。 -
newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }
⚠️创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
不过,该线程池的最大线程数量是
Integer.MAX_VALUE
,极端情况下,可能会推积大量请求,从而导致OOM。
ThreadPoolExecutor解读
-
构造方法
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
-
字段含义
- corePoolSize 线程基础数量
- maxmumPoolSize 线程最大数量
- keepAliveTime 存活时间
- unit 存活时间单位
- workqueue 存储任务的队列
- threadFactory 线程工厂
- handler 拒绝策略
-
字段解读
-
字面意思
-
字面意思
- 任务的性质:CPU密集型、IO密集型和混杂型
- 任务的优先级:高中低
- 任务执行的时间:长中短
- 任务的依赖性:是否依赖数据库或者其他系统资源
不同的性质的任务,我们采取的配置将有所不同。在《Java并发编程实践》中有相应的计算公式。
通常来说,如果任务属于CPU密集型,那么我们可以将线程池数量设置成CPU的个数,以减少线程切换带来的开销。如果任务属于IO密集型,我们可以将线程池数量设置得更多一些,比如CPU个数*2。
PS:我们可以通过
Runtime.getRuntime().availableProcessors()
来获取CPU的个数。 -
字面意思
-
字面意思
-
等待队列
等待队列是
BlockingQueue
类型的,理论上只要是它的子类,我们都可以用来作为等待队列。ArrayBlockingQueue
,队列是有界的,基于数组实现的阻塞队列LinkedBlockingQueue
,队列可以有界,也可以无界。基于链表实现的阻塞队列SynchronousQueue
,不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作将一直处于阻塞状态。该队列也是Executors.newCachedThreadPool()
的默认队列PriorityBlockingQueue
,带优先级的无界阻塞队列
-
线程工厂
public interface ThreadFactory { /** * Constructs a new {@code Thread}. Implementations may also initialize * priority, name, daemon status, {@code ThreadGroup}, etc. * * @param r a runnable to be executed by new thread instance * @return constructed thread, or {@code null} if the request to * create a thread is rejected */ Thread newThread(Runnable r); }
Executors
的实现使用了默认的线程工厂-DefaultThreadFactory
。它的实现主要用于创建一个线程,线程的名字为pool-{poolNum}-thread-{threadNum}
。static class DefaultThreadFactory implements ThreadFactory { private static final AtomicInteger poolNumber = new AtomicInteger(1); private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; DefaultThreadFactory() { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; } public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); if (t.isDaemon()) t.setDaemon(false); if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY); return t; } }
很多时候,我们需要自定义线程名字。我们只需要自己实现
ThreadFactory
,用于创建特定场景的线程即可。 -
拒绝策略
CallerRunsPolicy
// 在调用者线程执行AbortPolicy
// 直接抛出RejectedExecutionException
异常DiscardPolicy
// 任务直接丢弃,不做任何处理DiscardOldestPolicy
// 丢弃队列里最旧的那个任务,再尝试执行当前任务
这四种策略各有优劣,比较常用的是
DiscardPolicy
,但是这种策略有一个弊端就是任务执行的轨迹不会被记录下来。所以,我们往往需要实现自定义的拒绝策略, 通过实现RejectedExecutionHandler
接口的方式。
-