java多线程

一、什么是多线程

    在计算机体系中,进程是资源分配的基本单位,是程序运行的实体。线程是进程中的一条执行路径,多条线程共享进程内的资源。在一个时间点,一个cpu只能执行一个线程,其是通过时间片轮询机制不停切换线程造成的并发现象。多核cpu才是真正的并发。

 

二、使用多线程的优缺点

    优点:

  1. 多线程可以提高cpu使用率,防止在执行IO等耗时操作时,cpu处于等待状态。
  2. 多线程之间可以方便的共享内存。
  3. 避免阻塞,即异步调用:单个线程中程序是顺序执行的,如果程序过程中发生阻塞,则影响后续操作。而使用多线程则可以实现程序的并行异步操作。使用多线程来实现多任务并发操作比多进程高很多。

    缺点:

  1. 多线程会消耗系统资源,且线程之间的切换也会耗费时间
  2. 多个线程之间共享数据,容易出现死锁的情况。

三、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。

 

   三种方法对比

  1. callable可以返回值和抛出异常,其余与runable一样
  2. runable可以继承其他类,继承Thread则不可以再继承
  3. 继承Thread访问当前线程使用Thread.,runable使用Thread.currentThread().
  4. runable可以使多个线程共享一个target对象(就是线程类对象),适合多线程处理一份资源的场景(多个线程同时处理一个线程类对象)。

 

四、线程中常用的方法

  1.  start() :线程调用该方法将启动线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用CPU资源时,就可以脱离创建它的线程独立开始自己的生命周期了。
  2. run():直接调用run方法在本线程执行,而不开启新线程。跟调用普通方法一样。
  3. new Thread(mt,"线程-A").start(),设置线程名称,也可以Thread对象.setName()设置。可以start之前设置,也可以运行时设置,可以重名,但最好避免。内部设置使用Thread.setName()或者Thread.currentThread().setName()
  4. Thread对象.getName(),获取线程名称。内部使用Thread.getName()或者Thread.currentThread().getName()
  5. Thread对象.isAlive(),线程处于“新建”状态时,线程调用isAlive()方法返回false。在线程的run()方法结束之前,即没有进入死亡状态之前,线程调用isAlive()方法返回true.
  6. Thread对象.join(),使得一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后,才可以继续运行。
  7. Thread.currentThread().sleep(),线程主动休眠,内部执行。
  8. Thread对象.interrupt(),中断线程执行,需要内部配合。中断既触发线程的InterruptedException异常。
  9. Thread.currentThread().isInterrupted():线程内部使用,检查是否被中断,中断返回true,配合while使用,不断检查。
  10. Thread对象.setDaemon(true),设置为守护线程,在start之前。java中线程分为两种,一是普通线程,二是守护线程,java中线程没有父子关系,创建出线程就是平级关系,所有普通线程结束,JVM结束,守护线程在JVM结束时,被动结束。Thread对象.setPriority(),Thread.MIN_PRIORITY,Thread.MAX_PRIORITY,Thread.NORM_PRIORITY。设置优先级。Thread对象.getPriority();获取优先级。主方法优先级为5.
  11. Thread.currentThread().yield(),内部执行,让出执行权,进入排队。

 

五、线程的状态转换

线程状态:线程7种状态,给定时刻只能有一种状态

  1. 初始状态,线程被构建,还没有start
  2. 就绪状态,线程排队等待运行
  3. 运行状态,占用cpu
  4. 锁定阻塞状态,线程进入锁的等待队列。
  5. 等待阻塞状态,线程wait(),自己进入锁的阻塞队列,等待其他线程唤醒,再重新争夺锁。
  6. 其他阻塞状态,sleep阻塞,join阻塞等。可以自动唤醒的阻塞状态
  7. 终止状态,线程执行完毕。

 

六、线程安全与线程同步

   线程安全

    多条线程同时处理一个共享对象时。不管执行次序如何,都能得到期望的结果,叫线程安全。线程安全是针对共享区域的操作而言的。线程的不安全是由于对共享区域的操作不是原子性而引起的。操作过程中被其他线程插入操作,造成的不确定性。

   线程同步

    线程同步是指对线程共有对象或者变量的执行顺序的配合,通常有互斥和次序配合。是维护线程安全的手段。

 

  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():是锁住本类的所有对象,既调用本方法就是同步的

 

  1. synchronized关键字不能继承。
  2. 在定义接口方法时不能使用synchronized关键字。
  3. 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。

 

  2.Lock接口和ReentrantLock实现类

    jdk1.5之前只有synchronized一种锁,之后出现一种并发包,包中有Lock锁和它的一些实现类。其提供的是与synchronized相同的功能,区别是不如synchronized便捷,但是更有可操作性。其实现类有可重入锁,读写锁等,更能更强大。其内部是synchronized

最常用的是ReentrantLock实现类,可重入锁:锁可以多锁几次,解开也需要对应次数的unlock。这个锁默认非公平,可以设置为公平锁(先阻塞的先获得锁)。但是效率要降低百倍。每把锁都只有一个阻塞队列。

基本操作:

  1. void lock() 获取锁,其余线程执行到这会进入阻塞队列。
  2. boolean tryLock() 尝试非阻塞的获取锁,调用该方法立即返回,true表示获取到锁,并进入锁内部。false没有锁,但线程不阻塞。
  3. void lockInterruptibly() throws InterruptedException 可中断获取锁,既使正获取锁的线程释放锁,自己强行获得锁。
  4. boolean tryLock(long time,TimeUnit unit) throws InterruptedException 超时获取锁,以下情况会返回:时间内获取到了锁(进入,返回true),时间内被中断,时间到了没有获取到锁(false,不阻塞)。
  5. void unlock() 释放锁,
  6. 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

 

  1. synchronized获取锁获取不到只能阻塞,Lock可以有多种形式
  2. synchronized是JVM层锁定,在异常情况下,JVM自动释放锁,但Lock是代码层面,必须在finally中释放锁
  3. synchronized容易理解
  4. 在资源竞争激烈的情况下,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 这一步。保证多核心读取一致。

 并发编程中的三个概念:

  1. 原子性,即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
  2. 可见性,可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
  3. 有序性,即程序执行的顺序按照代码的先后顺序执行。一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

    volatile可以防止2,3

多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。 

用final域,有锁保护的域和volatile域可以避免非同步的问题。 

 

  • 使用线程安全的容器对象  

      就是对象的方法是原子性的。只能保证使用对象的方法是原子性,不用加锁,不能保证复合操作中间有没有线程插入,复合操作还是要加锁。

ConcurrentHashMap:线程安全的map,内部是hash表。不允许有null值。具体用法不详。

JDK8中ConcurrentHashMap参考了JDK8 HashMap的实现,采用了数组+链表+红黑树的实现方式来设计,内部大量采用CAS操作

操作:

ConcurrentLinkedQueue:线程安全的非阻塞队列,这是基于链表的无界安全队列。不能存null元素,当多线程要访问一个公共队列时,用这个。

操作:

  1. Queue<String> queue=new ConcurrentLinkedQueue<String>():创建
  2. queue.isEmpty():判断是否为空。
  3. queue.size():返回元素数量,比较耗费时间。
  4. queue.peek():获取头元素,不移除头结点
  5. queue.poll():获取并移除头结点。队列为空返回null
  6. queue.add():尾部添加元素。

   

    LinkedBlockingQueue:可以指定容量的,安全的,可阻塞的,先进先出的队列。队列满时会阻塞到有人出来,队列空时会阻塞到有人进来。有点像信号量。

  1. BlockingQueue<String> queue = new LinkedBlockingQueue<String>(3); 创建
  2. queue.add():尾部添加,满了抛出异常
  3. queue.offer():尾部添加,成功返回true,失败返回false
  4. queue.put():尾部添加,满了阻塞,直到有空间自动唤醒。
  5. queue.poll(time):移除头,如果为空可以等待时间,再拿不到返回null
  6. queue.take():移除头,为空阻塞至有元素加入,自动唤醒
  7. queue.clear():清除所有元素
  8. queue.remove():删除头
  9. 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();

 

  • 原子操作

    多线程情况下,就算对共享变量的一个小操作也是可能被其他线程侵入的。而微小操作不值得用锁,所以用原子类操作最好,保证小操作的原子性。

原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作

即-这几种行为要么同时完成,要么都不完成。

 

七、线程池

  1. 线程池是什么:线程池就是规定了线程个数的有n个线程的池子,用于管理线程但不管同步问题。
  2. 线程池执行过程:线程池执行任务流程:创建线程池,给基本大小,开始没有任何线程,任务过来后,创建线程,执行完线程不销毁,回到线程池,当线程池内线程不到基本大小,过来任务总是创建新线程,知道线程数达到基本大小。达到后,任务再进来就是使用已经创建的线程。当线程都被使用的时候,任务再进来会放到就绪队列,队列有大小,队列满了,再进任务,会创建新线程执行,直到达到设置的线程池最大值。都满了,线程池饱和。会采取饱和策略,有抛异常,有丢掉任务等。而线程池内的线程空闲时,有一个存活时间,时间内没有接到任务,会销毁线程。
  3. 线程池优点:
  • 降低资源消耗:线程已经创建好,避免不停创建和销毁带来的资源消耗
  • 提高线程管理性:线程不能无限创建,可以由线程池统一调配。
  • 提高响应速度:任务直接分配给线程,不用创建线程。

     线程池的使用:

    创建线程池:

      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创建线程,提交指定线程池使用

  1. 首先在主类上注解@EnableAsync,表示开启多线程,这时没有自定义的Executor,所以使用缺省的TaskExecutor
  2. 自定义线程池:主类上可以不注解,表示缺省的为自定义线程池。
  3. @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。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值