Java中的多线程

一、多线程相关概念

1.1 进程

  • 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。
  • 进程就是用来加载指令、管理内存、管理IO的。
  • 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程

1.2 线程

1.2.1 什么是线程

  • 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给CPU执行。
  • 一个进程之内可以分为一到多个线程。
    在这里插入图片描述

进程和线程的区别:

  • 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务。
  • 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低(上下文切换指的是从一个线程切换到另一个线程)。

1.2.2 线程状态

  1. 在JDK中的Thread类中的枚举State里面定义了6中线程的状态分别是:新建、可运行、终结、阻塞、等待和限时等待六种。
  2. 当一个线程对象被创建,但还未调用start()时处于新建状态NEW
  3. 调用了start(),就会由新建进入可运行状态RUNNABLE。如果线程内代码已经执行完毕,由可运行进入终结状态TERMINATED。当然这些是一个线程正常执行情况。
  4. 如果线程获取锁失败后,由可运行进入Monitor的阻塞队列进入阻塞状态BLOCKED,只有当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的阻塞线程,唤醒后的线程再次进入可运行状态RUNNABLE
  5. 如果线程获取锁成功后,但由于条件不满足,调用了wait(),此时从可运行状态释放锁等待状态WAITING,当其它持锁线程调用notify()或notifyAll(),会恢复为可运行状态RUNNABLE
  6. 当线程中调用了sleep(long),也会从可运行状态进入限时等待状态TIMED_WAITING,不需要主动唤醒,超时时间到自然恢复为可运行状态RUNNABLE
    在这里插入图片描述

1.3 并行与并发

1.3.1 单核CPU

  • 单核CPU下线程实际是串行执行的。
  • 操作系统中有一个组件叫做任务调度器,将cpu的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于cpu在线程间(时间片很短)的切换非常快,人类感觉是同时运行的 。
  • 总结为一句话就是: 微观串行,宏观并行。

在这里插入图片描述

  • 一般会将这种线程轮流使用CPU的做法称为并发(concurrent)。

1.3.2 多核CPU

  • 每个核(core)都可以调度运行线程,这时候线程可以是并行的。
    在这里插入图片描述
  • 并发:是同一时间应对多件事情的能力,多个线程轮流使用一个或多个CPU。例如家庭主妇做饭、打扫卫生、给孩子喂奶,她一个人轮流交替做这多件事,这时就是并发。
  • 并行:是同一时间动手做多件事情的能力。4核CPU同时执行4个线程。雇了3个保姆,一个专做饭、一个专打扫卫生、一个专喂奶,互不干扰,这时是并行。
  • 家庭主妇雇了个保姆,她们一起这些事,这时既有并发,也有并行(这时会产生竞争,例如锅只有一口,一个人用锅时,另一个人就得等待)。

二、线程的创建方式

2.1 继承Thread类

1. 定义子类MyThread继承Thread类,重写run()方法

package com.hkd.thread;

public class MyThread extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程执行输出"+ i);
        }
    }
}

2. main中创建MyThread类的对象,调用线程对象的start方法开启线程(启动后还是执行run方法)

package com.hkd.thread;

public class ThreadDemo1 {
    public static void main(String[] args) {
        // new一个新线程对象
        Thread t = new MyThread();
        // 调用start方法启动线程
        t.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("主线程执行输出"+ i);
        }
    }
}

疑问

  1. 为什么不调用run(),而是调用start()开启线程?

    直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。只有调用start方法才是启动一个新的线程,调用run方法执行run方法中的逻辑代码。并且start方法只能被调用一次。

  2. 为什么子线程要放在主线程之前写?

    如果主线程放在子线程之前了,主线程一直是先跑完的,相当于单线程了。

方法一的优缺点:

  1. 优点:编码简单。
  2. 缺点:线程类已经继承Thread类,无法继承其他类,不利于扩展。

2.2 实现Runnable接口

1. 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法

package com.hkd.thread;

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程执行输出"+ i);
        }
    }
}

2. 创建MyRunnable任务对象,把MyRunnable任务对象交给Thread处理;调用线程对象的start方法开启线程。

package com.hkd.thread;

public class ThreadDemo1 {
    public static void main(String[] args) {
        // 创建一个任务对象
        Runnable target = new MyRunnable();
        // 把任务对象交给Thread处理
        Thread t = new Thread(target);
        // 开启线程
        t.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("主线程执行输出"+ i);
        }
    }
}

在创建线程的时候,不会立即执行run(),而是等到start()执行的时候,才会开始执行run()。

方式二的优缺点:

  1. 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
  2. 缺点:编程多一层对象包装(任务对象),如果线程有执行结果是不可以直接返回的。run()不能抛异常,只能try/catch。

2.3 实现Runable的另一种写法(匿名内部类)

package com.hkd.thread;

public class ThreadDemo1 {
    public static void main(String[] args) {
	    new Thread(() -> {
	                for (int i = 0; i < 10; i++) {
	                    System.out.println("子线程执行输出"+ i);
	                }
	            }).start();
	        // 把任务对象交给Thread处理
	        Thread t = new Thread(target);

        for (int i = 0; i < 5; i++) {
            System.out.println("主线程执行输出"+ i);
        }
    }
}

2.4 实现Callable接口

1. 定义MyCallable类实现Callable接口,重写call方法,封装要做的事情

package com.hkd.thread;

import java.util.concurrent.Callable;

// 需声明线程任务执行完毕后的结果数据类型
public class MyCallable implements Callable<String> {

    private int n;
    public MyCallable(int n){
        this.n = n;
    }

    // 重写call方法
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }
        return "子线程的执行结果:"+ sum;
    }
}

2. 用FutureTask把Callable对象封装成线程任务对象,把这个对象交给Thread处理,调用start方法开启线程,执行任务;线程执行完毕后,通过FutureTask的get方法获取任务执行的结果

package com.hkd.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadDemo1 {
    public static void main(String[] args) {

        // 创建Callable对象
        Callable<String> call1 = new MyCallable(100);
        // 把Callable任务对象封装成FutureTask,它是Runnable对象(实现Runnable接口),可以交给Thread
        // 可以在线程执行完毕之后通过get方法得到线程执行的结果
        FutureTask<String> f1 = new FutureTask<>(call1);
        // 交给线程处理
        Thread t1 = new Thread(f1);
        t1.start();

        Callable<String> call2 = new MyCallable(200);
        FutureTask<String> f2 = new FutureTask<>(call2);
        Thread t2 = new Thread(f2);
        t2.start();

		// get方法获取任务执行的结果,如果上面代码没有执行完是不执行这里的
        try {
            String rs1 = f1.get();
            System.out.println("线程1结果:"+ rs1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            String rs2 = f2.get();
            System.out.println("线程2结果:"+ rs2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

方法三的优缺点:

  1. 优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。可以在线程执行完毕后去获取线程执行的结果。call()可以抛异常。
  2. 缺点:编码复杂一点。

三、线程提供的API

3.1 API

在这里插入图片描述
在这里插入图片描述

package com.hkd.thread.com.hkd.thread2;

public class ThreadMain {
    public static void main(String[] args) throws Exception {
        Thread t1 = new MyThread();
        // 给当前线程命名
        t1.setName("一号");
        t1.start();
        System.out.println(t1.getName());

        Thread t2 = new MyThread();
        t1.setName("二号");
        t2.start();
        System.out.println(t2.getName());

        // 当前正在运行的线程对象
        Thread m = Thread.currentThread();
        System.out.println(m.getName());

        for (int i = 0; i < 5; i++) {
            System.out.println("main线程输出:" + i);
            if(i==3)
                // 让当前线程休眠
                Thread.sleep(3000);
        }
    }
}
package com.hkd.thread.com.hkd.thread2;

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "输出:" +i);
        }
    }
}

3.2 Java中wait()和sleep()的区别

3.2.1 共同点

wait()、wait(long)、sleep(long)的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态。

3.2.2 不同点

  • 方法归属不同
    • sleep(long)是Thread的静态方法。
    • 而wait()、wait(long)都是Object的成员方法,每个对象都有。
  • 醒来时机不同
    • 执行sleep(long)和wait(long)的线程都会在等待相应毫秒后醒来。
    • wait(long)和wait()还可以被notify()唤醒,wait()如果不唤醒就一直等下去。
    • 它们都可以被打断唤醒。
  • 锁特性不同(重点)
    • wait()的调用必须先获取wait()对象的锁,而sleep则无此限制。
    • wait()执行后会释放对象锁,允许其它线程获得该对象锁(我放弃cpu使用权,但你们还可以用)。
    • 而sleep()如果在synchronized代码块中执行,并不会释放对象锁(我放弃cpu使用权,你们也用不了)。

四、线程安全

4.1 线程安全问题的原因

  • 多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。
  • 出现安全问题的原因:多线程并发,同时访问共享资源,存在修改共享资源。

4.2 解决方法:线程同步

  • 让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
  • 线程同步的核心思想:加锁。把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。

4.2.1 synchronized同步代码块

4.2.1.1 synchronized锁的使用
  • synchronized对象锁,采用互斥的方式,让同一时刻至多只有一个线程持有对象锁,其他线程再想获取这个对象锁的时候会被阻塞。当线程释放锁后,其他线程再争抢锁执行代码。
  • 一般建议使用共享资源作为锁对象;对于实例方法建议使用this作为锁对象;对于静态方法建议使用字节码(类名.class)对象作为锁对象。
    public void drawmoney(double money) {
        // 1. 获取谁来取钱
        String name = Thread.currentThread().getName();
        // 同步代码块
        synchronized (this) {
            // 2. 判断账户余额够不够
            if(this.money >= money){
                System.out.println(name + "来取钱成功,吐出" + money);
                this.money -= money;
                System.out.println(name + "取钱后剩余" + this.money);
            } else {
                System.out.println(name + "取钱,余额不足");
            }
        }
    }
4.2.1.2 synchronized锁的原理Monitor
  • Monitor 被翻译为监视器,是由JVM提供,C++语言实现。
  • 被monitorenter和monitorexit包围住的指令就是上锁的代码。
  • 有两个monitorexit的原因,第二个monitorexit是为了防止锁住的代码抛异常后不能及时释放锁。
    在这里插入图片描述

Monitor内部具体的存储结构:

  • Owner:存储当前获取锁的线程的,只能有一个线程可以获取。
  • EntryList:关联没有抢到锁的线程,处于Blocked状态的线程。
  • WaitSet:关联调用了wait方法的线程,处于Waiting状态的线程。

Monitor的执行流程:

  1. 代码进入synchorized代码块,先让对象锁lock关联到monitor,然后判断Owner是否有线程持有。(如何关联的在章节4.2.1.4讲解)
  2. 如果没有线程持有,则让当前线程持有,表示该线程获取锁成功。
  3. 如果有线程持有,则让当前线程进入entryList进行阻塞,如果Owner持有的线程已经释放了锁,在EntryList中的线程去竞争锁的持有权(非公平)。
  4. 如果代码块中调用了wait()方法,则会进去WaitSet中进行等待。

在这里插入图片描述

4.2.1.3 进阶:对象的内存组成

在HotSpot虚拟机中,对象在内存中存储的布局可分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充。

在这里插入图片描述
其中MarkWord部分的组成:

在这里插入图片描述

  • hashcode:25位的对象标识Hash码。
  • age:对象分代年龄占4位。
  • biased_lock:偏向锁标识,占1位 ,0表示没有开始偏向锁,1表示开启了偏向锁。
  • thread:持有偏向锁的线程ID,占23位。
  • epoch:偏向时间戳,占2位。
  • ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针,占30位。
  • ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针,占30位。
  • 我们可以通过lock的标识,来判断是哪一种锁的等级。
    • 后三位是001表示无锁。
    • 后三位是101表示偏向锁。
    • 后两位是00表示轻量级锁。
    • 后两位是10表示重量级锁。
4.2.1.4 进阶:Monitor重量级锁
  • 每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)之后,该对象头的Mark Word中就被设置指向Monitor对象的指针。
  • 每个对象的对象头都可以设置monoitor的指针,让对象与monitor产生关联。
4.2.1.5 锁升级:轻量级锁

在很多的情况下,在Java程序运行时,同步块中的代码都是不存在竞争的,不同的线程交替地执行同步块中的代码。这种情况下,用重量级锁是没必要的。因此JVM引入了轻量级锁的概念。

static final Object obj = new Object();

public static void method1() {
    synchronized (obj) {
        // 同步块 A
        method2();
    }
}

public static void method2() {
    synchronized (obj) {
        // 同步块 B
    }
}
  1. 在正常程序执行的时候,对象处于无锁状态。
    在这里插入图片描述
  2. 当程序执行到method1方法的时候,进入同步代码块,线程会在线程栈中创建一个Lock Record,将其obj字段指向锁对象。
    在这里插入图片描述
  3. 通过CAS指令将Lock Record的地址存储在对象头的MarkWord中(数据进行交换),如果对象处于无锁状态则修改成功,代表该线程获得了轻量级锁。
    在这里插入图片描述
  4. 如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一部分为null,起到了一个重入计数器的作用。
    在这里插入图片描述
  5. 如果CAS修改失败,说明发生了竞争,需要膨胀为重量级锁。
  6. 解锁过程。遍历线程栈,找到所有obj字段等于当前锁对象的Lock Record。如果Lock Record的Mark Word为null,代表这是一次重入,将obj设置为null后continue。
    在这里插入图片描述
  7. 如果Lock Record的Mark Word不为null,则利用CAS指令将对象头的Mark Word恢复成为无锁状态。如果失败则膨胀为重量级锁。
    在这里插入图片描述
4.2.1.6 锁升级:偏向锁
  • 轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。
  • Java 6中引入了偏向锁来做进一步优化,只有第一次使用CAS将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有。

在这里插入图片描述
在这里插入图片描述
三种锁的区别:

在这里插入图片描述

一旦锁发生了竞争,都会升级为重量级锁。

4.2.2 同步方法

  • 作用:把出现线程安全问题的核心方法给上锁。
  • 原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。
  • 对于实例方法默认使用this作为锁对象。
  • 对于静态方法默认使用类名.class对象作为锁对象。
	//同步方法(锁起来)
    public synchronized void drawmoney(double money) {
        // 1. 获取谁来取钱
        String name = Thread.currentThread().getName();

        // 2. 判断账户余额够不够
        if (this.money >= money) {
            System.out.println(name + "来取钱成功,吐出" + money);
            this.money -= money;
            System.out.println(name + "取钱后剩余" + this.money);
        } else {
            System.out.println(name + "取钱,余额不足");
        }
    }

疑问:

同步代码块和同步方法哪个更好一些?

答:同步代码块锁的范围更小,同步方法锁的范围更大。

4.2.3 Lock锁

可以灵活地上锁和解锁。

public void drawmoney(double money) {
        // 1. 获取谁来取钱
        String name = Thread.currentThread().getName();

        // 上锁
        lock.lock();
        try {
            // 2. 判断账户余额够不够
            if (this.money >= money) {
                System.out.println(name + "来取钱成功,吐出" + money);
                this.money -= money;
                System.out.println(name + "取钱后剩余" + this.money);
            } else {
                System.out.println(name + "取钱,余额不足");
            }
        } finally {
            //解锁
            lock.unlock();
        }
    }

4.3 线程通信

- 什么是线程通信、如何实现?

  • 所谓线程通信就是线程间相互发送数据。

- 线程通信常见形式

  • 通过共享一个数据的方式实现。
  • 根据共享数据的情况决定自己该怎么做,以及通知其他线程怎么做。

- 线程通信实际应用模型

  • 生产者与消费者模型:生产者线程负责生产数据,消费者线程负责消费生产者产生的数据。
  • 一般要求:生产者线程生产完数据后唤醒消费者,然后等待自己,消费者消费完该数据后唤醒生产者,然后等待自己。

4.4 wait()、notify()、notifyAll()的区别

  1. void wait()方法,当前线程等待,直到另一个线程调用notify()或者notifyAll()才能唤醒当前线程。
  2. void notify()方法,随机唤醒一个wait线程。
  3. void notifyAll()方法,唤醒所有wait线程。

4.5 JMM:Java内存模型

  1. Java内存模型定义了共享内存中,多线程程序读写操作的行为规范。通过这些规则来规范对内存的读写操作,从而保证指令的正确性。
  2. 首先,所有的共享变量,包括实例变量和类变量,都被存储在主内存中,也就是计算机的RAM。需要注意的是,局部变量并不包含在内,因为它们是线程私有的,所以不存在竞争问题。
  3. 其次,每个线程都有自己的工作内存,这里保留了线程所使用的变量的工作副本。这意味着,线程对变量的所有操作,无论是读还是写,都必须在自己的工作内存中完成,而不能直接读写主内存中的变量。
  4. 最后,不同线程之间不能直接访问对方工作内存中的变量。如果线程间需要传递变量的值,那么这个过程必须通过主内存来完成。

在这里插入图片描述

4.6 CAS的理解

4.6.1 CAS的工作原理

  • CAS的全称是: Compare And Swap(比较再交换),它体现的一种乐观锁的思想,在无锁情况下保证线程操作共享数据的原子性。
  • 一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当旧的预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。如果CAS操作失败,通过自旋的方式等待并再次尝试,直到成功。

在这里插入图片描述

  1. 线程1与线程2都从主内存中获取变量int a = 100,同时放到各个线程的工作内存中。
  2. 线程1操作:V:int a = 100,A:int a = 100,B:修改后的值:int a = 101 (a++)。
  3. 线程2操作:V:int a = 100,A:int a = 100,B:修改后的值:int a = 99(a–)。
  4. 线程1拿A的值与主内存V的值进行比较,判断是否相等。如果相等,则把B的值101更新到主内存中。
  5. 线程2拿A的值与主内存V的值进行比较,判断是否相等(目前不相等,因为线程1已更新V的值99),不相等,则线程2更新失败。

在这里插入图片描述

  1. 自旋锁操作。因为没有加锁,所以线程不会陷入阻塞,效率较高。如果竞争激烈,重试频繁发生,效率会受影响。
  2. 需要不断尝试获取共享内存V中最新的值,然后再在新的值的基础上进行更新操作,如果失败就继续尝试获取新的值,直到更新成功。

4.6.2 CAS的底层实现

  • CAS底层依赖于一个Unsafe类来直接调用操作系统底层的CAS指令。

在这里插入图片描述

  • 都是native修饰的方法,由系统提供的接口执行,并非Java代码实现,一般的思路也都是自旋锁实现。
  • 在Java中比较常见使用有很多,比如ReentrantLock和Atomic开头的线程安全类,都调用了Unsafe中的方法。

4.7 乐观锁和悲观锁

  • CAS是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。
  • synchronized是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。

4.8 volatile的理解

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:保证线程间的可见性、禁止进行指令重排序。

4.8.1 保证线程间的可见性

保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。

在这里插入图片描述

  1. 上面这个代码中,开启了三个线程,线程1在sleep了100ms后对stop全局变量进行更改。
  2. 线程2在sleep了200ms后对stop变量进行打印。
  3. 线程3会进入while循环,当stop为false的时候开始循环,在变为true的时候结束循环。
  4. 但是最终,线程2正确地打印了stop变量为true,线程3并未结束循环。这是因为在JVM虚拟机中有一个JIT(即时编辑器)给代码做了优化。
    在这里插入图片描述
  5. 解决方案:第一种在程序运行的时候加入vm参数-Xint表示禁用即时编辑器,不推荐,得不偿失(其他程序还要使用);第二种在修饰stop变量的时候加上volatile,表示当前代码禁用了即时编辑器,问题就可以解决,代码如下:static volatile boolean stop = false。

4.8.2 禁止进行指令重排序

五、线程池

5.1 什么是线程池

线程池就是一个可以复用线程的技术。

  • 不使用线程池的问题

    如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。

5.2 谁创建线程池

JDK 5.0起提供了代表线程池的接口:ExecutorService

5.3 如何得到线程对象

使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象,
ThreadPoolExecutor构造器的参数说明在这里插入图片描述

5.4 线程池面试题

1. 临时线程是什么时候创建的?

新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

2. 什么时候开始拒绝任务?

核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。

5.5 线程池处理Runnable任务

使用ExecutorService的方法:void execute(Runnable target)

package com.hkd.threadpool;

import java.util.concurrent.*;

/**
 * 自定义线程池对象
 */
public class threadpooldemo {
    public static void main(String[] args) {
        /**
         * public ThreadPoolExecutor(int corePoolSize,
         *                               int maximumPoolSize,
         *                               long keepAliveTime,
         *                               TimeUnit unit,
         *                               BlockingQueue<Runnable> workQueue,
         *                               ThreadFactory threadFactory)
         */
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        // 给任务线程池,Runnable任务
        Runnable target = new MyRunnable();
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
    }
}

5.6 线程池处理Callable任务

使用ExecutorService的方法:Future<T> submit(Callable<T> command)

package com.hkd.threadpool;

import com.hkd.thread.MyCallable;

import java.util.concurrent.*;

/**
 * 自定义线程池对象
 */
public class threadpooldemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        /**
         * 处理Callable任务
         */
        // 提交Callable任务,返回Future任务对象
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));

        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
    }
}

在线程池执行submit()方法的时候,假设第一个线程执行的时候,第二个线程是否会执行是看线程调度器决定的,如果线程池中有足够的空闲线程,那么第二个线程任务可以立马执行,但如果线程池中的所有线程都在执行其他任务,则第二个线程将会等待,直到有空闲线程可以使用。

5.7 新任务拒绝策略

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值