一、Java线程池使用
创建线程是一个耗资源的过程。创建大连线程会导致系统内存溢出。
所以为了更合理使用线程,通常我们使用线程池在解决
1、简单使用
jdk提供一个工具类Executors可以方便创建线程池
//核心线程0,最新线程Integer.MAX_VALUE,空闲线程存活时间60s
ExecutorService threadPool1=Executors.newCachedThreadPool();
//核心线程10,最大线程10,空闲线程存活时间0
ExecutorService threadPool2=Executors.newFixedThreadPool(10);
//核心线程1,最大线程1,空闲线程存活时间0
ExecutorService threadPool3=Executors.newSingleThreadExecutor();
2、自定义线程池
自定义线程池重点在于自定义以下几个参数
- 线程池名称
- 初始值
- 最大值
- 空闲线程保留时间
1)使用jdk原生代码
ThreadFactory namedThreadFactory = new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(Thread.currentThread().getThreadGroup(), r,
"threadPoolDemo_" + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
};
ExecutorService threadPool = new ThreadPoolExecutor(0, 100, 60L, TimeUnit.MILLISECONDS, new SynchronousQueue<>(),
namedThreadFactory);
上面这段代码表示最大只能100个线程,超过就会拒绝。 正常情况,如果使用SynchronousQueue,那么maximumPoolSize就要设置到足够大
ThreadFactory namedThreadFactory = new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(Thread.currentThread().getThreadGroup(), r,
"threadPoolDemo_" + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
};
ExecutorService threadPool = new ThreadPoolExecutor(10, 10, 60L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
namedThreadFactory);
上面这段代码表示最大只能10个线程,超过部分会放在队列中等待执行
2)使用google工具类
import com.google.common.util.concurrent.ThreadFactoryBuilder;
public void initThreadPool() {
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("loopPrecreate-pool-%d").build();
ExecutorService threadPool = new ThreadPoolExecutor(5, 50, 60L, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(),
namedThreadFactory);
}
上面这段代码表示最大只能50个线程,超过就会拒绝
3)注意:
- 如果使用SynchronousQueue,那么maximumPoolSize就要设置到足够大,并发请求时maximumPoolSize会逐渐增加到maximumPoolSize,超过max会拒绝
- 如果使用LinkedBlockingQueue,那么corePoolSize和maximumPoolSize要设置成一样,maximumPoolSize不会自动扩展
3、使用示例
并发100个线程池
int N = 100;
CountDownLatch latch = new CountDownLatch(N);
CountDownLatch lock = new CountDownLatch(1);
for (int i = 0; i < N; i++) {
Thread.sleep(100L);
threadPool.submit(()->{
try {
lock.await();
System.out.println("---> start " + Thread.currentThread().getName());
Thread.sleep(5000L);
System.out.println("---> end " + Thread.currentThread().getName());
latch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
});
}
lock.countDown();
latch.await();
System.out.println("--end all ---");
二、CountDownLatch 介绍
类似发令枪,CountDownLatch的构造函数可以传入一个数字,代表倒计时多少个数。
主要方法:
- countDown:每调用一次countDown(),倒计时就会减少1,
- await:当倒计时到0时,await()方法就会停止阻塞
1、示例代码
public static void main(String[] args) throws InterruptedException {
int N = 10;
CountDownLatch latch = new CountDownLatch(N);
CountDownLatch lock = new CountDownLatch(1);
for (int i = 0; i < N; i++) {
Thread.sleep(100L);
new Thread(() -> {
try {
lock.await();
System.out.println("---> start " + Thread.currentThread().getName());
Thread.sleep(1000L);
System.out.println("---> end " + Thread.currentThread().getName());
latch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
lock.countDown();
latch.await();
}
三、什么是Future
Future是一个未来对象,里面保存这线程处理结果,它像一个提货凭证,拿着它你可以随时去提取结果
1、什么时候使用
在两种情况下,离开Future几乎很难办。
- 一种情况是拆分订单,比如你的应用收到一个批量订单,此时如果要求最快的处理订单,那么需要并发处理,并发的结果如何收集,这个问题如果自己去编程将非常繁琐,此时可以使用CompletionService解决这个问题。CompletionService将Future收集到一个队列里,可以按结果处理完成的先后顺序进队。
- 另外一种情况是,如果你需要并发去查询一些东西(比如爬虫),并发查询只要有一个结果返回,你就认为查询到了,并且结束查询,这时也需要用CompletionService和Future来解决。
使用过程既可以用CompletionService,也可以自己维护一个list将Future添加进去。
区别是:CompletionService是先完成的先返回,自己维护的list就是按顺序一个个取值
示例代码:
public class FutureDemo {
private static class Task implements Callable<String> {
@Override
public String call() throws Exception {
// 模拟真实事务的处理过程,这个过程是非常耗时的。
Thread.sleep(5000);
return "call return " + Thread.currentThread().getName();
}
}
public static void main(String[] args) throws Exception {
FutureDemo demo =new FutureDemo();
demo.testComplete();
// demo.testFutrue();
}
private void testFutrue() throws Exception{
List<Future<String>> futures = new ArrayList<Future<String>>();
ExecutorService executorService = Executors.newCachedThreadPool();
System.out.println("已经提交资源申请");
for (int i = 0; i < 10; i++) {
futures.add(executorService.submit(new Task()));
}
for (Future<String> future : futures) {
if (!future.isDone()) {
System.out.println("资源还没有准备好");
}
System.out.println(future.get());
}
executorService.shutdown();
}
private void testComplete() throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
CompletionService<String> ecs = new ExecutorCompletionService<>(executorService);
System.out.println("已经提交资源申请");
int lenth =10;
for (int i = 0; i < lenth; i++) {
ecs.submit(new Task());
}
//CompletionService会按处理完后顺序返回结果
List<String> res =new ArrayList<>();
for(int i = 0;i<lenth;i++ ){
Future<String> f = ecs.take();
System.out.println(f.get());
}
executorService.shutdown();
}
}
四、Java前台线程和后台线程
1、前台线程
定义:普通线程又可以称为用户线程,只完成用户自己想要完成的任务,不提供公共服务。
通过new Thread()产生的线程默认都是前台线程。 前台线程在程序执行完成后,才会自己退出,不会随着主线程的退出而退出。假设main线程已经退出了,但是该线程还未执行完成,还会继续执行直到线程自己退出。
java线程池启动的是前台线程
2、后台线程
定义:指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。
后台线程又称守护线程,在前台线程全部退出后,后台线程会随着一起退出,并且不会执行未执行的finally语句。
在线程start之前通过setDaemon(true)方法可以将当前线程设置为后台线程
Thread t1 =new Thread(()->{
try{
System.out.println(Thread.currentThread().getName());
//如果前台线程全部退出,后台线程会一起退出,不会一直休眠下去
Thread.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName()+"out");
}
});
t1.setDaemon(true);//设置为后台线程
t1.start();
3、使用场景
- jvm的垃圾回收器其实就是一个后台线程
- 和主业务无关的服务线程
五、信号量 Semaphore 重点语法
- availablePermits():判断是否有权限
- acquire():获得一个信号,阻塞线程,可被中断
- release():释放一个信号
- acquireUninterruptibly():获得一个信号,阻塞线程,不可被中断
代码示例
1、每次只允许2个线程执行
public class SemaphoreThread {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2);
// 建立一个缓存线程池
ExecutorService es = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
es.submit(new Thread(() -> {
String name = Thread.currentThread().getName();
try {
semaphore.acquire();
System.out.println(LocalDateTime.now().toLocalTime()+" "+name + "开始执行");
Thread.sleep(1000);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}));
}
// 关闭线程池
es.shutdown();
}
}
输出:
13:42:11.763 pool-1-thread-2开始执行
13:42:11.763 pool-1-thread-1开始执行
13:42:12.764 pool-1-thread-3开始执行
13:42:12.764 pool-1-thread-5开始执行
13:42:13.764 pool-1-thread-6开始执行
13:42:13.764 pool-1-thread-4开始执行
13:42:14.764 pool-1-thread-9开始执行
13:42:14.764 pool-1-thread-10开始执行
13:42:15.765 pool-1-thread-7开始执行
13:42:15.765 pool-1-thread-8开始执行
从输出日志可以看出,每秒只有2个线程在执行
2、控制两个线程执行顺序
public class SemaphoreThread {
public static void main(String[] args) {
// 设置公平锁
Semaphore semaphore = new Semaphore(1,true);
// 建立一个缓存线程池
ExecutorService es = Executors.newCachedThreadPool();
es.submit( new Thread(() -> {
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
try {
semaphore.acquire();
System.out.println(LocalDateTime.now().toLocalTime()+" "+name + "正在工作");
Thread.sleep(1000);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}));
es.submit( new Thread(() -> {
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
try {
semaphore.acquire();
System.out.println(LocalDateTime.now().toLocalTime()+" "+name + "正在工作");
Thread.sleep(100);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}));
// 关闭线程池
es.shutdown();
}
}
输出:
13:45:11.661 pool-1-thread-1正在工作
13:45:12.662 pool-1-thread-2正在工作
13:45:12.763 pool-1-thread-1正在工作
13:45:13.763 pool-1-thread-2正在工作
13:45:13.863 pool-1-thread-1正在工作
13:45:14.864 pool-1-thread-2正在工作
13:45:14.965 pool-1-thread-1正在工作
13:45:15.965 pool-1-thread-2正在工作
13:45:16.066 pool-1-thread-1正在工作
13:45:17.067 pool-1-thread-2正在工作
13:45:17.167 pool-1-thread-1正在工作
13:45:18.168 pool-1-thread-2正在工作
13:45:18.268 pool-1-thread-1正在工作
13:45:19.268 pool-1-thread-2正在工作
13:45:19.368 pool-1-thread-1正在工作
13:45:20.369 pool-1-thread-2正在工作
13:45:20.470 pool-1-thread-1正在工作
13:45:21.470 pool-1-thread-2正在工作
13:45:21.570 pool-1-thread-1正在工作
13:45:22.571 pool-1-thread-2正在工作
从输出日志可以看出,两个线程交替执行,这里的关键是设置了信号量公平锁
本文暂时没有评论,来添加一个吧(●'◡'●)