java高级の多线程编程(高级篇)

本文内容:

  • Java内存模型
  • 线程特征
  • 多线程控制
  • 容器
  • 线程池

一、Java内存模型

1. Java程序执行流程

在这里插入图片描述

如上图所示:

- 1. '.java'文件会通过`Java编译器(Java Compiler)`'javac'命令),并编译成'.class'文件。
- 2. '.class'文件通过`类加载器(Class Loader)`,进入`Java虚拟机(JVM)`中。
- 3. 然后由`JVM`执行引擎执行。

Java内存模型指的就是 Runtime Data Area(运行时数据区),即程序执行期间用到的数据和相关信息保存区

2. Java内存模型

  • 根据JVM规范:
  • 虚拟机栈
  • 方法区
  • 程序计数器
  • 本地方发栈

在这里插入图片描述


2.1 PC程序计数器

- 1. `每个线程`对应有一个'程序计数器'。
- 2. 各线程的程序计数器是`线程私有的`'互不影响',是'线程安全'的。
- 3. 程序计数器`记录线程正在执行的内存地址``以便被中断线程恢复执行时再次按照中断时的指令地址继续执行`

2.2 Java栈 JavaStack(虚拟机栈JVM Stack)

- `每个线程`会对应`一个Java栈`;
- `每个Java栈``若干栈帧`组成;
- `每个方法`对应`一个栈帧`;
- `栈帧`在方法运行时,`创建并入栈`'方法执行完,该栈帧弹出栈帧中的元素作为该方法返回值,该栈帧被清除';
- `栈顶`的栈帧叫`活动栈`,表示当前执行的方法,才可以被CPU执行;

- 线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;
- 栈扩展时无法申请到足够的内存,就会抛出OutOfMemoryError(`OOM`)异常;

在这里插入图片描述


2.3 方法区 - MethodArea

- 方法区是Java`堆的永久区`(PermanetGeneration)
- 方法区存放了'要加载'`类的信息(名称、修饰符等)``类中的静态常量``类中定义为final类型的常量``类中的Field信息``类中的方法信息`。
- 方法区是`被Java线程共享`的
- 方法区要使用的内存超过其允许的大小时,会抛出OutOfMemoryError: PremGen space的错误信息。

2.4 常量池 - ConstantPool

- 1. 常量池是`方法区的一部分`。
- 2. 常量池中存储两类数据:字面量和引用量。
	- 字面量:字符串、final变量等。
	- 引用量:类/接口、方法和字段的名称和描述符,
- 3. 常量池在编译期间就被确定,并保存在已编译的.class文件中

2.5 本地方法栈 - Native Method Stack

- 本地方法栈和Java栈所发挥的作用非常相似,区别不过是`Java栈`为JVM执行`Java方法服务`,
  而`本地方法栈`为JVM执行`Native方法服务`。
- 本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。

3. Java内存模型工作示意图

在这里插入图片描述

- 1) 首先类加载器将Java代码加载到方法区
- 2) 然后执行引擎从方法区找到main方法
- 3) 为方法创建栈帧放入方法栈,同时创建该栈帧的程序计数器
- 4) 执行引擎请求CPU执行该方法
- 5) CPU将方法栈数据加载到工作内存(寄存器和高速缓存),执行该方法
- 6) CPU执行完之后将执行结果从工作内存同步到主内存
  • 线程计算的时候,原始的数据来自内存,在计算过程中,有些数据可能被频繁读取,这些数据被存储在寄存器和高速缓存中,当线程计算完后,这些缓存的数据在适当的时候应该写回内存。

  • 当个多个线程同时读写某个内存数据时,就会产生多线程并发问题,要解决这些问题就涉及到多线程编程三个特性:原子性,有序性,可见性。

二、多线程特性

  • 多线程编程要保证满足三个特性:原子性可见性有序性

1. 原子性

  • 原子性,即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

2. 可见性

  • 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。显然,对于单线程来说,可见性问题是不存在的。

3. 有序性

  • 有序性即程序执行的顺序按照代码的先后顺序执行。

三、多线程控制类

  • 为了保证多线程的三个特性,Java引入了很多线程控制机制,下面介绍其中常用的几种:
  • ThreadLocal
  • 原子类
  • Lock类
  • volatile关键字

1. ThreadLocal

1. 作用:

ThreadLocal提供线程局部变量,即为使用相同变量的每一个线程维护一个该变量的副本
当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal,比如数据库连接Connection,每个请求处理线程都需要,但又不相互影响,就是用ThreadLocal实现。

2. 示例:

  • 需求:银行的转账业务。银行现在有一个用户同时在向两个账户转账。

  • 实现代码:

/**
 * 描述:<br>案例:银行的转账业务。
 *  需求:银行现在有一个用户同时在向两个账户转账。
 *  代码测试结果:
 *      两个线程之间的结果没有任何影响
 * </>
 * @author 周志通
 * @version 1.0.0
 * @date 2020/9/5 17:23
 **/
public class ThreadLocalDemo01 {
    public static void main(String[] args) {
        Bank bank = new Bank();
        BankThread bankThread = new BankThread(bank);
        Thread thread1 = new Thread(bankThread);
        Thread thread2 = new Thread(bankThread);

        thread1.start();
        thread2.start();
        /*
        运行结果:
            Thread-0 10
            Thread-0 20
            Thread-0 30
            Thread-1 10
            Thread-1 20
            Thread-1 30
        */
    }
}
/**
 * 案例:银行的转账业务。
 * 需求:银行现在有一个用户同时在向两个账户转账。
 */
class Bank {
    ThreadLocal<Integer> t = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };
    public Integer get() {
        return t.get();
    }
    public void set() {
        t.set(t.get() + 10);
    }
}
class BankThread implements Runnable {
    private Bank bank;
    public BankThread(Bank bank) {
        this.bank = bank;
    }
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            bank.set();
            System.out.println(Thread.currentThread().getName() + " " + bank.get());
        }
    }
}

3. 结果分析:

在这里插入图片描述

- 在`ThreadLocal`类中定义了一个`ThreadLocalMap`,
- `每一个Thread`都有一个`ThreadLocalMap`类型的变量`threadLocals`
- `threadLocals`内部有一个'Entry''Entry''key''ThreadLocal'对象实例,'value'就是`共享变量副本`
- 'ThreadLocal''get'方法就是根据'ThreadLocal'对象实例获取共享变量副本
- 'ThreadLoca'l的'set'方法就是根据'ThreadLocal'对象实例保存共享变量副本

2. 原子类

  • Java的java.util.concurrent.atomic包里面提供了多种可以进行原子操作的类,大致分为四类
  • 原子更新基本类型AtomicIntegerAtomicBooleanAtomicLong
  • 原子更新数组AtomicIntegerArrayAtomicLongArray
  • 原子更新引用AtomicReferenceAtomicStampedReference等。
  • 原子更新属性AtomicIntegerFieldUpdaterAtomicLongFieldUpdater
  • 基本思想CAS问题

  • 案例代码:

/**
 * 描述:<br> 案例:三个人(三个线程),各买100张票,放进一个篮子里(同一个TicketThread对象)
 *      需求:统计三个人买的票数(理论上应该是300张)
 * 结论:
 *      1. AtomicInteger 保证了加减操作的原子性,保证了不会重复操作的情况发生。
 * </>
 * @author 周志通
 * @version 1.0.0
 * @date 2020/9/5 21:53
 **/
public class AtomicDemo01 {
    public static void main(String[] args) throws InterruptedException {
        TicketThread ticketThread = new TicketThread();
        Thread thread1 = new Thread(ticketThread);
        Thread thread2 = new Thread(ticketThread);
        Thread thread3 = new Thread(ticketThread);
        thread1.start();
        thread2.start();
        thread3.start();
        thread1.join();
        thread2.join();
        thread3.join();
        System.out.println("共买的票数:-->" + ticketThread.tickets.get());
    }
}
/**
 * 需求:三个人(三个线程),各买100张票
 */
class TicketThread implements Runnable {
    public AtomicInteger tickets = new AtomicInteger(0);
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            tickets.getAndIncrement();
        }
    }
}

原子类的 CAS 原理分析

  • CASCompare and Swap

在这里插入图片描述

- 1. 取出想要操作'value'的值,并赋值给'value1',然后进行相关操作,赋值给'value2'。
- 2. 然后,再进行获取'value'的值,并赋值给'value3',判断'value1''value3'的值是否相同。
	 如果相同,则将'value2'输出到最终操作的结果。
	 反之,`重复1,2的步骤`。直到'value1''value3'的值是否相同,并将'value2'输出到最终操作的结果,为止。

ABA问题:

  • 当前内存的值一开始是A,被另外一个线程先改为B然后再改为A,那么当前线程访问的时候发现是A,则认为它没有被其他线程访问过。在某些场景下这样是存在错误风险的。如下图:

在这里插入图片描述

public class AtomicClass {
    static AtomicStampedReference<Integer> n;
    public static void main(String[] args) throws InterruptedException {
        int j = 0;
        while(j<100){
            n = new AtomicStampedReference<Integer>(0,0);
            Thread t1 = new Thread(){
                public void run(){
                    for(int i=0; i<1000; i++){
                        int stamp;
                        Integer reference;
                        do{
                            stamp = n.getStamp();
                            reference = n.getReference();
                        } while(!n.compareAndSet(reference, reference+1, stamp, stamp+1));
                    }
                }
            };
            Thread t2 = new Thread(){
                public void run(){
                    for(int i=0; i<1000; i++){
                        int stamp;
                        Integer reference;
                        do{
                            stamp = n.getStamp();
                            reference = n.getReference();

                        } while(!n.compareAndSet(reference, reference+1, stamp, stamp+1));
                    }
                }
            };
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println("n的最终值是:"+n.getReference());
            j++;
        }

    }
}

注意:采用AtomicStampedReference会降低性能,慎用。

四、Lock

1. 简单介绍:

  • Lock接口关系图

在这里插入图片描述


- 'Lock''ReadWriteLock'是两大锁的根接口
- 'Lock'接口支持`重入``公平`等的锁规则:实现类'ReentrantLock''ReadLock''WriteLock'。
- 'ReadWriteLock'接口定义读取者共享而写入者`独占的锁`,实现类:'ReentrantReadWriteLock'

1.1 可重入锁

  • 不可重入锁:即线程请求它已经拥有的锁时会阻塞。
  • 可重入锁:即线程可以进入它已经拥有的锁的同步代码块。

2. 锁内容的相关补充

1公平锁和非公平锁

  • 公平锁:是指按照申请锁的顺序来获取锁。
  • 非公平锁:线程获取锁的顺序不一定按照申请所得顺序来的。
	// 默认false(是 不公平锁),传入true 为公平锁。
	ReentrantLock reentrantLock = new ReentrantLock(@Nullable boolean);

共享锁和独享锁:

  • 共享锁:只能同时被一个线程锁访问。
  • 独享锁:线程锁可以被多个线程所持有。
	ReadWriteLock // 读是共享锁,写是独享锁。

3乐观锁和悲观锁:

  • 乐观锁:对于一个数据的操作并发,是不会发生修改的。在更新数据的时候,会尝试采用更新,不断重入的方式,更新数据。
  • 悲观锁:对于同一个数据的并发操作,是一定会发生修改的。因此对于同一个数据的并发操作,悲观锁采用加锁的形式。悲观锁认为,不加锁的操作一定会出问题,

分段锁

  • 分段锁: jdk1.7及之前的concurrenthashmap。并发操作就是分段锁,其思想就是让锁的粒度变小。

偏向锁:

  • 偏向锁:指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价

轻量级锁和重量级锁

自旋锁

2. 可重入锁

  • 不可重入锁,即线程请求它已经拥有的锁时会阻塞。
  • 可重入锁,即线程可以进入它已经拥有的锁的同步代码块。
  • 相关类: ReentrantLock
  • 常用方法: lock()unlock()
public class ReentrantLockDemo01 {

    public static void main(String[] args) throws InterruptedException {
        LockThread lockThread = new LockThread();
        Thread thread1 = new Thread(lockThread);
        Thread thread2 = new Thread(lockThread);
        Thread thread3 = new Thread(lockThread);
        thread1.start();
        thread2.start();
        thread3.start();

        thread1.join();
        thread2.join();
        thread3.join();

        System.out.println(lockThread.count);
    }
    private static class LockThread implements Runnable {
        Integer count = 0;
        private Lock lock = new ReentrantLock();

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    lock.lock();
                    count++;
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
}
  • 注意: 该锁必须释放,否则会出现死锁状态。因此用finally去执行unlock()释放锁的操作。

3. 读写锁

  • 读写锁
- 1. 可以同时读;
- 2. 读的时候不能写;
- 3. 不能同时写;
- 4. 写的时候不能读。
  • 示例代码:
public class ReadAndWriteLockDemo01 {

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread(2);
        Thread thread1 = new Thread(myThread);
        Thread thread2 = new Thread(myThread);

        thread1.start();
        Thread.sleep(100);
        thread2.start();

    }

    private static class MyThread implements Runnable {
        private Map<Integer, String> map;
        private Lock readLock;
        private Lock writeLock;
        private Integer type;

        public MyThread(Integer type) {
            map = new HashMap<>();
            map.put(1, "hello");
            map.put(2, " world");
            ReadWriteLock rw = new ReentrantReadWriteLock();
            readLock = rw.readLock();
            writeLock = rw.writeLock();
            this.type = type;
        }
        @Override
        public void run() {
            switch (type) {
                case 1: {
                    type++;
                    // 写锁:执行写操作+读操作
                    try {
                        writeLock.lock();
                        System.out.println(Thread.currentThread().getName() + " 开始进行写操作~~~~");
                        Thread.sleep(3 * 1000);
                        map.put(3, "!!!");
                        System.out.println("写操作完成~~~开始测试");
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        writeLock.unlock();
                    }
                    break;
                }
                case 2: {
                    //type--;
                    try {
                        readLock.lock();
                        System.out.println("开始读操作!!!");
                        Thread.sleep(1000);
                        System.out.println("读取得到的值:-->" + map.get(2));
                        Thread.sleep(10 * 1000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        readLock.unlock();
                    }
                }
            }
        }
    }
}

运行结果:

  • 测试1: 在一个线程中,进行读写操作。

运行结果: 无论加怎么读写锁,都能正常进行,没有任何的阻塞。

  • 测试2: 两个线程 同时进行读操作。

运行结果:

- 在第一个`线程A`进入时,加上了写操作锁,此时`线程B`过来时,只能进行等待, 直到`线程A`释放该锁。
  • 测试3: 两个线程同时进行读操作:

运行结果: 两个线程可以同时进行读操作,没有相互影响。

  • 测试4: 一个线程进行写操作,一个线程进行读操作

运行结果:

- 1. 在`线程A`进入程序, 并加上了`写操作锁`。
- 2. 此时, `线程B`只能等待`线程A`释放`写操作锁`, 才能进行读操作。
  • 后面的测试: 如果自己希望能更多地了解,可以自行进行测试。可根据下面总结的内容,单独进行测试。
总结
- 1. `写操作锁`,只能进行一次'加锁':
  理由:在加上`写操作锁`时,`其他线程再进行加锁操作时`,将进入'阻塞状态'。
- 2. `读操作锁`, 可以多次进行'加锁':
  理由:在加上`读操作锁`时,`其他线程再进行加锁操作时`,没有出现任何的'阻塞'

五、Volatile关键字

  • 在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制

参考网址:https://www.cnblogs.com/zhengbin/p/5654805.html

1. 简单介绍:

  • 作用:

一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

- 1. 保证了不同线程对这个变量进行操作时的`可见性`,即一个线程修改了某个变量的值,这新值对其
  他线程来说是立即可见的。'(注意:不保证原子性)'
- 2. 禁止进行指令重排序。'(保证变量所在行的有序性)'
	- 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,
	且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
	- 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile
	变量后面的语句放到其前面执行。

  • 应用场景:

六、容器

  1. 容器类关系图:

在这里插入图片描述

List 集合

在这里插入图片描述

  • ArrayList(数组)

在这里插入图片描述

  • Vector(数组、线程同步,synchronized实现)

在这里插入图片描述

  • LinkList(链表)

在这里插入图片描述

Set集合

在这里插入图片描述

  • HashSet(底层实现:Hash表

在这里插入图片描述

  • TreeSet(底层实现:二叉树

在这里插入图片描述

  • LinkHashSet(底层实现:HashSet + LinkedHashMap

在这里插入图片描述

Map集合

在这里插入图片描述

  • HashMap(底层实现:数组+链表+红黑树)

参考网址:https://www.cnblogs.com/little-fly/p/7344285.html

  • ConcurrentHashMap(线程安全)

七、线程池

1. 简单介绍:

  • Java常见的四种线程池创建方式
- 1. `newCachedThreadPool` ---- 创建一个'可缓存线程池',如果线程池长度超过处理需要,可灵活回收空闲线程,
								若无可回收,则新建线程。
- 2. `newFixedThreadPool` ----- 创建一个'指定工作线程数量'的线程池。
								每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,
								则将提交的任务存入到池队列中。
- 3. `newSingleThreadExecutor`- 创建'一个单线程化的Executor',
								即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,
								保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。如果这个线程异常结束,
								会有另一个取代它,保证顺序执行。
- 4. `newScheduleThreadPool` -- 创建'一个定长的线程池'。
								支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

  • 为什么使用线程池

多线程的缺点:

  • 线程的创建和销毁都是需要消耗资源。
  • 多线程之间的切换也是个耗时并消耗资源。

线程池优点:

  • 使用时线程已存在,消除了线程创建的时耗
  • 通过设置线程数目,防止资源不足
  • 在线程使用完后,不进行销毁,而是返回线程池中,等待下个使用者。

2. ThreadPoolExecutor

  • Java中创建线程池常用的类就是ThreadPoolExectuotor,该类的全参构造函数如下:
public ThreadPoolExecutor(int corePoolSize,	// 核心线程数的最大值
                          int maximumPoolSize,	// 最多线程数
                          long keepAliveTime,	// 空闲线程的存活时间
                          TimeUnit unit,		// 表示keepAliveTime的单位
                          BlockingQueue<Runnable> workQueue,	// 缓存任务的阻塞队列
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler);
  • 参数介绍:
- 1. `corePoolSize`:线程池中核心;'线程数的最大值';
- 2. `maximumPoolSize`:线程池中能拥有'最多线程数';
- 3. `workQueue`:用于缓存任务的阻塞队列,对于不同的应用场景我们可能会采取不同的排队策略,
	              这就需要不同类型的阻塞队列,在线程池中常用的阻塞队列有以下2种:	
  • SynchronousQueue<Runnable>:此队列中不缓存任何一个任务。向线程池提交任务时,如果没有空闲线程来运行任务,则入列操作会阻塞。当有线程来获取任务时,出列操作会唤醒执行入列操作的线程。从这个特性来看,SynchronousQueue是一个无界队列,因此当使用SynchronousQueue作为线程池的阻塞队列时,参数maximumPoolSizes没有任何作用。
  • LinkedBlockingQueue<Runnable>:

以上三个参数之间的关系如下:

  1. 如果没有空闲的线程执行该任务且当前运行的线程数少于corePoolSize,则添加新的线程执行该任务。
  2. 如果没有空闲的线程执行该任务且当前的线程数等于corePoolSize同时阻塞队列未满,则将任务入队列,而不添加新的线程。
  3. 如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数小于maximumPoolSize,则创建新的线程执行任务。
  4. 如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数等于maximumPoolSize,则根据构造函数中的handler指定的策略来拒绝新的任务。
- 4. `keepAliveTime`:表示'空闲线程的存活时间';
- 5. `unit`:表示`keepAliveTime`'单位';
- 6. `handler`:表示当'workQueue已满',且池中的'线程数达到maximumPoolSize'时,线程池拒绝添加新任务时采取的策略。
	一般可以采取以下四种取值。
ThreadPoolExecutor.AbortPolicy()抛出RejectedExecutionException异常
ThreadPoolExecutor.CallerRunsPolicy()由向线程池提交任务的线程来执行该任务
ThreadPoolExecutor.DiscardOldestPolicy()抛弃最旧的任务(最先提交而没有得到执行的任务)
ThreadPoolExecutor.DiscardPolicy()抛弃当前的任务
- 7. `threadFactory`:指定创建线程的工厂

3. 四种常用线程池

- 1. `newCachedThreadPool` ---- 创建一个'可缓存线程池',如果线程池长度超过处理需要,可灵活回收空闲线程,
								若无可回收,则新建线程。
- 2. `newFixedThreadPool` ----- 创建一个'指定工作线程数量'的线程池。
								每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,
								则将提交的任务存入到池队列中。
- 3. `newSingleThreadExecutor`- 创建'一个单线程化的Executor',
								即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,
								保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。如果这个线程异常结束,
								会有另一个取代它,保证顺序执行。
- 4. `newScheduleThreadPool` -- 创建'一个定长的线程池'。
								支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

3.1 newCachedThreadPool

  • 说明:

该方法创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

  • 特点:
  1. 工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE)
  2. 空闲的工作线程会自动销毁,有新任务会重新创建
  3. 在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
  • 示例代码:
public class CachedThreadPoolDemo01 {
    private static ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        newCachedThreadPool.execute(runnable);
        newCachedThreadPool.execute(runnable);
        newCachedThreadPool.shutdown();
        System.out.println("main:" + runnable.count);
    }
    private static class MyRunnable implements Runnable {
        private Lock lock = new ReentrantLock();
        private Integer count = 0;
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                try {
                    lock.lock();
                    count++;
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
            System.out.println(Thread.currentThread().getName() + " -- " + count);
        }
    }
}

3.2 newFixedThreadPool

  • 说明:

该方法创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。

  • 特点:
  • 优点:具有线程池提高程序效率和节省创建线程时所耗的开销。
  • 缺点:在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
  • 示例代码:
public class FixedThreadPool {
    private static ExecutorService service = Executors.newFixedThreadPool(2);
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        service.execute(runnable);
        service.execute(runnable);
        service.execute(runnable);
        service.shutdown();
    }
    private static class MyRunnable implements Runnable {
        private Lock lock = new ReentrantLock();
        private Integer count = 0;
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                try {
                    lock.lock();
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + " -- " + count++);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
            System.out.println(" ================= ");
        }
    }
}

3.3 newSingleThreadExecutor

  • 说明:

该方法创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。

  • 特点:

单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

  • 示例代码:
public class SingleThreadPoolDemo {
    private static ExecutorService service = Executors.newSingleThreadExecutor();

    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();

        service.execute(runnable);
        service.execute(runnable);
        service.execute(runnable);
        service.shutdown();

    }
    private static class MyRunnable implements Runnable {
        private Integer count = 0;

        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + " -- " + (++count));
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                }
            }
            System.out.println(" ================= ");
        }
    }
}

3.4 newScheduleThreadPool

  • 说明:

该方法创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

  • 特点:

  • 示例代码:

public class ScheduleThreadPoolDemo01 {
    public static void main(String[] args) throws InterruptedException {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 10; i++) {
            scheduledThreadPool.schedule(() -> System.out.println(Thread.currentThread().getName()+" delay 3 seconds"), 1, TimeUnit.SECONDS);
            Thread.sleep(1500);
        }
        scheduledThreadPool.shutdown();
    }
}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值