Thread多线程速查手册

toc

常用概念

线程状态
新建状态、就绪状态、运行状态、阻塞状态及死亡状态。

  • 新建状态:
    当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码
  • 就绪状态:
    一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
    处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。
  • 运行状态:
    当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.
  • 阻塞状态:
    线程运行过程中,可能由于各种原因进入阻塞状态:
    1 线程通过调用sleep方法进入睡眠状态;
    2 线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
    3 线程试图得到一个锁,而该锁正被其他线程持有;
    4 线程在等待某个触发条件;
  • 死亡状态:
    有两个原因会导致线程死亡:
    1、run方法正常退出而自然死亡,
    2、一个未捕获的异常终止了run方法而使线程猝死。
    为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.

原子性
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。这2个操作必须要具备原子性才能保证不出现一些意外的问题。
我们操作数据也是如此,比如i = i+1;其中就包括,读取i的值,计算i,写入i。这行代码在Java中是不具备原子性的,则多线程运行肯定会出问题,所以也需要我们使用同步和lock这些东西来确保这个特性了。
原子性其实就是保证数据一致、线程安全一部分。

可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
保证可见性的方法
volatile,synchronized(unlock之前,写变量值回主存),final

有序性
程序执行的顺序按照代码的先后顺序执行。一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。如下:
int a = 10; //语句1
int r = 2; //语句2
a = a + 3; //语句3
r = a*a; //语句4
则因为重排序,他还可能执行顺序为 2-1-3-4,1-3-2-4
但绝不可能 2-1-4-3,因为这打破了依赖关系。
显然重排序对单线程运行是不会有任何问题,而多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了。

内存模型
共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:
线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

线程安全

介绍
当多个线程访问某一个类的对象和方法时,这个类始终能表现出正确的行为就是线程安全的

优先级
现代操作系统基本采用时分的形式调度运行的线程,线程分配得到的时间片的多少决定了线程使用处理器资源的多少,也对应了线程优先级这个概念。在JAVA线程中,通过一个int priority来控制优先级,范围为1-10,其中10最高,默认值为5。下面是源码(基于1.8)中关于priority的一些量和方法。

什么是线程安全?
当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。
对共享变量进行写入时,必须保证是原子操作,原子操作是指不能被中断的一个或一系列操作。

线程安全的解决办法?
如何解决多线程之间线程安全问题?
使用多线程之间同步或使用锁(lock)。

为什么使用线程同步或使用锁能解决线程安全问题呢?
将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。

什么是多线程之间同步?
当多个线程共享同一个资源,不会受到其他线程的干扰。

停止线程

停止线程思路

  1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
  2. 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
  3. 使用interrupt方法中断线程。 线程在阻塞状态。
class StopThread implements Runnable {
    private boolean flag = true;

    @Override
    public synchronized void run() {
        while (flag) {
            try {
                wait();
            } catch (Exception e) {
                //e.printStackTrace();
                stopThread();
            }
            System.out.println("thread run..");
        }
    }

    public void stopThread() {
        flag = false;
    }
}

public class StopThreadDemo {
    public static void main(String[] args) {
        StopThread stopThread1 = new StopThread();
        Thread thread1 = new Thread(stopThread1);
        Thread thread2 = new Thread(stopThread1);
        thread1.start();
        thread2.start();
        int i = 0;
        while (true) {
            System.out.println("thread main..");
            if (i == 300) {
                // stopThread1.stopThread();
                thread1.interrupt();
                thread2.interrupt();
                break;
            }
            i++;
        }
    }
}

守护线程

Java中有两种线程,一种是用户线程,另一种是守护线程。
当进程不存在或主线程停止,守护线程也会被停止。
使用setDaemon(true)方法设置为守护线程。
守护线程是为其他线程服务的线程
所有的非守护线程都执行完毕后,虚拟机退出
守护线程不能持有资源

多线程通讯

什么是多线程之间通讯?
多线程之间通讯,其实就是多个线程在操作同一个资源,但是操作的动作不同。比如一个线程写,一个线程读。通讯可能会发生数据错乱的问题,通过加synchronized方式解决问题,协调顺序的问题通过wait/notify解决。
1

  • wait/notify用于多线程协调运行:
    在synchronized内部可以使用wait使线程进入等待状态
    必须在已获得的锁对象上调用wait方法
    在synchronized内部可以调用notify/notifyAll()唤醒其他等待线程
    必须在已获得的锁对象上调用notify/notifyAll()方法
    wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态。
    这三个方法最终调用的都是jvm级的native方法。随着jvm运行平台的不同可能有些许差异。
    如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
    如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。
    如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。

wait与sleep区别?
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
获取对象锁进入运行状态。

join()方法作用
join作用是让其他线程变为等待

class JoinThread implements Runnable {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---i:" + i);
        }
    }
}

public class JoinThreadDemo {
    public static void main(String[] args) {
        JoinThread joinThread = new JoinThread();
        Thread t1 = new Thread(joinThread);
        Thread t2 = new Thread(joinThread);
        t1.start();
        t2.start();
        try {
       //其他线程变为等待状态,等t1线程执行完成之后才能执行join方法。
            t1.join();
        } catch (Exception e) {

        }

yield
Thread.yield()方法的作用:暂停当前正在执行的线程,并执行其他线程。(可能没有效果)
yield()让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的机会。因此,使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。
结论:大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。

Synchornized

当多个线程访问某一个类的对象和方法时,这个类始终能表现出正确的行为就是线程安全的
对于静态同步方法,锁是当前类的Class对象
对于同步方法块,锁是Sychronized括号里配置的对象。
synchronized 修饰方法使用锁是当前this锁。
synchronized 修饰静态方法使用锁是当前类的字节码文件
指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。

什么是同步代码块
答:就是将可能会发生线程安全问题的代码,给包括起来。
synchronized(同一个数据){
可能会发生线程冲突问题
}
什么是同步函数?
在方法上修饰synchronized 称为同步函数,加锁对象是this.

悲观锁

主要是表锁,行锁还有间隙锁,叶锁,读锁,因为这些锁在被触发的时候势必引起线程阻塞,所以叫悲观

乐观锁

只是在innodb引擎下存在,mvcc是为了满足事务的隔离,通过版本号的方式,避免同一数据不同事务间的竞争,所说的乐观锁只在事务级别为读未提交读提交,才会生效

共享锁/独占锁

共享锁是为了提高程序的效率,举个例子数据的操作有读写之分,对于写的操作加锁,保证数据正确性,而对于读的操作如果不加锁,在写读操作同时进行时,读的数据有可能不是最新数据,如果对读操作加独占锁,面对读多写少的程序肯定效率很低,所有就出现了共享锁,对于读的的操作就使用共享的概念,但是对于写的操作则是互斥的,保证了读写的数据操作都一致,在java中上述的锁叫读写锁

读写锁的机制

在java中读写锁(ReadWritelock)的机制是基于AQS的一种实现,保证读读共享,读写互斥,写写互斥,如果要说机制的话,还要从AQS说起,这是java实现的一种锁机制,互斥锁,读者写锁,条件产量,信号量,栅栏的都是它的衍生物,主要工作基于CHL队列,voliate关键字修饰的状态符stat,线程去修改状态符成功了就是获取成功,失败了就进队列等待,等待唤醒,AQS中还有很重要的一个概念是自旋,在等待唤醒的时候,很多时候会使用自旋(while(!cas()))的方式,不停的尝试获取锁,直到被其他线程获取成功

死锁

死锁的形成条件

两个线程各自持有不同的锁
两个线程格子视图获取对方已持有的锁
双方无限等待导致死锁
线程1
public void add (int m){
 synchronized(lockA){ //获取lockA
  this.value +=m;
  synchronized(lockB){//等待lockB
  this.another +=m;
  }     
 }
}
线程2
public void add (int m){
 synchronized(lockB){ //获取lockB
  this.value +=m;
  synchronized(lockB){//等待lockA
  this.another +=m;
  }     
 }
}

如何避免
多线程获取锁的顺序要一致

Lock

介绍
在 jdk1.5 之后,并发包中新增了 Lock 接口(以及相关实现类)用来实现锁功能,Lock 接口提供了与 synchronized 关键字类似的同步功能,但需要在使用时手动获取锁和释放锁。
使用

Lock lock = new ReentrantLock();
lock.lock();
try{
//可能会出现线程安全的操作
}finally{
//一定在finally中释放锁
//也不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常
  lock.ublock();
}

ReentrantLock

ReentrantLock是一个可重入的互斥锁,ReentrantLock由最近成功获取锁,还没有释放的线程所拥有,当锁被另一个线程拥有时,调用lock的线程可以成功获取锁。如果锁已经被当前线程拥有,当前线程会立即返回.
Lock 接口与 synchronized 关键字的区别
Lock 接口可以尝试非阻塞地获取锁 当前线程尝试获取锁。如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁。
Lock 接口能被中断地获取锁 与 synchronized 不同,获取到锁的线程能够响应中断,当获取到的锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。
Lock 接口在指定的截止时间之前获取锁,如果截止时间到了依旧无法获取锁,则返回。

Condition

Condition用法
Condition的功能类似于在传统的线程技术中的,Object.wait()和Object.notify()的功能,
Condition condition = lock.newCondition();
res. condition.await(); 类似wait
res. Condition. Signal() 类似notify
Signalall notifyALL

CountDownLatch

介绍

CountDownLatch工作原理相对简单,可以简单看成一个倒计数器,在构造方法中指定初始值,
每次调用countDown()方法时将计数器减1,而await()会等待计数器变为0。
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        /*
        用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,
        所以在当前计数到达零之前,await 方法会一直受阻塞。
         */
        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {
                    test(threadNum);
                } catch (Exception e) {
                    log.error("exception", e);
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        log.info("finish");
        exec.shutdown();
    }

CyclicBarrier

介绍

CyclicBarrier可以译为循环屏障,也有类似的功能。CyclicBarrier可以在构造时指定需要在屏障前执行await的个数,所有对await的调用都会等待,直到调用await的次数达到预定指,所有等待都会立即被唤醒。
从使用场景上来说,CyclicBarrier是让多个线程互相等待某一事件的发生,然后同时被唤醒。
而上文讲的CountDownLatch是让某一线程等待多个线程的状态,然后该线程被唤醒。

  int totalThread = 5;
        CyclicBarrier barrier = new CyclicBarrier(totalThread);

        for (int i = 0; i < totalThread; i++) {
            String threadName = "Thread " + i;
            new Thread(() -> {
                System.out.println(String.format("%s\t%s %s", new Date(), threadName, " is waiting"));
                try {
                    barrier.await();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                System.out.println(String.format("%s\t%s %s", new Date(), threadName, "ended"));
            }).start();
        }

信号量Semaphore

介绍

信号量维护一个许可集,可通过acquire()获取许可(若无可用许可则阻塞),通过release()释放许可,
从而可能唤醒一个阻塞等待许可的线程

    exec.execute(() -> {
                try {
                    semaphore.acquire(); // 获取一个许可
                    test(threadNum);
                    semaphore.release(); // 释放一个许可
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });

ThreadLocal

介绍

ThreadLocal
ThreadLocal提高一个线程的局部变量,访问某个线程拥有自己局部变量。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal的接口方法
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
•void set(Object value)设置当前线程的线程局部变量的值。
•public Object get()该方法返回当前线程所对应的线程局部变量。
•public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
•protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

高并发

高并发修改同一行数据的问题

解决方式有两种
第一种在不是分布式的情况下用JVM提供的锁来解决
synchronized
lock
如:
try{
 lock.lock();
 SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 return simpt.format(new Date())+"_"+ ++count;
}finally{
 lock.unlock();
}
第二种分布式锁
1数据库锁性能差
2redis可能发生死锁
3zookper(基于异常,基于相互监听)

线程安全与不安全的类

不安全的

SimpleDateFormat
解决方案
1只在需要的时候创建新实例,不用static修饰,不过也加重了创建对象的负担,会频繁地创建和销毁对象,效率较低。
2简单粗暴,synchronized往上一套也可以解决线程安全问题,缺点自然就是并发量大的时候会对性能有影响,线程阻塞。
3ThreadLocal可以确保每个线程都可以得到单独的一个SimpleDateFormat的对象,那么自然也就不存在竞争问题了。

spring与多线程

多个请求---》 tomcat(线程池处理,多个线程)---》web服务--》controller
spring mvc Controller不是线程安全的,所以在controller中不能存放实例变量进行写操作。
解决办法
1:ThreadLocal
2:更改springmvc的配置,每次生成新的controller实例
即使是一个web服务也要考虑线程安全问题,事务控制交给数据库和默认配置,不要再方法中使用线程不安全的类,使用的话也每次都new.当项目存在与多个服务器时,有写操作就要考虑分布式锁

线程池

介绍

线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。

线程池的优点

重用线程池中的线程,减少因对象创建,销毁所带来的性能开销;
能有效的控制线程的最大并发数,提高系统资源利用率,同时避免过多的资源竞争,避免堵塞;
能够多线程进行简单的管理,使线程的使用简单、高效。

线程池工作过程

1)线程池判断核心线程池里的线程是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务,如果核心线程池里的线程都在执行任务,则进入下一个流程。
2)线程池判断工作队列是否已满,如果工作队列已满,则将新提交的任务存储在这个工作队列里如果工作队列满了,则进入下一个流程。
3) 线程池判断线程池的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务,如果满了,则交给饱和策略来处理这个任务。
假如10个人去银行取钱,此时有两个办公窗口,最大窗口为4,第一个人,第二个人来就直接处理,2个窗口就是核心线程数,还有2个空置窗口,即最大线程数为4,假如来了更多人取钱,此时银行会让这些人等待,等待的座位只有10个,即此时如果阻塞队列满了,会激活空置窗口,即查找空闲线程活激活线程来处理任务。如果队列和窗口都满了,这时候就会激活饱和策略处理。
RejectedExecutionHandle(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。
AbortPolicy: 直接抛出异常(默认处理方式)
CallerRunsPolicy: 只用调用者所在线程来执行任务
DiscardOldestPolicy: 丢弃队列里的最近一个任务,并执行当前任务;
DiscardPolicy: 不处理,丢弃掉

常用方法

构造方法
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
参数详解:
corePoolSize:线程池中的核心线程数
maximumPoolSize:线程池最大大小
keepAliveTime:线程存活保持时间
threadFactory:线程工厂,主要用来创建线程,不能为空,默认为DefaultThreadFactory类
workQueue:是一个阻塞队列,用来存储等待执行的任务
1、ArrayBlockingQueue,一个基于数组结构的有界阻塞队列,按照 FIFO 原则对任务进行排序。
2、LinkedBlockingQueue,一个基于链表结构的阻塞队列,同样按照 FIFO 原则对任务排序。
(常用于生产者/消费者模式中)
3、SynchronousQueue,一个不存储元素的阻塞队列,每个插入操作必须等待另一个线程调用移除操作,
否则插入操作将会阻塞。
4、PriorityBlockingQueue,一个具有优先级的无限阻塞队列。
ThreadPoolExecutor的状态变量
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
RUNNING 111 表示正在运行
SHUTDOWN 000 表示拒绝接收新的任务
STOP 001 表示拒绝接收新的任务并且不再处理任务队列中剩余的任务,并且中断正在执行的任务。
TIDYING 010 表示所有线程已停止,准备执行terminated()方法。
TERMINATED 011 表示已执行完terminated()方法。
提交任务
可以使用 execute() 与 submit() 方法来向线程池中提交任务。
public void execute(Runnable command);
execute() 方法用来提交不需要返回值的任务,无需判断任务是否被线程池执行成功。
其接受参数是一个 Runnable 实例。

public <T> Future<T> submit(Runnable task, T result);
public <T> Future<T> submit(Callable<T> task);
public Future<?> submit(Runnable task);

submit() 方法用于提交需要返回值的任务。线程池会返回一个 Future 对象,通过其来判断任务是否执行成功。
线程池关闭
shutdown:将线程池状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
shutdownNow:将线程池的状态设置成STOP状态,然后中断所有任务(包括正在执行的)的线程,并返回等待执行任务的列表。

基本使用

Executors类提供了4种不同的线程池

newCachedThreadPool
newFixedThreadPool
newScheduledThreadPool
newSingleThreadExecutor
newFixedThreadPool

newFixedThreadPool创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,对于超出的线程会在LinkedBlockingQueue队列中等待。该线程池的线程会维持在指定线程数,不会进行回收。可控制线程最大并发数。

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

示例:
  ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 9; i++) {
            final int index = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    log.info("task:{}", index);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
        executorService.shutdown();
    }
11:58:31.403 [pool-1-thread-1] INFO thread.ThreadSchool.threadPool.ThreadPoolExample2 - task:0
11:58:31.403 [pool-1-thread-2] INFO thread.ThreadSchool.threadPool.ThreadPoolExample2 - task:1
11:58:31.403 [pool-1-thread-3] INFO thread.ThreadSchool.threadPool.ThreadPoolExample2 - task:2
-------------
11:58:32.417 [pool-1-thread-1] INFO thread.ThreadSchool.threadPool.ThreadPoolExample2 - task:3
11:58:32.417 [pool-1-thread-3] INFO thread.ThreadSchool.threadPool.ThreadPoolExample2 - task:4
11:58:32.417 [pool-1-thread-2] INFO thread.ThreadSchool.threadPool.ThreadPoolExample2 - task:5
-------------
11:58:33.417 [pool-1-thread-3] INFO thread.ThreadSchool.threadPool.ThreadPoolExample2 - task:6
11:58:33.417 [pool-1-thread-1] INFO thread.ThreadSchool.threadPool.ThreadPoolExample2 - task:7
11:58:33.417 [pool-1-thread-2] INFO thread.ThreadSchool.threadPool.ThreadPoolExample2 - task:8
newCachedThreadPool

创建一个可缓存的无界线程池,该方法无参数。当线程池中的线程空闲时间超过60s则会自动回收该线程,当任务超过线程池的线程数则创建新线程。线程池的大小上限为Integer.MAX_VALUE,可看做是无限大。有空闲线程则复用空闲线程,若无空闲线程则新建线程,一定程序减少频繁创建/销毁线程,减少系统开销。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
示例:
ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 20; i++) {
            final int index = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    log.info("task:{}",Thread.currentThread().getName()+"---"+index);
                }
            });
             /*try { 
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            //如果把注释放开会复用空闲线程
            //task:pool-1-thread-1---0
            //task:pool-1-thread-1---1
            //task:pool-1-thread-1---2
            //task:pool-1-thread-1---3
            //task:pool-1-thread-1---4
        }
        executorService.shutdown();
    }
task:pool-1-thread-1---0
task:pool-1-thread-5---4
task:pool-1-thread-3---2
task:pool-1-thread-4---3
task:pool-1-thread-2---1
newSingleThreadExecutor

有且仅有一个工作线程执行任务,所有任务按照指定顺序执行,即遵循队列的入队出队规则,所有任务都保存队列LinkedBlockingQueue中,等待唯一的单线程来执行任务,并保证所有任务按照指定顺序(FIFO或优先级)执行。

public static ExecutorService newSingleThreadExecutor() {
    //线程池中只有一个线程进行任务执行,其他的都放入阻塞队列
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
示例:
 ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            final int index = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    log.info("task:{}", Thread.currentThread().getName()+"-----"+index);
                }
            });
        }
    }
    task:pool-1-thread-1-----0
    task:pool-1-thread-1-----1
    task:pool-1-thread-1-----2
    task:pool-1-thread-1-----3
    task:pool-1-thread-1-----4
newScheduledThreadPool

可定时执行或周期执行任务的线程池,该方法可指定线程池的核心线程个数。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
示例:
  ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
        executorService.schedule(new Runnable() {
            @Override
            public void run() {
                log.warn("schedule run");
            }
        }, 3, TimeUnit.SECONDS);//延迟3s
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                log.warn("schedule run");
            }
        }, 1, 3, TimeUnit.SECONDS);//1s后,每3s一次地周期性执行任务
        executorService.shutdown();

阻塞队列

介绍
它提供了一种线程安全的队列访问方式。所谓的阻塞,意思就是,当遇到出队列的请求时它会先阻塞,直到队列非空的时候再出队列;当遇到入队列的请求时,它也会阻塞,直到队列非满的时候才入队列。
队列是一种数据结构,它有两个基本操作:在队列尾部加入一个元素,从队列头部移除一个元素。阻塞队里与普通的队列的区别在于,普通队列不会对当前线程产生阻塞,在面对类似消费者-生产者模型时,就必须额外的实现同步策略以及线程间唤醒策略。使用阻塞队列,就会对当前线程产生阻塞,当队列是空时,从队列中获取元素的操作将会被阻塞,当队列是满时,往队列里添加元素的操作也会被阻塞。Java 的阻塞队列自带了阻塞特性,不再需要显式的同步。
ArrayBlockingQueue
实现了BlockingQueue接口,基于数组实现的阻塞队列,在创建ArrayBlockingQueue对象时必须指定其容量大小,还可以指定访问策略,默认情况下为非公平的,即不保证等待时间最长的线程最优先能够访问队列。其所含的对象是以FIFO(先入先出)顺序排序的.
LinkedBlockingQueue
实现了BlockingQueue接口,基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE。其所含的对象是以FIFO(先入先出)顺序排序的.
PriorityBlockingQueue
是一个支持优先级的无界队列。默认情况下元素采取自然顺序升序排列。可以自定义实现compareTo()方法来指定元素进行排序规则,或者初始化PriorityBlockingQueue时,指定构造参数Comparator来对元素进行排序。
SynchronousQueue
实现了BlockingQueue接口,是一个没有数据缓冲的BlockingQueue(队列只能存储一个元素),其对外表现为队列的行为,每一个线程的入队操作必须等待另一个线程相应的出队(take)操作;相反,每一个线程的出队操作必须等待另一个线程相应的入队(offer)操作。SynchronousQueue的一个使用场景是在线程池里。Executors.newCachedThreadPool()就使用了SynchronousQueue,这个线程池根据需要(新任务到来时)创建新的线程,如果有空闲线程则会重复使用,线程空闲了60秒后会被回收。

猜你喜欢

转载自www.cnblogs.com/gustavo/p/12237575.html