/**线程池**/
private static final ExecutorService pool = Executors.newFixedThreadPool(THREAD_SIZE); // THREAD_SIZE为线程数
/**
线程池有多种预设模板,如果不熟悉的话直接使用FixedThreadPool就可以了
根据后面传入的长度, 限定线程数。超出的任务会等待。
**/
具体的大小计算方式:
调用方法获取CPU核心数(含虚拟化的核心):
Runtime.getRuntime().availableProcessors();
CPU密集型:CPU核心数 +1 (如果CPU本身性能不强,建议不要+1,例如双核的CPU)
I/0、网络密集型:CPU核心数 * 2 +1 (网络请求如果很慢,可以再高一点。如果是同一台机的IO话,个人觉得还是算密集型,因为主要瓶颈在IO)
主要介绍两种,一种是不返回的Runnable,另一种是带返回的Future
两种方法对线程的结束均采用倒计锁CountDownLatch
// SIZE为任务的数量
CountDownLatch latch = new CountDownLatch(SIZE);
final CountDownLatch latch = new CountDownLatch(SIZE);
pool.execute(new Runnable() {
@Override
public void run() {
try {
//do
} catch (Exception e) {
// error
} finally {
latch.countDown();
}
}
});
latch.await();
如果直接使用Runnable,传入的参数需要为final.所以可以新建一个Runnable类,这样只需要传入普通的java对象即可
CountDownLatch latch = new CountDownLatch(SIZE);
pool.execute(new SaveWorker(id,latch));
latch.await();
SaveWorker类
private final class SaveTask implements Runnable {
private String id;
private CountDownLatch latch;
public SaveTask(String id, CountDownLatch latch) {
this.id = id;
this.latch = latch;
}
@Override
public void run() {
try {
logger.info("传入的Id为{}", id);
} catch (Exception e) {
logger.error("出现异常:{}",e.getMessage(), e);
} finally {
latch.countDown(); // 计数器减一
}
}
}
注意:
线程中的异常不能再往外面抛出,所以要在线程中进行打印或者处理。
线程间内存有限制,不要传入太大的对象。
对于增删改的操作。要使用线程安全的容器,对象来进行处理。
1、集合要保证线程安全,里面会用到锁,这样CPU在执行到锁相关的操作时就会在“用户态”和“内核态”之间进行切换,由于从用户态要去访问内核态需要做额外的工作,所以就会增加访问所带来的开销;(因为线程、锁等概念和对象都是内核中的指令,OS只是对其进行了封装并向上层程序开放接口)
2、当多线程去竞争锁时,没有竞争到锁使用权的线程就会被阻塞并等待被唤醒然后再去接着竞争知道获取锁的使用权为止。在这个过程中,当线程被阻塞时,就会增加CPU执行上下文切换所带来的开销,CPU的使用率就不高。(因为CPU在不停的轮询检查,被阻塞的线程会被挂起,处于可运行状态的线程会获得CPU的使用权)
可以采用juc包中的Callable接口来解决这个问题。在将任务放入到线程池时,线程池返回一个Future对象,将其保存起来(此过程是同步的,不是异步。所以可以使用线程不安全的类以提高性能),当工作线程执行完成后会将结果保存在Future对象中,然后就可以从其中拿到返回的结果并做后续的处理操作。
如果用Runnable,那么就要用并发工具类中的线程安全类来处理。这里只需要用普通的LinkedList(线程不安全)即可。
// 结果集合
List<Map<String, Object>> mapList = new LinkedList<>();
try {
int totalCount = getCount();//获取总页数的方法;
int pages = (int) Math.ceil(totalCount / (double) pageSize); // 计算出分页数
List<Future<List<Map<String, Object>>>> futures = new ArrayList<>(pages);
CountDownLatch latch = new CountDownLatch(pages);
Future<List<Map<String, Object>>> res = null;
for (int i = 0; i < pages; i++) {
// 返回结果添加到List中
res = EXECUTOR.submit(new GetTask(latch, i));
futures.add(res);
}
latch.await();
// 遍历这个List,添加到我们真正返回的结果集合中。
for (Future<List<Map<String, Object>>> future : futures) {
mapList.addAll(future.get());
}
} catch (Exception e) {
logger.error("多线程-分页获取排除的客户,发生错误:", e);
}
// 实现的类和上面的比较类似,只需要多实现个返回的接口
private final class GetTask implements Callable<List<Map<String, Object>>> {
private CountDownLatch latch;
private int page;
public GetEliminateCustomerTask(CountDownLatch latch, int page) {
this.latch = latch;
this.page = page;
}
@Override
public List<Map<String, Object>> call() throws Exception {
try {
int start = page * pageSize;
return seachByPage(start,pageSize); // 分页获取数据
} catch (Exception e) {
logger.error("获取第".concat(String.valueOf((page+1)).concat("页数据发生错误")), e);
} finally {
latch.countDown();
}
return null;
}
}