初步了解Java多线程

线程概念

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

进程概念

进程是程序的基本执行实体

多线程概念

有了多线程,我们就可以让程序同时做多件事情,多线程的作用就是提高效率

并发:在同一时刻,有多个指令在单个CPU上交替执行

并行:在同一时刻,有多个指令在多个CPU上同时执行

多线程的实现方式

  1. 继承Thread类的方式
  2. 实现Runable接口的方式
  3. 利用Callable接口和Future接口方式
继承Thread类的方式实现
  1. 自定义一个类继承Thread
  2. 重写run方法
  3. 创建子类的对象,并启动线程
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"第"+i+"次打印Hello World");
        }
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        /**
         * 多线程第一种启动方式
         *  1.自定义一个类继承Thread
         *  2.重写run方法
         *  3.创建子类的对象,并启动线程
         */

        MyThread t1 = new MyThread();
        t1.setName("线程1");
        t1.start();

        MyThread t2 = new MyThread();
        t2.setName("线程2");
        t2.start();
    }
}

执行结果(截取部分)

线程2第73次打印Hello World
线程2第74次打印Hello World
线程1第55次打印Hello World
线程2第75次打印Hello World
线程2第76次打印Hello World

实现Runable接口的方式实现
  1. 自己定义一个类实现Runable接口
  2. 重写里面的run方法
  3. 创建自己的类的对象
  4. 创建一个Thread类的对象,并开启线程
public class MyRun implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"第"+i+"次打印Hello World");
        }
    }
}
public class ThreadDemo2 {
    public static void main(String[] args) {
        //创建MyRun的对象
        MyRun mr = new MyRun();
        //创建第一个线程对象
        Thread t1 = new Thread(mr);
        //设置线程名字
        t1.setName("线程1");
        //开启第一个线程
        t1.start();

        //创建第二个线程对象
        Thread t2 = new Thread(mr);
        //设置线程名字
        t2.setName("线程2");
        //开启第一个线程
        t2.start();
    }
}

执行结果(截取部分)

线程1第52次打印Hello World
线程2第80次打印Hello World
线程2第81次打印Hello World
线程1第53次打印Hello World
线程2第82次打印Hello World
线程2第83次打印Hello World

利用Callable接口和Future接口方式实现

特点:可以获取到多线程运行的结果

  1. 创建一个类MyCallable实现Callable接口
  2. 重写call(是有返回值的,表示多线程运行的结果)
  3. 创建MyCallable的对象(表示多线程要执行的任务)
  4. 创建FutureTask的对象(作用管理多线程运行的结果)
  5. 创建Thread类的对象,并启动
//创建Thread类的对象
Thread t1 = new Thread(ft);
t1.setName("线程1");
//启动
t1.start();
public static void main(String[] args) throws ExecutionException, InterruptedException {
    //创建MyCallable的对象(表示多线程要执行的任务)
    MyCallable mc = new MyCallable();
    //创建FutureTask的对象(作用管理多线程运行的结果)
    FutureTask<Integer> ft = new FutureTask<>(mc);
    //创建Thread类的对象
    Thread t1 = new Thread(ft);
    t1.setName("线程1");
    //启动
    t1.start();

    //创建Thread类的对象
    Thread t2 = new Thread(ft);
    t2.setName("线程2");
    //启动
    t2.start();

    Integer result = ft.get();
    System.out.println(result);

}
三种方式对比
优点缺点
继承Thread类编程比较简单,可以直接使用Thread类中的方法可以扩展性交叉,不能再继承其他的类
实现Runnable接口扩展性强,实现该接口的同时还能集成其他类编程相对复杂,不能直接使用Thread类中的方法
实现Callable接口可以获取多线程运行结果,扩展性强,实现该接口的同时还能集成其他类编程相对复杂,不能直接使用Thread类中的方法

常见成员方法

方法名称说明
String getName()返回此线程的名称,默认设计Thread-X,从Threa-0开始
void setName(String name)设置线程的名字
static Thread currentThread()获取当前线程的对象
static void sleep(long time)让线程休眠指定的时间,单位为毫秒
setPriority(int newPriority)设置线程的优先级,默认的优先级是5,范围是1-10。
final int getPriority()获取线程的优先级
final void setDaemon(boolean on)设置为守护线程
public static void yield()出让线程/礼让线程
public static void join()插入线程/插队线程

优先级,在java中多线程是抢占式调度,就是随机获取CPU的使用权,线程的优先级越高,获得使用权的概率越高,默认的优先级是5,范围是1-10。

守护线程

当非守护线程执行完毕之后,守护线程就没有执行下去的必要了,就会陆陆续续结束

应用场景:QQ聊天是非守护线程,文件传输时守护线程,如果聊天框关闭了,那么文件传输就结束了

public class Thread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"@"+i);
        }
    }
}
public class Thread2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"@"+i);
        }
    }
}
public static void main(String[] args) {
    Thread1 t1 = new Thread1();
    Thread2 t2 = new Thread2();

    t1.setName("女神");
    t2.setName("备胎");
    //设置女神线程优先级为8
    t1.setPriority(8);
    //设置备胎线程为守护线程
    t2.setDaemon(true);

    t1.start();
    t2.start();
}

执行结果(截取部分)

备胎@72
女神@99
备胎@73
备胎@74
备胎@75
备胎@76
备胎@77
备胎@78
备胎@79
备胎@80
备胎@81
备胎@82
备胎@83
备胎@84
备胎@85
备胎@86
备胎@87
备胎@88
备胎@89
备胎@90

Process finished with exit code 0

礼让线程

礼让线程会让线程执行尽量均衡

    public static void main(String[] args) {
    
        Thread1 t1 = new Thread1();
        Thread2 t2 = new Thread2();

        t1.setName("飞机");
        t2.setName("坦克");

        t1.start();
        t2.start();

    }
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"@"+i);
            //礼让线程
            Thread.yield();

        }
    }
}

执行结果(截取部分)

飞机@0
坦克@0
飞机@1
坦克@1
坦克@2
飞机@2
坦克@3

插入线程

public static void main(String[] args) {


        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.setName("飞机");
        t1.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("main线程先执行");
        }
    }

执行结果

main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
飞机@0
飞机@1
飞机@2
飞机@3
飞机@4

想让飞机线程插入到main线程之前执行

    public static void main(String[] args) throws InterruptedException {


        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.setName("飞机");

        t1.start();
        //插入线程
        t1.join();

        for (int i = 0; i < 10; i++) {
            System.out.println("main线程先执行");
        }
    }

执行结果

飞机@97
飞机@98
飞机@99
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行
main线程先执行

线程的生命周期

在这里插入图片描述

线程安全

需求:某电影院正在上映大片,共有100张票,电影院有3个窗口卖票,请设计一个程序模拟该电影院卖票

第一版

public class MyThread extends Thread{
   
    int ticket = 0;

    @Override
    public void run() {
        while (true){
            if (ticket<100){
                try {
                    sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket++;
                System.out.println(getName()+"正在卖第"+ticket+"张瞟");
            }else {
                break;
            }
        }
    }
}
public class ThreadDemo5 {


    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        //开启线程
        t1.start();
        t2.start();
        t3.start();
    }

}

这一版会出现每个窗口都卖出去100张票的问题,为了解决这个问题,可以在int ticket = 0;修改为static int ticket = 0;

public class MyThread extends Thread{
    //解决多个窗口数据不同步的问题
    static int ticket = 0;

    @Override
    public void run() {
        while (true){
            if (ticket<100){
                try {
                    sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket++;
                System.out.println(getName()+"正在卖第"+ticket+"张瞟");
            }else {
                break;
            }
        }
    }
}

这一版也会出现问题,就是多个窗口卖出去同一张票,比如3号窗口和2号窗口同时卖出第4张票,最后窗口2和窗口3又多卖了两张票,出现了超出范围的票

执行结果

窗口3正在卖第2张瞟
窗口2正在卖第1张瞟
窗口1正在卖第3张瞟
窗口3正在卖第4张瞟
窗口2正在卖第4张瞟
窗口1正在卖第5张瞟

窗口1正在卖第100张瞟
窗口2正在卖第101张瞟
窗口3正在卖第102张瞟

因为线程在执行代码时,CPU随时会被别的线程抢走,线程执行时,有随机性

为了解决这些BUG,我们需要把操作共享数据的代码锁起来

public class MyThread extends Thread{
    //解决多个窗口数据不同步的问题
    static int ticket = 0;

    @Override
    public void run() {
            while (true){
                //锁对象一定要唯一,类的字节码对象唯一
                synchronized (MyThread.class){
                if (ticket<100){
                    try {
                        sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket++;
                    System.out.println(getName()+"正在卖第"+ticket+"张票");
                }else {
                    break;
                }
            }
        }
    }
}
同步方法

概念:就是把synchronized关键字加到方法上

特点:

  1. 同步方法是锁住方法里面所有的代码

  2. 锁对象不能自己指定,

    如果当前方法是非静态的,锁对象是this,是当前方法的调用者

    如果是静态方法,那么锁对象是当前类的字节码文件对象

public class MyRun implements Runnable{
    int ticket = 0;
    @Override
    public void run() {
        //1.循环
        while (true) {
            //2.同步代码块(同步方法)
            if (method())
                break;
        }
    }

    private synchronized boolean method(){
        //3.判断共享数据是否超出上限
        if (ticket == 100) {
            return true;
        } else {
            //没有超出上限的业务代码
            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket++;
            System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
        }
        return false;
    }
}
StringBuffer和StringBuilder
  • StringBuffer里面的所有方法都加了synchronized关键字,因此是线程安全的
  • StringBuilder里面的方法都没有加synchronized,因此是线程不安全的,效率高
//StringBuffer部分方法
@Override
public synchronized StringBuffer append(CharSequence s) {
    toStringCache = null;
    super.append(s);
    return this;
}

/**
 * @throws IndexOutOfBoundsException {@inheritDoc}
 * @since      1.5
 */
@Override
public synchronized StringBuffer append(CharSequence s, int start, int end)
{
    toStringCache = null;
    super.append(s, start, end);
    return this;
}
//StringBuilder部分方法
@Override
public StringBuilder append(CharSequence s) {
    super.append(s);
    return this;
}

/**
 * @throws     IndexOutOfBoundsException {@inheritDoc}
 */
@Override
public StringBuilder append(CharSequence s, int start, int end) {
    super.append(s, start, end);
    return this;
}
Lock锁

虽然我们可以理解同步代码块和同步方法的锁对象问题

但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁

为了清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作

Lock中提供了获得锁和释放锁的方法

void lock():获得锁

void unlock():释放锁

Lock是接口不能直接实例化,这里采用它的实现ReentrantLock来实例化

public class MyThread extends Thread{
    //解决多个窗口数据不同步的问题
    static int ticket = 0;
    //Lock是接口不能直接实例化,这里采用它的实现ReentrantLock来实例化
    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            //锁对象一定要唯一
            lock.lock();
            try {
                if (ticket<100){
                    try {
                        sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket++;
                    System.out.println(getName()+"正在卖第"+ticket+"张票");
                }else {
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
            	//扫尾代码,不管系统如何执行,报错也罢,一定要解锁
                lock.unlock();
            }
        }

    }

}
死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁

例如,两个食客来西餐厅吃牛排,此时餐厅还剩一把刀和一把叉子,只有同时拿到刀叉才能顺利进餐,此时食客A拿到了刀,食客B拿到了叉子,但是他们两个都不舍得放弃手中的餐具,又拿不到另一把需要的餐具,此时谁都吃不到牛排。

public class MyThread1 extends Thread{
    static Object knife = new Object();
    static Object fork = new Object();

    @Override
    public void run() {
        while (true){
            if ("食客A".equals(getName())){
                synchronized (knife){
                    System.out.println("食客A拿到了knife,准备拿fork吃牛排");
                    synchronized (fork){
                        System.out.println("食客A拿到了knife和fork,顺利吃到了牛排");
                    }
                }
            }else if ("食客B".equals(getName())){
                synchronized (fork){
                    System.out.println("食客B拿到了fork,准备拿knife吃牛排");
                    synchronized (knife){
                        System.out.println("食客B拿到了knife和fork,顺利吃到了牛排");
                    }
                }
            }
        }
    }
}
public class ThreadDemo7{
    public static void main(String[] args) {
        MyThread1 ta = new MyThread1();
        MyThread1 tb = new MyThread1();

        ta.setName("食客A");
        tb.setName("食客B");

        ta.start();
        tb.start();
    }
}

执行结果

食客A拿到了knife,准备拿fork吃牛排
食客B拿到了fork,准备拿knife吃牛排

生产者和消费者(等待唤醒机制)

在这里插入图片描述

生产者消费者常见方法

方法名称说明
void wait()当前线程等待,知道被其他线程唤醒
void notify()随机唤醒单个线程
void notifyAll()唤醒所有线程

生产者代码

/**
 * 生产者厨师
 */
public class Cook extends Thread{
    @Override
    public void run() {
        while (true){
            synchronized (Desk.lock){
                if (Desk.count==0){
                    break;
                }else {
                    if (Desk.foodFlag==1){
                        //有食物,等待消费者吃饭
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else {
                        //没有食物,开始做

                        System.out.println("厨师做了一份食物");
                        Desk.foodFlag = 1;
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}

消费者代码

/**
 * 消费者
 */
public class Foodie extends Thread{
    @Override
    public void run() {
        while (true){
            synchronized (Desk.lock){
                //吃满十份,就不能再吃了
                if (Desk.count==0){
                    break;
                }else {
                    //判断桌子上是否有食物,如果有就开车,没有就等待,0表示没有
                    if (Desk.foodFlag==0){
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        Desk.lock.notifyAll();//唤醒跟这把锁绑定的线程
                    }else {
                        //如果有食物,就开吃
                        //把能吃的分数-1
                        Desk.count--;
                        System.out.println("吃货正在吃饭,还能再吃"+Desk.count+"份食物");
                        //吃完之后,唤醒厨师继续做饭
                        Desk.lock.notifyAll();


                        //修改桌子的状态
                        Desk.foodFlag = 0;

                    }
                }
            }
        }
    }
}

餐桌代码

/**
 * 餐桌,控制生产者和消费者的执行
 */
public class Desk {
    //定义桌子上是否有食物,1有,0没有
    public static int foodFlag = 0;

    //总个数
    public static  int count = 10;

    //锁对象
    public static Object lock = new Object();
}

运行测试

public class ThreadDemo8 {
    public static void main(String[] args) {
        Cook cook = new Cook();
        Foodie foodie = new Foodie();

        cook.setName("厨师");
        foodie.setName("吃货");

        cook.start();
        foodie.start();
    }
}

控制台输出结果

厨师做了一份食物
吃货正在吃饭,还能再吃9份食物
厨师做了一份食物
吃货正在吃饭,还能再吃8份食物
厨师做了一份食物
吃货正在吃饭,还能再吃7份食物
厨师做了一份食物
吃货正在吃饭,还能再吃6份食物
厨师做了一份食物
吃货正在吃饭,还能再吃5份食物
厨师做了一份食物
吃货正在吃饭,还能再吃4份食物
厨师做了一份食物
吃货正在吃饭,还能再吃3份食物
厨师做了一份食物
吃货正在吃饭,还能再吃2份食物
厨师做了一份食物
吃货正在吃饭,还能再吃1份食物
厨师做了一份食物
吃货正在吃饭,还能再吃0份食物

Process finished with exit code 0

阻塞队列失效生产者消费者

在这里插入图片描述

让阻塞队列容量为1,此时生产者生产一份,消费者就消费一份

生产者代码

public class Cook1 extends Thread{
    ArrayBlockingQueue<String> queue;

    public Cook1(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true){
            try {
                queue.put("食物");
                System.out.println("厨师做了一份食物");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

消费者代码

public class Foodie1 extends Thread{
    ArrayBlockingQueue<String> queue;

    public Foodie1(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true){
            try {
                String food = queue.take();
                System.out.println("吃货吃了一碗"+food);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

测试结果

public class ThreadDemo9 {
    public static void main(String[] args) {
        ArrayBlockingQueue<String>queue = new ArrayBlockingQueue<>(1);

        Cook1 c = new Cook1(queue);
        Foodie1 f = new Foodie1(queue);

        c.start();
        f.start();
    }
}

控制台输出结果

厨师做了一份食物
吃货吃了一碗食物
厨师做了一份食物
吃货吃了一碗食物
厨师做了一份食物
吃货吃了一碗食物
厨师做了一份食物
吃货吃了一碗食物

线程的状态

JVM管理线程六大状态,运行状态由操作系统管理

新建状态(new)创建线程对象
就绪状态(RUNNABLE)start()
阻塞状态(BLOCKED)无法获得锁对象
等待状态(WAITING)wait()
计时等待(TIME_WAITING)sleep()
结束状态(TERMINATED)全部代码运行完毕

在这里插入图片描述

线程池

核心原理

  1. 创建一个池子,池子中是空的
  2. 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
  3. 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待

在这里插入图片描述

线程池代码实现

Executors工具类,是线程池的工具类,通过调用不同的方法创建不同的线程池对象

方法名称说明
public static ExecutorService newCachedThreadPool()创建一个上限很大的线程池,可视为没有上限的线程池
public static ExecutorService newFixedThreadPool(int nThreads)创建有上限的线程池
public class MyRunnable1 implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
}
public class MyThreadPoolDemo {
    public static void main(String[] args) {

        //1.获取线程池对象,创建一个没有上限的线程池
        ExecutorService pool1 = Executors.newCachedThreadPool();

        //2.提交任务
        pool1.submit(new MyRunnable1());

        pool1.submit(new MyRunnable1());
        pool1.submit(new MyRunnable1());
        pool1.submit(new MyRunnable1());
        pool1.submit(new MyRunnable1());

        //3.销毁线程池
        pool1.shutdown();
    }
}

执行结果

pool-1-thread-2—1
pool-1-thread-2—2
pool-1-thread-4—1
pool-1-thread-4—2
pool-1-thread-2—3
pool-1-thread-4—3
pool-1-thread-3—1
pool-1-thread-3—2
pool-1-thread-2—4
pool-1-thread-3—3
pool-1-thread-3—4
pool-1-thread-3—5
pool-1-thread-3—6
pool-1-thread-3—7
pool-1-thread-3—8
pool-1-thread-3—9
pool-1-thread-4—4
pool-1-thread-4—5
pool-1-thread-4—6
pool-1-thread-4—7
pool-1-thread-4—8
pool-1-thread-4—9
pool-1-thread-5—1
pool-1-thread-5—2
pool-1-thread-5—3
pool-1-thread-5—4
pool-1-thread-5—5
pool-1-thread-5—6
pool-1-thread-5—7
pool-1-thread-5—8
pool-1-thread-5—9
pool-1-thread-1—1
pool-1-thread-2—5
pool-1-thread-1—2
pool-1-thread-1—3
pool-1-thread-2—6
pool-1-thread-1—4
pool-1-thread-2—7
pool-1-thread-1—5
pool-1-thread-2—8
pool-1-thread-1—6
pool-1-thread-2—9
pool-1-thread-1—7
pool-1-thread-1—8
pool-1-thread-1—9

Process finished with exit code 0

线程池创建了5个线程去执行这些任务

创建一个有上限的线程池,演示线程池复用的情况

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

        //1.获取线程池对象,创建一个上限为3的线程池
		
        ExecutorService pool1 = Executors.newFixedThreadPool(3);
        //2.提交任务
        pool1.submit(new MyRunnable1());

        pool1.submit(new MyRunnable1());
        pool1.submit(new MyRunnable1());
        pool1.submit(new MyRunnable1());
        pool1.submit(new MyRunnable1());

        //3.销毁线程池
        pool1.shutdown();
    }
}

执行结果

pool-1-thread-2—1
pool-1-thread-2—2
pool-1-thread-2—3
pool-1-thread-2—4
pool-1-thread-2—5
pool-1-thread-2—6
pool-1-thread-2—7
pool-1-thread-2—8
pool-1-thread-2—9
pool-1-thread-3—1
pool-1-thread-3—2
pool-1-thread-3—3
pool-1-thread-3—4
pool-1-thread-3—5
pool-1-thread-3—6
pool-1-thread-3—7
pool-1-thread-3—8
pool-1-thread-3—9
pool-1-thread-3—1
pool-1-thread-3—2
pool-1-thread-3—3
pool-1-thread-3—4
pool-1-thread-3—5
pool-1-thread-3—6
pool-1-thread-3—7
pool-1-thread-3—8
pool-1-thread-1—1
pool-1-thread-3—9
pool-1-thread-2—1
pool-1-thread-2—2
pool-1-thread-2—3
pool-1-thread-2—4
pool-1-thread-2—5
pool-1-thread-2—6
pool-1-thread-1—2
pool-1-thread-2—7
pool-1-thread-2—8
pool-1-thread-2—9
pool-1-thread-1—3
pool-1-thread-1—4
pool-1-thread-1—5
pool-1-thread-1—6
pool-1-thread-1—7
pool-1-thread-1—8
pool-1-thread-1—9

Process finished with exit code 0

thread-X 代表线程编号,当线程池容量上限为3时,执行5个任务,thread-X ,X为3

自定义线程池

定义时要定义

  1. 核心线程的数量
  2. 线程池中最大线程的数量
  3. 空闲时间(值),即临时线程空闲多长时间被弃用
  4. 空闲时间(单位),线程空闲时间的单位,如秒
  5. 阻塞队列,即排队的线程数
  6. 创建线程的方式
  7. 要执行的任务过多时的解决方案

核心线程为3,临时线程也为3,即线程池中最大的线程数量是6

自定义线程的工作流程

在这里插入图片描述

假如一开始只提交了三个任务,线程池就只创建3个线程去处理这3个任务。

如果提交了5个任务,线程池创建3个线程去处理3个任务,剩余的2个任务就会去排队等待,等有了空闲的线程,后面的2个任务才会去处理。

如果提交了8个任务,线程池创建3个线程去处理3个任务,如果定义了排队的队伍长度为3,那么第4,5,6个任务就会去排队等待,任务7.8才会创建临时线程去处理任务。即核心线程都被占用,排队的任务超过限定的队伍长度,才会创建临时线程。

如果提交了10个任务,线程池创建3个线程去处理3个任务,如果定义了排队的队伍长度为3,那么第4,5,6个任务就会去排队等待,任务7、8、9才会创建临时线程去处理任务,此时整个线程池已经是满负荷工作,核心线程、临时线程、阻塞队列全部被满额占用。那么超负荷的任务就会触发拒绝策略,默认的策略就是舍弃不要。另外还有其他的拒绝策略如下表。

任务拒绝策略

任务拒绝策略说明
ThreadPoolExecutor.AbortPolicy默认策略:丢弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardPolicy丢弃任务,但是不抛出异常,这是不推荐的做法
ThreadPoolExecutor.DiscardOldestPolicy抛弃队列中等待最久的任务,然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy调用任务的run方法绕过线程池直接执行

代码实现

public class MyRunnable1 implements Runnable{
    @Override
    public void run() {
            System.out.println(Thread.currentThread().getName()+"---");
    }
}
public class MyThreadPoolDemo {
    public static void main(String[] args) {

        //获取线程池对象,核心线程数量为3,最大线程数量是6,空闲线程最大存活时间,时间单位,任务队列长队为3,拒绝策略
        ThreadPoolExecutor pool2 = new ThreadPoolExecutor(3,//核心线程数量为3,
                6,//最大线程数量是6
                60,//空闲线程最大存活时间
                TimeUnit.SECONDS,//时间单位
                new ArrayBlockingQueue<>(3),//任务队列长队为3
                Executors.defaultThreadFactory(),//创建线程工厂
                new ThreadPoolExecutor.AbortPolicy()//拒绝策略,内部类
        );
		//提交10个任务
        pool2.submit(new MyRunnable1());
        pool2.submit(new MyRunnable1());
        pool2.submit(new MyRunnable1());
        pool2.submit(new MyRunnable1());
        pool2.submit(new MyRunnable1());
        pool2.submit(new MyRunnable1());

        pool2.submit(new MyRunnable1());
        pool2.submit(new MyRunnable1());
        pool2.submit(new MyRunnable1());
        pool2.submit(new MyRunnable1());

        pool2.shutdown();
    }
}

执行结果分析,10个任务拒绝了1个,并抛出了rejectedExecution异常,线程编号thread-X,最大达到了6

Exception in thread “main” java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@3b95a09c[Not completed, task = java.util.concurrent.Executors R u n n a b l e A d a p t e r @ 64 a 294 a 6 [ W r a p p e d t a s k = c o m . a t g u i g u . c l o u d . t h r e a d . M y R u n n a b l e 1 @ 7 e 0 b 37 b c ] ] r e j e c t e d f r o m j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r @ 6 a e 40994 [ R u n n i n g , p o o l s i z e = 6 , a c t i v e t h r e a d s = 6 , q u e u e d t a s k s = 3 , c o m p l e t e d t a s k s = 0 ] a t j a v a . b a s e / j a v a . u t i l . c o n c u r r e n t . T h r e a d P o o l E x e c u t o r RunnableAdapter@64a294a6[Wrapped task = com.atguigu.cloud.thread.MyRunnable1@7e0b37bc]] rejected from java.util.concurrent.ThreadPoolExecutor@6ae40994[Running, pool size = 6, active threads = 6, queued tasks = 3, completed tasks = 0] at java.base/java.util.concurrent.ThreadPoolExecutor RunnableAdapter@64a294a6[Wrappedtask=com.atguigu.cloud.thread.MyRunnable1@7e0b37bc]]rejectedfromjava.util.concurrent.ThreadPoolExecutor@6ae40994[Running,poolsize=6,activethreads=6,queuedtasks=3,completedtasks=0]atjava.base/java.util.concurrent.ThreadPoolExecutorAbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2065)
at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:833)
at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1365)
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:123)
at com.atguigu.cloud.thread.MyThreadPoolDemo.main(MyThreadPoolDemo.java:44)
pool-1-thread-2—
pool-1-thread-4—
pool-1-thread-2—
pool-1-thread-3—
pool-1-thread-2—
pool-1-thread-4—
pool-1-thread-1—
pool-1-thread-5—
pool-1-thread-6—

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值