@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("主线程");
}
}
@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("主线程"); } }
@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
public class Demo1 {
/**
* 需求 开器多个线程时需要主线程等待线程全部结束后再继续执行
* 1. join 简单实现是线程顺序执行
* 2. @CountDownLatch 实现
*
* 代码实现 保安最后关门离开
*/
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("保安离开 并且关闭商场的门");
}
}
public class Demo2 {
/**
* 需求 每运行N次线程程序之后 进行主线程的执行
*
* 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();
}
}
}
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: 对 ThreadPoolExecutor
和ScheduledThreadPoolExecutor
封装的工具类,方便创建线程池。但是不推荐使用。应该使用ThreadPoolExecutor
往线程池中提交任务,主要有两种方法,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。
构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
字段含义
字段解读
字面意思
字面意思
不同的性质的任务,我们采取的配置将有所不同。在《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
接口的方式。