一、什么是多线程
在计算机体系中,进程是资源分配的基本单位,是程序运行的实体。线程是进程中的一条执行路径,多条线程共享进程内的资源。在一个时间点,一个cpu只能执行一个线程,其是通过时间片轮询机制不停切换线程造成的并发现象。多核cpu才是真正的并发。
二、使用多线程的优缺点
优点:
- 多线程可以提高cpu使用率,防止在执行IO等耗时操作时,cpu处于等待状态。
- 多线程之间可以方便的共享内存。
- 避免阻塞,即异步调用:单个线程中程序是顺序执行的,如果程序过程中发生阻塞,则影响后续操作。而使用多线程则可以实现程序的并行异步操作。使用多线程来实现多任务并发操作比多进程高很多。
缺点:
- 多线程会消耗系统资源,且线程之间的切换也会耗费时间
- 多个线程之间共享数据,容易出现死锁的情况。
三、java创建线程的三种方法
继承Thread类创建线程
- 定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。
- 创建Thread子类的实例,也就是创建了线程对象
- 启动线程,即调用线程的start()方法
public class MyThread extends Thread{//继承Thread类
public void run(){
//重写run方法
}
}
public class Main {
public static void main(String[] args){
new MyThread().start();//创建并启动线程
}
}
实现Runnable接口创建线程
- 定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体创建
- Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
- 第三部依然是通过调用线程对象的start()方法来启动线程
public class MyThread2 implements Runnable {//实现Runnable接口
public void run(){
//重写run方法
}
}
public class Main {
public static void main(String[] args){
//创建并启动线程
MyThread2 myThread=new MyThread2();
Thread thread=new Thread(myThread);
thread().start();
//或者
new Thread(new MyThread2()).start();
}
}
使用Callable和Future创建线程
- 其主要功能是在线程执行完毕后,可以获取执行结果(阻塞获取),可以抛出异常,以上两种不能。
- 向线程内传递参数是通过线程类的属性和构造方法传递,以上两种一样。
- 创建实现Callable接口的实现类,实现call()方法。返回值类型为Callable的泛型。
- 然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
- 使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
- 使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值,如抛异常则在此方法抛出。
public class MyCallable implements Callable<String> {
private long waitTime;
public MyCallable(int timeInMillis){
this.waitTime=timeInMillis;
}
@Override
public String call() throws Exception {
Thread.sleep(waitTime);
//return the thread name executing this callable task
return Thread.currentThread().getName();
}
}
public static void main(String[] args) throws Exception {
MyCallable call = new MyCallable(1000);
FutureTask<String> task = new FutureTask<>(call);
Thread thread = new Thread(task);
thread.start();
String name = task.get();
}
task的五种方法:
- boolean cancel(boolean mayInterruptIfRunning);
cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示 是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
- boolean isCancelled();
isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
- boolean isDone();
isDone方法表示任务是否已经完成,若任务完成,则返回true;
- V get() throws InterruptedException, ExecutionException;
get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
- V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
三种方法对比
- callable可以返回值和抛出异常,其余与runable一样
- runable可以继承其他类,继承Thread则不可以再继承
- 继承Thread访问当前线程使用Thread.,runable使用Thread.currentThread().
- runable可以使多个线程共享一个target对象(就是线程类对象),适合多线程处理一份资源的场景(多个线程同时处理一个线程类对象)。
四、线程中常用的方法
- start() :线程调用该方法将启动线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用CPU资源时,就可以脱离创建它的线程独立开始自己的生命周期了。
- run():直接调用run方法在本线程执行,而不开启新线程。跟调用普通方法一样。
- new Thread(mt,"线程-A").start(),设置线程名称,也可以Thread对象.setName()设置。可以start之前设置,也可以运行时设置,可以重名,但最好避免。内部设置使用Thread.setName()或者Thread.currentThread().setName()
- Thread对象.getName(),获取线程名称。内部使用Thread.getName()或者Thread.currentThread().getName()
- Thread对象.isAlive(),线程处于“新建”状态时,线程调用isAlive()方法返回false。在线程的run()方法结束之前,即没有进入死亡状态之前,线程调用isAlive()方法返回true.
- Thread对象.join(),使得一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后,才可以继续运行。
- Thread.currentThread().sleep(),线程主动休眠,内部执行。
- Thread对象.interrupt(),中断线程执行,需要内部配合。中断既触发线程的InterruptedException异常。
- Thread.currentThread().isInterrupted():线程内部使用,检查是否被中断,中断返回true,配合while使用,不断检查。
- Thread对象.setDaemon(true),设置为守护线程,在start之前。java中线程分为两种,一是普通线程,二是守护线程,java中线程没有父子关系,创建出线程就是平级关系,所有普通线程结束,JVM结束,守护线程在JVM结束时,被动结束。Thread对象.setPriority(),Thread.MIN_PRIORITY,Thread.MAX_PRIORITY,Thread.NORM_PRIORITY。设置优先级。Thread对象.getPriority();获取优先级。主方法优先级为5.
- Thread.currentThread().yield(),内部执行,让出执行权,进入排队。
五、线程的状态转换
线程状态:线程7种状态,给定时刻只能有一种状态
- 初始状态,线程被构建,还没有start
- 就绪状态,线程排队等待运行
- 运行状态,占用cpu
- 锁定阻塞状态,线程进入锁的等待队列。
- 等待阻塞状态,线程wait(),自己进入锁的阻塞队列,等待其他线程唤醒,再重新争夺锁。
- 其他阻塞状态,sleep阻塞,join阻塞等。可以自动唤醒的阻塞状态
- 终止状态,线程执行完毕。
六、线程安全与线程同步
线程安全
多条线程同时处理一个共享对象时。不管执行次序如何,都能得到期望的结果,叫线程安全。线程安全是针对共享区域的操作而言的。线程的不安全是由于对共享区域的操作不是原子性而引起的。操作过程中被其他线程插入操作,造成的不确定性。
线程同步
线程同步是指对线程共有对象或者变量的执行顺序的配合,通常有互斥和次序配合。是维护线程安全的手段。
1.synchronized和等待/通知机制:
synchronized是java的关键字,是一种同步锁,通常修饰代码块或者方法。
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public void run() {
synchronized(this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public int getCount() {
return count;
}
}
如上例,修饰的代码块可在任意方法中,使用的锁为this(既指本线程对象),只有当两个线程使用同一个线程对象才会启用锁。任意object对象都可作为锁,使用对象作为锁,可以通过构造方法传递对象作为锁,使用多线程同时访问一个代码块,可传递同一个对象。
class AccountOperator implements Runnable{
private Account account;
public AccountOperator(Account account) {
this.account = account;
}
public void run() {
synchronized (account) {
account.deposit(500);
account.withdraw(500);
System.out.println(Thread.currentThread().getName() + ":" + account.getBalance());
}
}
}
当没有确定的锁可用时,可以使用private byte[] lock = new byte[0];作为锁,最经济。普通锁只锁本对象,static的对象作为锁锁全部,只锁能用此对象作为锁的。通常不用static,而用类锁。
class Test implements Runnable
{
private byte[] lock = new byte[0]; // 特殊的instance变量
public void method()
{
synchronized(lock) {
// todo 同步代码块
}
}
public void run() {
}
}
使用类作为锁,锁住全部使用这个类作为锁的代码。不管是本方法还是其他方法。
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public static void method() {
synchronized(SyncThread.class) {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public synchronized void run() {
method();
}
}
每个对象都有一个锁定阻塞队列,被阻塞的线程挂于队列中,当执行线程执行完,则唤醒全部阻塞线程争夺锁。
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
public synchronized void run():是以本对象作为锁
等同于
public void method()
{
synchronized(this) {
// todo
}
}
public synchronized static void method():是锁住本类的所有对象,既调用本方法就是同步的
- synchronized关键字不能继承。
- 在定义接口方法时不能使用synchronized关键字。
- 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
2.Lock接口和ReentrantLock实现类
jdk1.5之前只有synchronized一种锁,之后出现一种并发包,包中有Lock锁和它的一些实现类。其提供的是与synchronized相同的功能,区别是不如synchronized便捷,但是更有可操作性。其实现类有可重入锁,读写锁等,更能更强大。其内部是synchronized
最常用的是ReentrantLock实现类,可重入锁:锁可以多锁几次,解开也需要对应次数的unlock。这个锁默认非公平,可以设置为公平锁(先阻塞的先获得锁)。但是效率要降低百倍。每把锁都只有一个阻塞队列。
基本操作:
- void lock() 获取锁,其余线程执行到这会进入阻塞队列。
- boolean tryLock() 尝试非阻塞的获取锁,调用该方法立即返回,true表示获取到锁,并进入锁内部。false没有锁,但线程不阻塞。
- void lockInterruptibly() throws InterruptedException 可中断获取锁,既使正获取锁的线程释放锁,自己强行获得锁。
- boolean tryLock(long time,TimeUnit unit) throws InterruptedException 超时获取锁,以下情况会返回:时间内获取到了锁(进入,返回true),时间内被中断,时间到了没有获取到锁(false,不阻塞)。
- void unlock() 释放锁,
- lock里的wait(),notify()方法:
private static Lock lock = new ReentrantLock();//先创建一把锁,每个线程都用这把锁
private static Condition A = lock.newCondition();//在线程外创建锁的一个阻塞队列。
在锁内部,A.await()是将线程加入A阻塞队列,并释放锁
A.signal()是唤醒A阻塞队列的一个线程,进入争夺锁的队列。从await()处开始执行。
A.signalAll()唤醒所有线程
下例为A,B,C交替打印10次
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ABC {
private static Lock lock = new ReentrantLock();//创建一个共同锁
private static Condition A = lock.newCondition();//创建三个等待队列
private static Condition B = lock.newCondition();
private static Condition C = lock.newCondition();
private static int count = 0;
//三个线程的策略是,A先阻塞自己,B也阻塞自己,C先叫醒A,再阻塞自己,A再叫醒B,执行一遍逻辑, 再阻塞自己。B叫醒C
static class ThreadA implements Runnable{
@Override
public void run() {
lock.lock();
try{
for(int i=0;i<10;i++){
if(count%3==0){//确保第一个执行的是A
System.out.println("A");
count++;//确保第二个执行的是B
A.await();//把A,B全部放进阻塞队列,确保除了在锁中的只有一个线程在争夺 锁
B.signalAll();
}else{
i--;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
static class ThreadB implements Runnable{
@Override
public void run() {
lock.lock();
try{
for(int i=0;i<10;i++){
if(count%3==1){
System.out.println("B");
count++;
B.await();
C.signalAll();
}else{
i--;//如果B挣到第一个执行,要把多加的减回去。等于没执行
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
static class ThreadC implements Runnable{
@Override
public void run(){
lock.lock();
try{
for(int i=0;i<10;i++){
if(count%3==2){
System.out.println("C");
count++;
A.signalAll();//先释放A让A争夺锁,此时C还在锁中,不释放,A拿不到。
C.await();
}else{
i--;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
public static void main(String[] args){
new Thread(new ThreadA(),"A").start();
new Thread(new ThreadB(),"B").start();
new Thread(new ThreadC(),"C").start();
}
}
Lock 与synchronized
- synchronized获取锁获取不到只能阻塞,Lock可以有多种形式
- synchronized是JVM层锁定,在异常情况下,JVM自动释放锁,但Lock是代码层面,必须在finally中释放锁
- synchronized容易理解
- 在资源竞争激烈的情况下,synchronized耗时急剧上升,Lock则稳定
| 1 | 10 | 50 | 100 | 500 | 1000 | 5000 |
synchronized | 542 | 4894 | 4667 | 4700 | 5151 | 5156 | 5178 |
lock | 838 | 1211 | 821 | 847 | 851 | 1211 | 1241 |
建议普通情况下用synchronized,需要优化用Lock
- volatile域
用于修饰非final域。
volatile修饰的变量不允许线程内部缓存和重排序。既每个线程对这个变量的修改都是对其他线程可见的。但其不保证原子性。其是一种弱的同步机制,因为不用加锁,其效率与非同步代码差不多。
当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。对缓存的变量操作,从cpu退出时,再把数据刷新回内存(单核没问题,多核同时拷贝一个对象操作,返回就有问题)。当没有synchronized同步机制的时候,两个核心同时执行两条线程,线程更改共享变量,同时取未更改内存数据,返回更改后的。则一条线程的工作就会被覆盖。
而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。保证多核心读取一致。
并发编程中的三个概念:
- 原子性,即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
- 可见性,可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
- 有序性,即程序执行的顺序按照代码的先后顺序执行。一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
volatile可以防止2,3
多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。
用final域,有锁保护的域和volatile域可以避免非同步的问题。
-
使用线程安全的容器对象
就是对象的方法是原子性的。只能保证使用对象的方法是原子性,不用加锁,不能保证复合操作中间有没有线程插入,复合操作还是要加锁。
ConcurrentHashMap:线程安全的map,内部是hash表。不允许有null值。具体用法不详。
JDK8中ConcurrentHashMap参考了JDK8 HashMap的实现,采用了数组+链表+红黑树的实现方式来设计,内部大量采用CAS操作
操作:
ConcurrentLinkedQueue:线程安全的非阻塞队列,这是基于链表的无界安全队列。不能存null元素,当多线程要访问一个公共队列时,用这个。
操作:
- Queue<String> queue=new ConcurrentLinkedQueue<String>():创建
- queue.isEmpty():判断是否为空。
- queue.size():返回元素数量,比较耗费时间。
- queue.peek():获取头元素,不移除头结点
- queue.poll():获取并移除头结点。队列为空返回null
- queue.add():尾部添加元素。
LinkedBlockingQueue:可以指定容量的,安全的,可阻塞的,先进先出的队列。队列满时会阻塞到有人出来,队列空时会阻塞到有人进来。有点像信号量。
- BlockingQueue<String> queue = new LinkedBlockingQueue<String>(3); 创建
- queue.add():尾部添加,满了抛出异常
- queue.offer():尾部添加,成功返回true,失败返回false
- queue.put():尾部添加,满了阻塞,直到有空间自动唤醒。
- queue.poll(time):移除头,如果为空可以等待时间,再拿不到返回null
- queue.take():移除头,为空阻塞至有元素加入,自动唤醒
- queue.clear():清除所有元素
- queue.remove():删除头
- queue.peek():拿头不删除,没有返回null
生产者/消费者模型
package cn.itcast.java.concurrency;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 多线程模拟实现生产者/消费者模型
*/
public class BlockingQueueTest {
/**
*
* 定义装苹果的篮子
*
*/
public class Basket {
// 篮子,能够容纳3个苹果
BlockingQueue<String> basket = new LinkedBlockingQueue<String>(3);
// 生产苹果,放入篮子
public void produce() throws InterruptedException {
// put方法放入一个苹果,若basket满了,等到basket有位置
basket.put("An apple");
}
// 消费苹果,从篮子中取走
public String consume() throws InterruptedException {
// take方法取出一个苹果,若basket为空,等到basket有苹果为止(获取并移除此队列的头部)
return basket.take();
}
}
// 定义苹果生产者
class Producer implements Runnable {
private String instance;
private Basket basket;
public Producer(String instance, Basket basket) {
this.instance = instance;
this.basket = basket;
}
public void run() {
try {
while (true) {
// 生产苹果
System.out.println("生产者准备生产苹果:" + instance);
basket.produce();
System.out.println("!生产者生产苹果完毕:" + instance);
// 休眠300ms
Thread.sleep(300);
}
} catch (InterruptedException ex) {
System.out.println("Producer Interrupted");
}
}
}
// 定义苹果消费者
class Consumer implements Runnable {
private String instance;
private Basket basket;
public Consumer(String instance, Basket basket) {
this.instance = instance;
this.basket = basket;
}
public void run() {
try {
while (true) {
// 消费苹果
System.out.println("消费者准备消费苹果:" + instance);
System.out.println(basket.consume());
System.out.println("!消费者消费苹果完毕:" + instance);
// 休眠1000ms
Thread.sleep(1000);
}
} catch (InterruptedException ex) {
System.out.println("Consumer Interrupted");
}
}
}
public static void main(String[] args) {
BlockingQueueTest test = new BlockingQueueTest();
// 建立一个装苹果的篮子
Basket basket = test.new Basket();
ExecutorService service = Executors.newCachedThreadPool();
Producer producer = test.new Producer("生产者001", basket);
Producer producer2 = test.new Producer("生产者002", basket);
Consumer consumer = test.new Consumer("消费者001", basket);
service.submit(producer);
service.submit(producer2);
service.submit(consumer);
// 程序运行5s后,所有任务停止
// try {
// Thread.sleep(1000 * 5);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// service.shutdownNow();
}
}
- 信号量
可以创建有n个信号量的信号池,acquire() 获得一个, release() 释放一个,当信号量没有时,线程阻塞在acquire上,有的时候唤醒。当信号量满的时候,线程阻塞在release上,有空位则唤醒。有点像生产者,消费者模式。
private static Semaphore A = new Semaphore(1);
A.acquire();
A.release();
- 原子操作
多线程情况下,就算对共享变量的一个小操作也是可能被其他线程侵入的。而微小操作不值得用锁,所以用原子类操作最好,保证小操作的原子性。
原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作
即-这几种行为要么同时完成,要么都不完成。
七、线程池
- 线程池是什么:线程池就是规定了线程个数的有n个线程的池子,用于管理线程但不管同步问题。
- 线程池执行过程:线程池执行任务流程:创建线程池,给基本大小,开始没有任何线程,任务过来后,创建线程,执行完线程不销毁,回到线程池,当线程池内线程不到基本大小,过来任务总是创建新线程,知道线程数达到基本大小。达到后,任务再进来就是使用已经创建的线程。当线程都被使用的时候,任务再进来会放到就绪队列,队列有大小,队列满了,再进任务,会创建新线程执行,直到达到设置的线程池最大值。都满了,线程池饱和。会采取饱和策略,有抛异常,有丢掉任务等。而线程池内的线程空闲时,有一个存活时间,时间内没有接到任务,会销毁线程。
- 线程池优点:
- 降低资源消耗:线程已经创建好,避免不停创建和销毁带来的资源消耗
- 提高线程管理性:线程不能无限创建,可以由线程池统一调配。
- 提高响应速度:任务直接分配给线程,不用创建线程。
线程池的使用:
创建线程池:
ThreadPoolExecutor threadsPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue)
分别为基本线程数,最大线程数,存活时间,时间单位,任务队列。
任务队列:BlockingQueue<Runnable> workQueue=new可以提供的任务队列有:
ArrayBlockingQueue:基于数组的有界先进先出队列
LinkedBlockingQueue:基于链表的有界先进先出队列,效率高于上一个
SynchronousQueue:空队列,不能存东西,任务提交到这就阻塞等待有线程
PriorityBlockingQueue:无限的优先级队列。
提交任务:threadPool.execute(new Runnable(){重写run方法}),无返回值的提交
Future<Object> future = threadPool.submit (new Runnable(){重写run方法} ).返回值是一个对象,通过对象可以判断任务是否执行成功,future.get()会阻塞当前线程直至任务完成。
关闭线程池:threadPool.shutdown().会先关闭没执行任务的线程,有任务的执行完再关闭。调用threadPool.isShutdown().显示true。threadPool.isTerminaed(),在全部关闭后显示true。
threadPool.shutdownNow().正在执行的任务会暂停,然后全部关闭。
使用Excutors框架创建线程池,可以创建4种基本线程池,其内部是对ThreadPoolExecutor的配置
- ExecutorService service = Excutors.newCachedThreadPool() 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。这是一个无线线程池,任务多的时候可以无限创建线程,少的时候可以销毁线程 service. submit(producer)提交任务,
- ExecutorService service = Excutors. newScheduledThreadPool(5): 创建一个定长线程池,支持定时及周期性任务执行。初始化时确定大小,提交线程
- ExecutorService service = Excutors.newFixedThreadPool(5): 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。可以控制最大并发数,在初始化时确立,超出的在队列等待
- ExecutorService service = Excutors.newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
八、spring中的多线程
1. 创建线程池,交给spring管理其生命周期,自定义线程使用此线程池
第一种方式:创建一个私有,静态线程池,只供本类方法使用。
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, corePoolSize+1, 10l, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(1000));
第二种方式:注册到spring中
@Configuration
public class GlobalConfig {
/**
* 默认线程池线程池
*
* @return Executor
*/
@Bean
public ThreadPoolTaskExecutor defaultThreadPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程数目
executor.setCorePoolSize(16);
//指定最大线程数
executor.setMaxPoolSize(64);
//队列中最大的数目
executor.setQueueCapacity(16);
//线程名称前缀
executor.setThreadNamePrefix("defaultThreadPool_");
//rejection-policy:当pool已经达到max size的时候,如何处理新任务
//CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
//对拒绝task的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//线程空闲后的最大存活时间
executor.setKeepAliveSeconds(60);
//加载
executor.initialize();
return executor;
}
}
使用:
@Resource(name = "defaultThreadPool")
private ThreadPoolTaskExecutor executor;
Future<?> future = executor.submit(() -> {
//发送短信
mobileMessageFacade.sendCustomerMessage(response.getMobile(),
msgConfigById.getContent());
});
2.使用spring封装线程池,@Async创建线程,提交指定线程池使用
- 首先在主类上注解@EnableAsync,表示开启多线程,这时没有自定义的Executor,所以使用缺省的TaskExecutor
- 自定义线程池:主类上可以不注解,表示缺省的为自定义线程池。
-
@Configuration @EnableAsync public class ExecutorConfig { /** Set the ThreadPoolExecutor's core pool size. */ private int corePoolSize = 10; /** Set the ThreadPoolExecutor's maximum pool size. */ private int maxPoolSize = 200; /** Set the capacity for the ThreadPoolExecutor's BlockingQueue. */ private int queueCapacity = 10; @Bean public Executor mySimpleAsync() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(corePoolSize); executor.setMaxPoolSize(maxPoolSize); executor.setQueueCapacity(queueCapacity); executor.setThreadNamePrefix("MySimpleExecutor-"); executor.initialize(); return executor; } @Bean public Executor myAsync() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(corePoolSize); executor.setMaxPoolSize(maxPoolSize); executor.setQueueCapacity(queueCapacity); executor.setThreadNamePrefix("MyExecutor-"); // rejection-policy:当pool已经达到max size的时候,如何处理新任务 // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } } 使用@Async定义实现类,开启线程操作。线程方法有两种,一种是void,另一种返回Future,()中string为生产线程池的方法。 @Component public class AsyncTask { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); @Async("mySimpleAsync") public void doTask1() { logger.info("Task1 started."); long start = System.currentTimeMillis(); Thread.sleep(5000); long end = System.currentTimeMillis(); logger.info("Task1 finished, time elapsed: {} ms.", end-start); } @Async("myAsync") public Future<String> doTask2() throws InterruptedException{ logger.info("Task2 started."); long start = System.currentTimeMillis(); Thread.sleep(3000); long end = System.currentTimeMillis(); logger.info("Task2 finished, time elapsed: {} ms.", end-start); return new AsyncResult<>("Task2 accomplished!"); } }
返回值方法:
它声明这样的五个方法:
- cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
- isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
- isDone方法表示任务是否已经完成,若任务完成,则返回true;
- get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
- get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。