java 笔记 - 多线程 (重点)

一、进程与线程的概念

进程:进程是程序的基本执行实体。【线程是进程里面的】
线程:线程是操作系统能够进行运算调度的最小单位。它被包含在进程中,是进程中的实际运作单位。(简单理解:应用软件中互相独立,可以同时运行的功能

程序的执行是需要时间的,多线程就是让cpu像牛马一样不间断的工作,在等待的时间也要去做其他事情,充分利用cpu来达到提高效率的目的,一句话:不许歇着,起来干活!

image.png

二、并发与并行的概念

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

image.png

三、多线程的三种实现方式

方式1.继承 Thread 类并重写run方法

myThread 类

package com.进程和多线程;

//多线程的第一种实现方式:继承 Thread 类并重写run方法
public class myThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+ " hello world");
        }
    }
}

测试类

package com.进程和多线程;

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

public class demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 方式1
        method1();
    }

    // 方式一:多线程的第一种实现方式:继承 Thread 类并重写run方法
    private static void method1() {
        System.out.println("--------------------------------- 方式一:多线程的第一种实现方式:继承 Thread 类并重写run方法 ---------------------------------");
        myThread t1 = new myThread();
        myThread t2 = new myThread();
        // 给线程取名字
        t1.setName("线程1");
        t2.setName("线程2");
        // 开启线程 - 可以看到控制台是交替执行的两个线程 - “并发”
        t1.start();
        t2.start();
    }


}

方式2.实现 Runnable 接口重写run方法

myThread2 类

package com.进程和多线程;

// 多线程的第二种实现方式:实现 Runnable 接口重写run方法
public class myThread2 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {

            // Thread.currentThread() 获取当前执行的线程
            System.out.println(Thread.currentThread().getName() + " hello world");
        }
    }
}

测试类

package com.进程和多线程;

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

public class demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 方式2
        method2();
    }

    // 方式二:多线程的第二种实现方式:实现 Runnable 接口重写run方法
    private static void method2(){
        System.out.println("--------------------------------- 方式二:多线程的第二种实现方式:实现 Runnable 接口重写run方法 ---------------------------------");

        // 定义一个任务
        myThread2 m = new myThread2();

        // 将任务传递给线程,表示执行这个任务一次
        Thread t1 = new Thread(m);
        // 将任务传递给线程,表示执行这个任务两次
        Thread t2 = new Thread(m);

        // 给线程取名字
        t1.setName("线程1");
        t2.setName("线程2");

        // 开启线程 - 可以看到控制台是交替执行的两个线程 - “并发”
        t1.start();
        t2.start();
    }
}

方式3.利用Callable接口和Future接口实现 - 注意:这种方式可以获取多线程的 “返回值”

myThread3 类,注意你想要的线程的返回值也就是 Callable 的泛型

package com.进程和多线程;

import java.util.concurrent.Callable;

/**
 * 想要获取多线程的返回值,那么使用第三种方式 利用Callable接口和Future接口实现
 * 多线程的第三种实现方式:
 * 1.定义一个类实现 Callable 接口重写call方法(有返回值的,表示多线程的运行结果)
 * 2.创建 Callable 的对象(表示多线程要执行的任务)
 * 3.创建 FutureTask 对象来管理多线程的运行结果
 * 4.创建线程对象
 * 5.在 FutureTask 里面获取多线程的运行结果
 */
public class myThread3 implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        // 求1-100的和
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

测试类

package com.进程和多线程;

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

public class demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 方式3
        method3();
    }

    // 方式三:利用Callable接口和Future接口实现 - 这种方式可以获取多线程的返回值
    private static void method3() throws ExecutionException, InterruptedException {
        System.out.println("--------------------------------- 方式三:多线程的第三种实现方式 - 利用Callable接口和Future接口实现 ---------------------------------");
        // 1.定义一个类实现 Callable 接口重写call方法(有返回值的,表示多线程的运行结果)
        // 2.创建 Callable 的对象(表示多线程要执行的任务)
        myThread3 m3 = new myThread3();
        // 3.创建 FutureTask 对象来管理多线程的运行结果
        FutureTask<Integer> ft = new FutureTask<>(m3);

        // 4.创建线程对象
        Thread t1 = new Thread(ft);
        t1.start();

        // 5.在 FutureTask 里面获取多线程的运行结果
        Integer result = ft.get();
        System.out.println(result);

    }
}

四、多线程中常用的成员方法

image.png

下面列出的是几个难以理解的方法

(1) 当前的线程对象 Thread.currentThread()

Thread.currentThread() 代表执行到这行代码时当前的线程对象,如果是main方法里面直接调用会发现有个main线程

当JVM虚拟机启动之后,会自动启动多条线程,其中一条线程就是main线程,他的作用就是去调用main方法并执行里面的代码。


image.png

(2) 线程的优先级相关方法 setPriority() 和 getPriority()

Java 在主流系统 如Windows/Linux上,线程调度是抢占式的。特点:

线程优先级(Priority):Java 线程有优先级(1~10,默认5),但不保证严格按优先级执行。高优先级线程更可能被调度器选中,也就是带权重的随机
时间片(Time Slicing):大多数现代操作系统(如Windows、Linux)使用时间片轮转的抢占式调度。每个线程执行一段时间后会被强制暂停,让其他线程运行。
不可控性:开发者无法精确控制线程切换的时机,由JVM和操作系统共同决定。

    private static void memberMethods() {
        // 采用的上面第二种线程实现方式 
        myThread2 m = new myThread2();
        Thread t1 = new Thread(m, "飞机");
        Thread t2 = new Thread(m, "坦克");
        System.out.println("默认线程优先级:" + t1.getPriority() + "--" + t2.getPriority());
        // 线程优先级是 1~10 之间
        t1.setPriority(1);
        t2.setPriority(8);
        t1.start();
        t2.start();
    }

(3) 守护线程 setDaemon() (备胎线程)

当其他非守护线程执行完毕,守护线程就会陆续结束,即使它的代码未完全执行完毕。
注意这个结束不是立即结束,是陆续结束。就比如有个聊天窗口,里面的文件传输设置为守护线程,当聊天窗口关闭,那么文件传输也就不用传了,可以停了。

image.png

(4) 礼让线程 Thread.yield() --- 了解,平时很少用

礼让线程就是把cpu的执行权让出去,然后重新开始和其他线程争夺cpu执行权,让几个线程尽可能的均匀执行。
比如这个例子,如果没有下面的礼让线程代码,那么执行结果可能是某个线程执行了很久才轮到其他线程,而有了礼让线程那么打印结果就会比较均匀。

image.png

(5) 插入线程 join() --- 了解,平时很少用

表示的是把某个线程插入到正在执行的线程“前”执行,也就是插个队,先执行这个线程再执行当前线程。

比如下面的代码,如果没有t.join()这行代码,那么t线程和main线程会抢夺执行权,交替执行!如果我想让t线程执行完毕后再去执行main线程后面的代码,那么给t线程插个队就行了。

image.png

五、线程的生命周期(六大状态)

image.png
image.png

问:sleep 方法会让线程睡眠,睡眠时间到了之后,立马就会执行下面的代码吗?
答:不会,需要重新抢到cpu的执行权才会执行

六、线程的安全问题

因为线程执行的时候有“随机性”,线程在执行代码的时候随时可能被其他线程抢走执行权,那么在“操作共享数据”的时候就有问题。
比如卖票的例子,总共100张票,有3个线程卖,那么可能出现卖的票重复或者超出范围的情况,因为线程的执行有随机性!

为了解决这个问题,那么出现了一个概念:同步代码块

(1)同步代码块

就像抢厕所一样,如果有人在里面那其他人就不能进去了,只能等里面的人处理完了出来了才能再抢这个厕所。


image.png

代码如下:
售票类 - SellTicket

package com.进程和多线程2_安全问题之卖票例子;

public class SellTicket extends Thread {

    // 表示这个类的所有对象都共享 ticket 数据
    static int ticket = 0;

    // synchronized 需要指定一个唯一对象,用 static 随便定义一个就行了,但是一般会使用这个类的字节码对象
    // static Object obj = new Object();

    @Override
    public void run() {
        while (true) {

            // 将共享代码用 synchronized 包裹,代表锁起来,当有线程在里面时其他线程不能执行里面的代码
            synchronized (SellTicket.class) {
                if (ticket >= 100) {
                    break;
                }
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket++;
                System.out.println(getName() + "正在卖第" + ticket + "张票!!");
            }

        }
    }
}

测试类

package com.进程和多线程2_安全问题之卖票例子;

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

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

特别注意这里 synchronized 里面的对象一定要是唯一的,这样才能锁起来。

(2)同步方法

image.png

将上面的 售票类 - SellTicket 改写成同步方法的方式,测试类不变

package com.进程和多线程2_安全问题之卖票例子;

public class SellTicket extends Thread {

    // 表示这个类的所有对象都共享 ticket 数据
    static int ticket = 0;


    // 方式二:同步方法的方式
    @Override
    public void run() {
        while (true) {
            // 执行同步方法
            if(extracted()) break;
        }
    }

    //synchronized 包裹的是同步方法
    static synchronized boolean extracted() {
        if (ticket >= 100) {
            return true;
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ticket++;
        System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票!!");
        return false;
    }
}

特别注意:这里是用的static synchronized修饰的,因为这个卖票类是直接继承的 Thread 类,我们在使用的时候根据这个类创建了三个不同的对象,这三个对象之间需要共享锁,因此需要用 static 修饰。
如果是实现 Runnable 接口这种方式来创建线程的,那就可以 不用static 修饰,只需要创建一次对象,如对象a,然后再创建三个Thread对象来共享这个对象a就行了。这里 synchronized 锁的对象就是this,也就是对象a。

(3)同步方法的扩展知识:StringBuilder 和 StringBuffer 的区别
StringBuilder 和 StringBuffer 在使用上基本是一模一样的,但 StringBuilder 对于多线程是不安全的,这个时候需要改为使用 StringBuffer。如果是单线程的就直接使用 StringBuilder 就行了

image.png

七、Lock 锁

相比同步代码块synchronized,Lock 可以自己来手动上锁,手动释放锁。注意:Lock 锁是接口不能直接实例化,需要使用他的实现类ReentrantLock来实例化。

image.png

将上面的 售票类 - SellTicket 改写成Lock 锁的方式,测试类不变

package com.进程和多线程2_安全问题之卖票例子;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellTicket extends Thread {

    // 表示这个类的所有对象都共享 ticket 数据
    static int ticket = 0;

    // 方式三:lock 方法的方式 start ---------

    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            // 加锁
            lock.lock();

            try {
                if (ticket >= 100) {
                    break;
                }
                Thread.sleep(10);
                ticket++;
                System.out.println(getName() + "正在卖第" + ticket + "张票!!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 释放锁 - 注意:break 后会执行 finally里面的代码后才终止循序
                // finally 的优先级高于 break/return(除非调用 System.exit() 或 JVM 崩溃)。
                lock.unlock();
            }
        }
    }
    // 方式三:lock 方法的方式 end ---------
}

这里有两个注意点:

1.Lock 这里是 static 修饰的,同样是因为这个类是继承的 Thread ,我们在使用的时候实例化了三次这个类,为了保证三个实例化对象都共享一把锁,所以需要使用 static
2.lock.unlock(); 释放锁是放到的 try catch 的 finally 里面,这里有个特性是 finally 的优先级高于 break/return(除非调用 System.exit() 或 JVM 崩溃)。所以当执行 break 的时候,会先执行 finally 里面的代码才会去终止 while 循环。

八、死锁的错误

死锁,不需要学习其他代码,这是一种程序的错误,我们需要理解并且避免使用它。死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,导致这些线程都无法继续执行下去,程序无法正常运行。

典型死锁示例:

image.png

要想不出现死锁,我们平时写代码的时候需要注意不要让锁出现嵌套的情况

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

image.png

方式一:简单实现

image.png

吃货和厨师的例子:

调度中心 - Desk (桌子)类

package com.进程和多线程3_等待唤醒机制;

public class Desk {
    /*
    * 作用:控制生产者消费者的执行
    * */

    // 是否有面条 0:没有面条 1:有面条
    public static int foodFlag = 0;

    // 总的要生产的个数
    public static int count = 10;

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

消费者 - Foodie (吃货) 类

package com.进程和多线程3_等待唤醒机制;

public class Foodie extends Thread {
    /*
    * 多线程的套路:
    * 1.循环
    * 2.同步代码块
    * 3.判断共享数据是否到了末尾(到了末尾)
    * 4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
    * */
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {
                // 吃完了跳出循环
                if(Desk.count == 0) {
                    break;
                }else{
                    // 先判断桌子上是否有面条
                    if(Desk.foodFlag == 0) {
                        // 如果没有,就等待
                        try {
                            Desk.lock.wait(); // 让当前线程和锁进行变绑定,后面通知的时候是通知的和锁绑定的线程
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{
                        // 把吃的总数 -1
                        Desk.count--;
                        // 如果有就开吃
                        System.out.println("吃货正在吃面条,还能吃"+ Desk.count + "碗!!!");
                        // 吃完之后,唤醒厨师继续做
                        Desk.lock.notifyAll();
                        // 修改桌子的状态
                        Desk.foodFlag = 0;                    }
                }
            }
        }
    }
}

生产者 - Cook (厨师)类

package com.进程和多线程3_等待唤醒机制;

public class Cook extends Thread {
    /*
     * 多线程的套路:
     * 1.循环
     * 2.同步代码块
     * 3.判断共享数据是否到了末尾(到了末尾)
     * 4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
     * */
    @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();
                    }
                }
            }
        }
    }
}

测试类

package com.进程和多线程3_等待唤醒机制;

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

        // 设置名字
        foodie.setName("吃货");
        cook.setName("厨师");

        // 启动线程
        foodie.start();
        cook.start();

    }
}

执行结果如下:
可以看到是abababab...这种有规律的执行顺序,也就是生产一份消费一份生产一份消费一份。。。这种

image.png

方式二:阻塞队列方式实现

相比上一种,这个生产者可以一次生产多个,直到放不下了才开始等待,消费者则是可以吃完多个,直到“管道“拿不出来了再等待。

image.png

image.png

注意这两个实现类:ArrayBlockingQueue 和 LinkedBlockingQueue,一个是有界的一个是无界的。

消费者 - Foodie 类

package com.进程和多线程4_阻塞队列方式实现;

import java.util.concurrent.ArrayBlockingQueue;

public class Foodie extends Thread {
    // 定义一个队列来接收和消费者公用的阻塞队列
    ArrayBlockingQueue<String> queue;

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

    @Override
    public void run() {
        while (true) {
            // 不断的从阻塞队列中获取面条
            try {
                // 注意:queue.take 里面有锁,这里不需要再定义
                String food = queue.take();
                // 注意下面这个打印语句是在锁外面执行的,因此运行后看起来的结果可能是同时执行了多次,但是不影响,因为没有操作共享数据
                System.out.println(food);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

生产者 - Cook 类

package com.进程和多线程4_阻塞队列方式实现;

import java.util.concurrent.ArrayBlockingQueue;

public class Cook extends Thread {
    // 定义一个队列来接收和消费者公用的阻塞队列
    ArrayBlockingQueue<String> queue;

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

    @Override
    public void run() {
        while (true) {
            // 不断的把面条放到阻塞队列里面
            try {
                // 注意:queue.put 里面有锁,这里不需要再定义
                queue.put("面条");
                //注意下面这个打印语句是在锁外面执行的,因此运行后看起来的结果可能是同时执行了多次,但是不影响,因为没有操作共享数据
                System.out.println("厨师放了一碗面条");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

test 测试类

package com.进程和多线程4_阻塞队列方式实现;

import java.util.concurrent.ArrayBlockingQueue;

public class test {
    public static void main(String[] args) {
        // 定义一个阻塞队列,注意这个队列必须是生产者和消费者共享的
        // 注意:ArrayBlockingQueue 是有界的,这里的参数1代表这个队列容量是1个,因此生产者只会生产1个就等待
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);

        // 定义线程并启动
        Cook cook = new Cook(queue);
        Foodie foodie = new Foodie(queue);
        cook.start();
        foodie.start();
    }
}

注意:
1.这个例子会一直执行,因为没有在生产者和消费者里面写终止循环的代码,这里只是演示阻塞队列的用法,实际也可以像方式一一样定义一个调度中心来进行判断。
2.注意这里的打印语句是在锁外面执行的,可能看到的结果是同时执行了多次,但是不影响,因为没有操作共享数据,这里只是打印的显示有问题。

方式三: 将等待、通知的逻辑写到调度中心里面去

这个方式和上一种方式有些类似。生产者只关心怎么生产数据,消费者只关心怎么消费数据, 将等待、通知的逻辑写到调度中心里面去。

package com.进程和多线程6_生产者消费者demo6;

import java.util.LinkedList;
import java.util.Queue;

public class test {
    public static void main(String[] args) {
        Buffer bf = new Buffer();
        Producer producer = new Producer(bf);
        Consumer consumer = new Consumer(bf);

        producer.start();
        consumer.start();
    }
}

// 生产者
class Producer extends Thread {
    private Buffer buf;

    public Producer(Buffer buf) {
        this.buf = buf;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                buf.add(i);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 消费者
class Consumer extends Thread {
    private Buffer buf;

    public Consumer(Buffer buf) {
        this.buf = buf;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                int poll = buf.poll();
                System.out.println("消费者 - " + Thread.currentThread().getName() + "消费了数据:" + poll);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Buffer {
    // 数据存储队列
    private Queue<Integer> queue = new LinkedList<>();
    // 这个队列的大小是5
    private int size = 5;

    // 添加数据
    public synchronized void add(int x) throws InterruptedException {
        // 数据满了则阻塞生产者生产数据
        if (queue.size() >= size) {
            wait();
        }
        // 数据不够则开始生产
        queue.add(x);
        // 通知消费者消费
        notify();
    }

    // 获取数据
    public synchronized int poll() throws InterruptedException {
        // 没有数据则等待生产者生产数据
        if (queue.size() == 0) {
            wait();
        }
        // 有数据就获取,获取了再通知消费者生产
        int x = queue.poll();
        // 通知生产者继续生产
        notify();
        return x;
    }
}

十、多线程的内存图解

注意:每个线程都有自己的栈空间,而堆空间只有一个

image.png

十一、线程池

Java线程池是Java并发编程中非常重要的一个组件,它能够有效地管理线程的生命周期,减少线程创建和销毁的开销,提高系统性能。
通常是创建一个线程池,这个线程池不会销毁,当需要线程的时候在里面拿来用,用完还回去,后面的任务来了如果线程池没有空闲线程,而且没有达到上限,那么会新创建线程。如果没有空闲线程并且达到上限了就会等待,直到有空闲线程了再执行这个任务。


image.png

方式1:用java的工具类来创建线程池

image.png

myRunnable 类

package com.进程和多线程6_线程池;

public class myRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}

测试类

package com.进程和多线程6_线程池;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class test {
    public static void main(String[] args) throws InterruptedException {
        method1();
        method2();
    }

    // 没有上限的线程池
    private static void method1() throws InterruptedException {
        // 创建线程池对象
        ExecutorService pool = Executors.newCachedThreadPool();
        // 提交任务
        pool.submit(new myRunnable());
        //Thread.sleep(100); // 睡眠上个线程可以看到线程是复用的
        pool.submit(new myRunnable());

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

    // 有上限的线程池
    private static void method2(){
        // 创建线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(3);
        // 提交任务
        pool.submit(new myRunnable());
        //Thread.sleep(100); // 睡眠上个线程可以看到线程是复用的
        pool.submit(new myRunnable());
        pool.submit(new myRunnable());
        pool.submit(new myRunnable());
        pool.submit(new myRunnable());

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

方式2:更灵活的方式 - 自定义线程池 (ThreadPoolExecutor)

这个类的构造方法的参数太多,这里类比饭店例子来理解。


image.png
image.png

测试类:

package com.进程和多线程6_线程池;

import java.util.concurrent.*;

public class test {
    public static void main(String[] args) throws InterruptedException {
        method3();
    }
    // 方式三:自定义线程池 - 就是参数太多了点
    private static void method3(){
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3, // 核心线程数量,不能小于0
                6, // 最大线程数,>= 核心线程数量 (临时线程数 = 最大线程数 - 核心线程数)
                60, // 空闲线程最大存活时间
                TimeUnit.SECONDS, // 时间单位,这里是秒,注意:需要用 TimeUnit
                new LinkedBlockingQueue<>(), // 任务队列, ArrayBlockingQueue 或者 LinkedBlockingQueue
                Executors.defaultThreadFactory(), // 创建线程工厂
                new ThreadPoolExecutor.AbortPolicy() // 任务的拒绝策略
        );

        // 提交任务
        pool.submit(new myRunnable());
        pool.submit(new myRunnable());
    }
}

那么什么时候创建临时线程?

答:核心线程都在忙,并且排队的队伍已经满了

任务的执行顺序一定是先提交的先执行吗?

答:不是,就像下面图里面展示的,7和8任务先于任务4,5,6执行。先得排队排满了才会去创建临时线程执行后面的任务。


image.png

任务拒绝策略

当任务超出了(核心线程数 + 临时线程数 + 队伍长度)就会触发任务拒绝策略。


image.png

image.png
image.png

十二、线程池设置多大合适

1.获取最大并行数量

首先4核8线程的电脑的最大并行数量为8,就是最理想的情况下同时可以做8件事。(4核8线程的CPU在操作系统层面可以同时管理8个线程,通过超线程技术让4个物理核心模拟出8个逻辑处理器的效果,在理想情况下可以近似实现8个任务的并发执行,但实际并行计算能力仍以4个物理核心为基础。)

在java里面可以用下面的代码来获取java 虚拟机可用的处理器数目

        int i = Runtime.getRuntime().availableProcessors();
        System.out.println("java 虚拟机可用的处理器数目"+i);

2.计算公式

image.png

CPU 密集型运算是指你的项目是偏计算的,I/O密集型运算是指你的项目读取数据库或者本地文件的操作比较多。根据不同场景来套上面的公式达到cpu最大利用率。

十三、多线程扩展内容

通常面试才用的到,初学略过。。。


image.png

参考 :
黑马教程
生产者消费者模型

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容