Thread多线程速查手册

常用概念
线程安全
停止线程
守护线程
多线程通讯

Synchornized
悲观锁
乐观锁
共享锁/独占锁
读写锁的机制
死锁
Lock
ReentrantLock
Condition
CountDownLatch
CyclicBarrier
信号量Semaphore
ThreadLocal
高并发
线程安全与不安全的类
不安全的
spring与多线程
线程池
介绍
线程池的优点
线程池工作过程
常用方法
基本使用
阻塞队列

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解决。

  • 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秒后会被回收。

原文地址:https://www.cnblogs.com/gustavo/p/12237575.html

时间: 2024-10-10 04:21:30

Thread多线程速查手册的相关文章

25个有用和方便的 WordPress 速查手册

如果你是一个 WordPress 编码器或开发人员,下载一些方便的 WordPress 备忘单寻找你的工作然后你在正确的地方.我们已经列出了25个有用的和方便的 WordPress 速查手册.WordPress 备忘单后将帮助你发展你的 WordPress 主题和插件以及这些有助于搜索引擎优化你的博客.享受! ! 1. WP-CheatSheet 2. Complete WordPress Cheat Sheet 3. Cheat Sheet SEO for WordPress 4. WordP

《zw版&#183;Halcon-delphi系列原创教程》 zw版-Halcon常用函数Top100中文速查手册

<zw版·Halcon-delphi系列原创教程> zw版-Halcon常用函数Top100中文速查手册 Halcon函数库非常庞大,v11版有1900多个算子(函数). 这个Top版,对最常用的函数,做了中文说明,目前约250条,以后会逐步优化.增减. 目标是,类似常用英文单词500一样,做成<Halcon常用函数300条>.<halcon常用函数500条>等版本,方便大 家学习. 考虑到通用性,函数采用的是Halcon手册格式,没有转成delphi版,请大家注意.

Linux/Unix 系统分析命令速查手册

1.Hardware CPU information: cat /proc/cpuinfo 物理core个数: 统计core 逻辑CPU个数:统计processor Memory information: free -m 其中-+buffer是针对OS/App来说的. Disk information: fdisk -l df -h IO 性能: iostat -d -x -k 1 10 此命令属于sysstat包 观察await 平均io operation等待时间 观察%util 一秒中IO

R之data.table速查手册

R语言data.table速查手册 介绍 R中的data.table包提供了一个data.frame的高级版本,让你的程序做数据整型的运算速度大大的增加.data.table已经在金融,基因工程学等领域大放光彩.他尤其适合那些需要处理大型数据集(比如 1GB 到100GB)需要在内存中处理数据的人.不过这个包的一些符号并不是很容易掌握,因为这些操作方式在R中比较少见.这也是这篇文章的目的,为了给大家提供一个速查的手册. data.table的通用格式: DT[i, j, by],对于数据集DT,

Pandas速查手册中文版

本文翻译自文章: Pandas Cheat Sheet - Python for Data Science ,同时添加了部分注解. 对于数据科学家,无论是数据分析还是数据挖掘来说,Pandas是一个非常重要的Python包.它不仅提供了很多方法,使得数据处理非常简单,同时在数据处理速度上也做了很多优化,使得和Python内置方法相比时有了很大的优势. 如果你想学习Pandas,建议先看两个网站. (1)官网: Python Data Analysis Library (2)十分钟入门Pandas

HTML基础教程(17)——HTML 4.01速查手册

自 W3School 的 HTML 快速参考.可以打印它,以备日常使用. HTML Basic Document <html> <head> <title>Document name goes here</title> </head> <body> Visible text goes here </body> </html> Text Elements <p>This is a paragraph&

8086汇编指令速查手册

一.常用指令 二.算术运算指令 三.逻辑运算指令四.串指令 五.程序跳转指令------------------------------------------ 计算机寄存器分类简介: 32位CPU所含有的寄存器有:4个数据寄存器(EAX.EBX.ECX和EDX)2个变址和指针寄存器(ESI和EDI) 2个指针寄存器(ESP和EBP) 6个段寄存器(ES.CS.SS.DS.FS和GS)1个指令指针寄存器(EIP) 1个标志寄存器(EFlags) 1.数据寄存器数据寄存器主要用来保存操作数和运算结

几个较好的SQL速查手册网址

微软 SQL server 数据库开发手册 数据库设计 Transact-SQL 速查手册 数据库设计 MySQL 中文参考手册速查 结构化查询语言 SQL 学习手册速查 转自:http://www.cnblogs.com/yencain/articles/1313465.html

awk速查手册

awk速查手册 score.txt cat score.txt Marry 2143 78 84 77 Jack 2321 66 78 45 Tom 2122 48 77 71 Mike 2537 87 97 95 Bob 2415 40 57 62 netstat.txt $cat netstat.txt Proto Recv-Q Send-Q Local-Address Foreign-Address State tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN t