多线程基础总结

news2024/11/25 19:54:47

1. 为什么要有多线程?

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

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

  1. 什么是多线程?

    有了多线程,我们就可以让程序同时做多件事情。

    1. 多线程的作用?

    提高效率

    1. 多线程的应用场景?

    只要你想让多个事件同时运行就需要多线程

    比如:软件中的耗时操作、所有的聊天软件、所有的服务器。

2. 多线程的两个概念?

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

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

3. 多线程的实现方式

1. 继承 Thread 类的方法进行实现

  	2. 实现 Runnable 接口的方式进行实现
            	3. 利用 Callable 接口和 Future 接口方式实现

多线程实现方式1-代码示例:

public class MyThread extends Thread{
    @Override
    public void run() {
        // 线程要执行的代码
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "hello world");
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        /**
         * 多线程的第一种启动方式
         *      1. 自己定义一个类继承 Thread 类
         *      2. 重写 run 方法
         *      3. 创建子类对象,并启动线程
         */
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("线程1");
        t2.setName("线程2");

        // 开启线程
        t1.start();
        t2.start();
    }
}

多线程实现方式2-代码示例:

public class MyRun implements Runnable{
    @Override
    public void run() {
        // 线程要执行的代码
        for (int i = 0; i < 100; i++) {
            // 获取当前线程对象
            System.out.println(Thread.currentThread().getName() + "hello world");
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        /**
         * 多线程的第二种实现方式
         *      1. 自己定义一个类实现 Runnable 接口
         *      2. 重写里面的 run 方法
         *      3. 创建自己的类的对象。
         *      4. 创建一个 Thread 类的对象,并开启多线程
         */
        // 创建 MyRun 对象
        // 表示多线程要执行的任务
        MyRun mr = new MyRun();
        // 创建线程对象
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        // 给线程设置名字
        t1.setName("线程一");
        t2.setName("线程二");
        // 开启线程
        t1.start();
        t2.start();
    }
}

多线程实现方式3-代码示例:

public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        // 求 1-100 之间的和
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum = sum + i;
        }
        return sum;
    }
}
public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /**
         * 多线程第三种实现方式:
         *      特点:可以获取到多线程运行的结果
         *
         *      1. 创建一个类 MyCallable 实现 Callable 接口
         *      2. 重写 call(是有返回值的,表示多线程运行的结果)
         *      3. 创建 MyCallable 的对象(表示多线程要执行的任务)
         *      4. 创建 FutureTask 的对象(作用管理多线程运行的结果)
         *      5. 创建 Thread 类的对象,并启动(表示线程)
         */

        // 创建 MyCallable 的对象(表示多线程要执行的任务)
        MyCallable mc = new MyCallable();
        // 创建 FutureTask 的对象(作用管理多线程运行的结果)
        FutureTask<Integer> ft = new FutureTask<>(mc);
        // 创建线程的对象
        Thread t1 = new Thread(ft);
        // 开启线程
        t1.start();
        // 获取多线程运行的结果
        Integer result = ft.get();
        System.out.println(result);
    }
}

在这里插入图片描述

4. 常见的成员方法

setName && currentThread && sleep

public class MyThread extends Thread{
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        /**
         * void setName(String name)        设置线程的名字(构造方法也可以设置名字)
         *      细节:
         *          1、如果我们没有给线程名字,线程也有默认的名字的
         *              格式:Thread-X(X 序号,从 0 开始的)
         *          2、如果我们要给线程设置名字,可以用 set 方法进行设置,也可以用构造方法设置
         *
         * static Thread currentThread()    获取当前线程对象
         *      细节:
         *          当 JVM 虚拟机启动后,会自动启动多条线程
         *          当其中有一条线程就叫做 main 线程
         *          它的主要作用发就是调用 main 方法,并执行里面的代码
         *          在以前,我们写的所有代码,其实就是运行在 main 线程当中。
         * static void sleep(long time)     让线程休眠指定的时间,单位为毫秒
         *      细节:
         *          1、那条线程执行到这个方法,那么哪条线程就会停留对应的时间
         *          2、方法的参数:就表示睡眠的时间,单位毫秒(1秒 = 1000毫秒)
         *          3、当时间到了之后,线程就会自动醒来,继续执行下面的其他代码
         *
         */

        //setName
        /*
        // 1. 创建线程对象
        MyThread t1 = new MyThread("飞机");
        MyThread t2 = new MyThread("坦克");

        // 2. 开启线程
        t1.start();
        t2.start();*/

        // 哪条线程执行到这个方法,此时获取的就是哪条线程的镀锡
        /*Thread t = Thread.currentThread();
        String name = t.getName();
        System.out.println(name); // main*/

        // sleep
        /*System.out.println("111111111111111");
        Thread.sleep(5000);
        System.out.println("222222222222222");*/
    }
}

setPriority && getPriority

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" +i);
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        /**
         *  setPriority(int newPriority)    设置线程的优先级
         *  final int getPriority()         获取线程的优先级
         */
        // 创建线程要执行的参数对象
        MyRunnable mr = new MyRunnable();
        // 创建线程对象
        Thread t1 = new Thread(mr, "飞机");
        Thread t2 = new Thread(mr, "坦克");

        t1.setPriority(1);
        t2.setPriority(10);

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

    }
}

守护线程

public class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}
public class MyThread2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        /**
         * final void setDaemon(boolean on)     设置为守护线程
         *      细节:
         *          当其他的非守护线程执行完毕之后,守护线程就会陆续结束
         *      通俗易懂:
         *          当女神线程结束了,那么备胎也没有存在的必要了
         */
        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        t1.setName("女神");
        t2.setName("备胎");

        // 把第二个线程设置为守护线程(备胎线程)
        t2.setDaemon(true);

        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);

            // 表示出让当前 CPU 的执行权,让出执行权的线程也会重新参与抢夺。
            Thread.yield();
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        /**
         * public static void yield()       出让线程/礼让线程
         */
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        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);
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        /**
         * public final void join()     插入线程/插队线程
         */
        MyThread t = new MyThread();
        t.setName("土豆");
        t.start();

        // 把 t 线程插入到当前线程之前。
        // t:土豆
        // 当前线程:main
        t.join();

        // 执行在 main 线程中的方法
        for (int i = 0; i < 10; i++) {
            System.out.println("main 线程" + i);
        }
    }
}

线程的生命周期

在这里插入图片描述

5. 线程安全问题

public class MyThread extends Thread{

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

    @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 ThreadDemo {
    public static void main(String[] args) {
        /**
         * 需求:
         *      某电影院目前正在上映国产大片,共 100 张票,而它有 3 个窗口卖票,请设计一个程序模拟该电影院卖票。
         */
        // 创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        // 起名字
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        // 开启线程
        t1.start();
        t2.start();
        t3.start();

    }
}

上面的代码存在以下问题:

  1. 超卖:线程1、2、3都有可能在同时查看剩余票数时,都看到还有可卖的票,于是同时执行买票操作。
  2. 卖出相同的票:因为在线程1、2、3都有可能同一时间进行买票操作

同步代码块解决线程安全问题

格式:

synchronized () {
    操作共享数据的代码
}

特点1:锁默认打开,有一个线程进去了,锁会自动关闭

特点2:里面的代码全部执行完毕,线程出来,锁自动打开

修改之后的线程代码

public class MyThread extends Thread{

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

    // 加 static 保证 obj 是唯一的。(锁对象要保证是唯一的)
    static Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            // 同步代码块
            synchronized (obj) { // 这里的 obj 也可以替换成 MyThread.class(MyThread 的字节码文件),因为 MyThread 的字节码文件也是唯一的。 
                if (ticket < 100) {
                    try {
                        sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket++;
                    System.out.println(getName() + "正在卖" + ticket + "张票!!!");
                } else {
                    break;
                }
            }
        }
    }
}

注意:锁要加在 while 循环的里面,如果加在循环的外面,某个线程抢到锁后,会一直执行循环内的代码,直到这个线程把所有的票买完。因为线程抢到票之后,就算其它线程也抢到票,也只能在循环锁外面等着。

同步方法

格式:修饰符 synchronized 返回值类型 方法名(方法参数) {…}

特点1:同步方法是锁住方法里面的代码

特点2:锁对象不能自己指定

  • 非静态:this
  • 静态:当前类的字节码文件对象

StringBuffer 与 StringBuilder 的线程安全区别

  • StringBuffer 是线程安全的。因为在 StringBuffer 中有 synchronized 关键字。

  • 而 StringBuilder 则不是线程安全的。
    在这里插入图片描述

    那对 StringBuffer 和 StringBuilder 我们如何选择?

    • 代码是单线程的,不涉及多线程、线程安全的问题,那么选择 StringBuilder 就好了。
    • 如果是多线程,设计线程安全问题,那么可以选择 StringBuffer 。

6. 死锁

6.1 锁 lock

  1. 示例代码

    // 创建锁对象
    Lock lock = new ReentrantLock();
    // 设置锁
    lock.lock();
    // 释放锁
    lock.unlock();
    
  2. 锁的应用

    public class MyThread extends Thread{
    
        static int ticket = 0;
    
        // 加 static 使每个线程都用统一把锁
        static Lock lock = new ReentrantLock();
    
        @Override
        public void run() {
            // 1. 循环
            while (true) {
               // 2. 同步代码块
                lock.lock();
                try {
                    // 3. 判断
                    if (ticket == 100) {
                        break;
                    } else {
                        // 4. 判断
                        Thread.sleep(10);
                        ticket++;
                        System.out.println(getName() + "在卖第" + ticket + "张票");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
    
    public class ThreadDemo {
        public static void main(String[] args) {
            /**
             * 需求:
             *      某电影院目前正在上映国产大片,共 100 张票,而它有 3 个窗口卖票,请设计一个程序模拟该电影院卖票。
             *
             *      利用同步方法完成
             *
             */
    
            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();
        }
    }
    

6.2 死锁

  • 死锁示例代码
public class MyThread extends Thread{

    static Object objA = new Object();
    static Object objB = new Object();

    @Override
    public void run() {
        // 1. 循环
        while (true) {
            if ("线程A".equals(getName())) {
                synchronized (objA) {
                    System.out.println("线程 A 拿到了 A 锁,准备拿 B 锁");
                    synchronized (objB) {
                        System.out.println("线程 A 拿到了 B 锁,顺利执行完一轮");
                    }
                }
            } else if ("线程B".equals(getName())) {
                if ("线程B".equals(getName())) {
                    synchronized (objB) {
                        System.out.println("线程 B 拿到了 B 锁,准备拿 A 锁");
                        synchronized (objA) {
                            System.out.println("线程 B 拿到了 A 锁,顺利执行完一轮");
                        }
                    }
                }
            }
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        /**
         * 死锁
         */
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("线程A");
        t2.setName("线程B");

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

执行这个代码就会出现死锁的情况。

7. 生产者和消费者

7.1 等待唤醒机制

等待唤醒机制思路(Desk、Cook、Foodie)
在这里插入图片描述

示例代码(写法一):

public class Desk {

    /**
     * 作用:控制生产者和消费者的执行
     */

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

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

    // 锁对象
    public static Object lock = new Object();
}
public class Cook extends Thread{
    @Override
    public void run() {
        /**
         * 1. 循环
         * 2. 同步代码块
         * 3. 判断共享数据是否到了尾声(到了尾声)
         * 4. 判断共享数据是否到了尾声(没有到尾声,执行核心逻辑)
         */

        while (true) {
            synchronized (Desk.lock) {
                if (Desk.count == 0) {
                    break;
                } else {
                    // 判断桌子上是否有食物
                    // 如果有,就等待
                    if (Desk.foodFlog == 1) {
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        // 如果没有,就制作食物
                        System.out.println("厨师做了一碗面条");
                        // 修改桌子上的食物状态
                        Desk.foodFlog = 1;
                        // 叫醒等待的消费者开吃
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}
public class Foodie extends Thread{

    @Override
    public void run() {
        /**
         * 1. 循环
         * 2. 同步代码块
         * 3. 判断共享数据是否到了末尾(到了末尾)
         * 4. 判断共享数据是否到了末尾(没到末尾,执行核心逻辑)
         */

        while (true) {
            synchronized (Desk.lock) {
                if (Desk.count == 0) {
                    break;
                } else {
                    // 先判断桌子上是否有面条
                    if (Desk.foodFlog == 0) {
                        // 如果没有,就等待
                        try {
                            Desk.lock.wait();  // 让当前线程跟锁进行绑定
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        // 把吃的总数 -1
                        Desk.count--;
                        // 如果有,就开吃
                        System.out.println("吃货正在吃面条,还能再吃" + Desk.count + "碗!!!");
                        // 吃完之后,唤醒厨师继续做
                        Desk.lock.notifyAll();
                        // 修改桌子的状态
                        Desk.foodFlog = 0;
                    }
                }
            }
        }
    }
}

示例代码(写法二 — 阻塞队列方式实现):

​ 阻塞队列的继承结构
在这里插入图片描述

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("面条");
                System.out.println("厨师放了一碗面条");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Foodie extends Thread{

    ArrayBlockingQueue<String> queue;

    public Foodie(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 ThreadDemo {
    public static void main(String[] args) {
        /**
         * 需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
         *
         * 细节:
         *      生产者和消费者必须使用同一个阻塞队列
         */

        // 1. 创建阻塞队列的对象
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);

        // 2. 创建线程的对象,并把阻塞队列传递过去
        Cook c = new Cook(queue);
        Foodie f = new Foodie(queue);

        // 3. 开启线程
        c.start();
        f.start();
    }
}

7.2 线程的状态

在这里插入图片描述

但是再 JVM 里面是没有定义运行状态的
在这里插入图片描述

8. 练习

8.1 多线程实现抢红包

public class MyThread extends Thread{
    // 共享数据
    // 100 块,分成了3个红包
    static double money = 100;

    static int count = 3;

    // 最小的中奖金额
    static final double MIN = 0;

    @Override
    public void run() {
        // 同步代码块
        synchronized (MyThread.class) {
            if (count == 0) {
                // 判断,共享数据是否到了末尾(已经到末尾)
                System.out.println(getName() + "没有抢到红包!");
            } else {
                // 判断,共享数据是否到了末尾(没有到末尾)
                // 定义一个变量,表示中奖金额
                double prize = 0;
                if (count == 1) {
                    // 表示此时是最后一个红包
                    // 就无需随机,剩余所有的钱都是中奖金额
                    prize = money;
                } else {
                    // 表示第一次,第二次(随机)
                    Random r = new Random();
                    double bounds = money - (count - 1) * MIN;
                    prize = r.nextDouble(bounds);
                    if (prize < MIN) {
                        prize = MIN;
                    }
                }
                // 从 money 中去掉当前中奖的金额
                money = money - prize;
                // 红包的个数 -1
                count--;
                // 本次红包的信息进行打印
                System.out.println(getName() + "抢到了" + prize + "元");
            }
        }

    }
}
public class Test {
    public static void main(String[] args) {
        /**
         * 微信中的抢红包也用了多线程。
         * 假设:100块,分成了3个包,现在有5个人去抢。
         * 其中,红包是共享数据。
         * 5个人是5条线程
         * 打印结果如下:
         *      xxx 抢到了 xxx 元
         *      xxx 抢到了 xxx 元
         *      xxx 没抢到
         *      xxx 没抢到
         *
         */

        // 创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        MyThread t4 = new MyThread();
        MyThread t5 = new MyThread();

        // 给线程设置名字
        t1.setName("小A");
        t2.setName("小B");
        t3.setName("小C");
        t4.setName("小D");
        t5.setName("小E");

        // 启动线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();

    }
}

8.2 多线程实现抽奖

public class MyThread extends Thread{

    ArrayList<Integer> list;

    public MyThread(ArrayList<Integer> list) {
        this.list = list;
    }

    @Override
    public void run() { // 1  // 2
        // 循环
        // 同步代码块
        // 判断
        // 判断
        while (true) {
            synchronized (MyThread.class) {
                if (list.size() == 0) {
                    break;
                } else {
                    // 继续抽奖
                    Collections.shuffle(list);
                    int price = list.remove(0);
                    System.out.println(getName() + "又产生了一个 " + price + " 元大奖");
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        /**
         * 有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{18,5,20,50,100,200,500,800,2,80,300,700);
         * 创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
         * 随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
         *      每次抽出一个奖项就打印一个(随机)
         * 抽奖箱1 又产生了一个 10 元大奖
         * 抽奖箱1 又产生了一个 100 元大奖
         * 抽奖箱1 又产生了一个 200 元大奖
         * 抽奖箱1 又产生了一个 800 元大奖
         * 元大奖抽奖箱2 又产生了一个 700
         * ......
         */

        // 创建奖池
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list, 18,5,20,50,100,200,500,800,2,80,300,700);

        // 创建线程
        MyThread t1 = new MyThread(list);
        MyThread t2 = new MyThread(list);

        // 设置名字
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");

        // 启动线程
        t1.start();
        t2.start();
    }
}

8.3 多线程统计并求最大值(解法1)

public class MyThread extends Thread{

    ArrayList<Integer> list;

    public MyThread(ArrayList<Integer> list) {
        this.list = list;
    }

    // 线程1
    static ArrayList<Integer> list1 = new ArrayList<>();

    // 线程2
    static ArrayList<Integer> list2 = new ArrayList<>();


    @Override
    public void run() { // 1  // 2
        // 循环
        // 同步代码块
        // 判断
        // 判断
        while (true) {
            synchronized (MyThread.class) {
                if (list.size() == 0) {
                    if ("抽奖箱1".equals(getName())) {
                        System.out.println("抽奖箱1" + list1);
                    } else {
                        System.out.println("抽奖箱2" + list2);
                    }
                    break;
                } else {
                    // 继续抽奖
                    Collections.shuffle(list);
                    int price = list.remove(0);
                    if ("抽奖箱1".equals(getName())) {
                        list1.add(price);
                    } else {
                        list2.add(price);
                    }
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        /**
         * 有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80,300,700};
         * 创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
         * 随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
         * 每次抽的过程中,不打印,抽完时一次性打印(随机)     在此次抽奖过程中,抽奖箱1总共产生了6个奖项。
         *      分别为: 10,20,100,500,2,300最高奖项为300元,总计额为932元
         * 在此次抽奖过程中,抽奖箱2总共产生了6个奖项。
         *      分别为: 5,50,200,800,80,700最高奖项为800元,总计额为1835元
         */

        // 创建奖池
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list, 18,5,20,50,100,200,500,800,2,80,300,700);

        // 创建线程
        MyThread t1 = new MyThread(list);
        MyThread t2 = new MyThread(list);

        // 设置名字
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");

        // 启动线程
        t1.start();
        t2.start();

    }
}

8.4 多线程统计并求最大值(解法2)

public class MyThread extends Thread{

    ArrayList<Integer> list;

    public MyThread(ArrayList<Integer> list) {
        this.list = list;
    }

    @Override
    public void run() {
        ArrayList<Integer> boxList = new ArrayList<>();
        while (true) {
            synchronized (MyThread.class) {
                if (list.size() == 0) {
                    System.out.println(getName() + boxList);
                    break;
                } else {
                    // 继续抽奖
                    Collections.shuffle(list);
                    int price = list.remove(0);
                    boxList.add(price);
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        /**
         * 有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80,300,700};
         * 创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”
         * 随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
         * 每次抽的过程中,不打印,抽完时一次性打印(随机)     在此次抽奖过程中,抽奖箱1总共产生了6个奖项。
         *      分别为: 10,20,100,500,2,300最高奖项为300元,总计额为932元
         * 在此次抽奖过程中,抽奖箱2总共产生了6个奖项。
         *      分别为: 5,50,200,800,80,700最高奖项为800元,总计额为1835元
         */

        // 创建奖池
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list, 18,5,20,50,100,200,500,800,2,80,300,700);

        // 创建线程
        MyThread t1 = new MyThread(list);
        MyThread t2 = new MyThread(list);

        // 设置名字
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");

        // 启动线程
        t1.start();
        t2.start();

    }
}

8.4 多线程之间的比较

public class MyCallable implements Callable<Integer> {

    ArrayList<Integer> list;

    public MyCallable(ArrayList<Integer> list) {
        this.list = list;
    }

    @Override
    public Integer call() throws Exception {
        ArrayList<Integer> boxList = new ArrayList<>();
        while (true) {
            synchronized (MyCallable.class) {
                if (list.size() == 0) {
                    System.out.println(Thread.currentThread().getName() + boxList);
                    break;
                } else {
                    // 继续抽奖
                    Collections.shuffle(list);
                    int price = list.remove(0);
                    boxList.add(price);
                }
            }
            Thread.sleep(10);
        }
        // 把集合中的最大值返回
        if (boxList.size() == 0) {
            return null;
        } else {
            return Collections.max(boxList);
        }
    }
}
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /**
         * 有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80,300,700};
         * 创建两个抽奖箱(线程)设置线程名称分别为     "抽奖箱1","抽奖箱2"
         * 随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
         * 在此次抽奖过程中,抽奖箱1总共产生了6个奖项,分别为: 10,20,100,500,2,300
         *      最高奖项为300元,总计额为932元
         *
         * 在此次抽奖过程中,抽奖箱2总共产生了6个奖项,分别为: 5,50,200,800,80,700
         *      最高奖项为800元,总计额为1835元
         *
         * 在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元
         *  核心逻辑:获取线程抽奖的最大值(看成是线程运行的结果)
         * 以上打印效果只是数据模拟,实际代码运行的效果会有差异
         */

        // 创建奖池
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list, 10,5,20,50,100,200,500,800,2,80,300,700);

        // 创建多线程要运行的参数对象
        MyCallable mc = new MyCallable(list);

        // 创建多线程运行结果的管理对象
        FutureTask<Integer> ft1 = new FutureTask<>(mc);
        FutureTask<Integer> ft2 = new FutureTask<>(mc);

        // 创建线程对象
        Thread t1 = new Thread(ft1);
        Thread t2 = new Thread(ft2);

        // 设置名字
        t1.setName("抽奖箱1");
        t2.setName("抽奖箱2");

        // 开启线程
        t1.start();
        t2.start();

        Integer max1 = ft1.get();
        Integer max2 = ft2.get();

        System.out.println(max1);
        System.out.println(max2);

        // 在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元
        System.out.println("在此次抽奖过程中,抽奖箱"+(ft1.get()==800?t1.getName():t2.getName())+"中产生了最大奖项,该奖项金额为800元");
    }
}

9. 线程池

9.1 以前写多线程的弊端

  • 弊端1:用到线程就得创建
  • 弊端2:用完之后线程就消失

9.3 线程池主要核心原理

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

9.3 线程池代码实现

  1. 创建线程池
  2. 提交任务
  3. 所有任务全部执行完毕,关闭线程池

newCachedThreadPool 演示

public class MyThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException {
        /**
         * public static ExecutorService newCachedThreadPool()              创建一个没有上限的线程池
         * public static ExecutorService newFixedThreadPool(int nThreads)   创建有上线的线程池
         */

        // newCachedThreadPool 演示

        // 1. 获取线程池的对象
        ExecutorService pool1 = Executors.newCachedThreadPool();

        Thread.sleep(1000);

        // 2. 提交任务
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
        pool1.submit(new MyRunnable());

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

    }
}
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "---");
    }
}

在这里插入图片描述

运行效果:可以看到会一直在服用线程池中的线程1。

newFixedThreadPool 演示

public class MyThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException {
        /**
         * public static ExecutorService newCachedThreadPool()              创建一个没有上限的线程池
         * public static ExecutorService newFixedThreadPool(int nThreads)   创建有上线的线程池
         */

        // newFixedThreadPool 演示
        // 1. 获取线程池的对象
        ExecutorService pool1 = Executors.newFixedThreadPool(3);

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

        // 3. 销毁线程池
//        pool1.shutdown();
    }
}
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}

在这里插入图片描述
在运行的结果可以看到虽然 new 的线程大于3个,但是实际生成的线程只有3个。

或者我们可以使用 DEBUG 的方式查看结果
在这里插入图片描述

在长度为3的线程池中,创建了4个线程之后,线程池的长度为3,在外面的等待的任务数为1。

9.4 自定义线程池详解

在这里插入图片描述
在这里插入图片描述
注意:Java 默认的任务拒绝策略是 AbortPolicy,默认策略:丢弃任务并抛出 PejectdExecutionException 异常

拒接策略
在这里插入图片描述
创建自定义线程的构造方法参数解析:
在这里插入图片描述
自定义线程池小结:

  1. 当核心线程满时,再提交任务就会排队
  2. 当核心线程满,队伍满时,会创建临时线程
  3. 当核心线程满时,队伍满,临时线程满时,会触发任务策略
学习视频:https://www.bilibili.com/video/BV1LG4y1T7n2?p=1&vd_source=6108736e361d963b64f872fefb8bc1e7

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/519518.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java学习路线【看看ChatGPT怎么说】

目录 1、介绍情况2、路线简述3、Java初学者路线4、Java高级开发路线5、安卓开发 1、介绍情况 本文主体内容是chatGPT生成的&#x1f609; 先说结论&#xff1a;chatGPT写出来的路线&#xff0c;深度比较一般&#xff0c;但是对于初学者而言&#xff0c;具有不错的参考价值。…

【机器学习】XGBoost 详细解读 (集成学习_Boosting_GBM)

【机器学习】XGBoost 详细解读 &#xff08;集成学习_Boosting_GBM&#xff09; 文章目录 【机器学习】XGBoost 详细解读 &#xff08;集成学习_Boosting_GBM&#xff09;1. 介绍2. 基本原理3. 目标函数&#xff08;二阶泰勒展开求解&#xff09;3.1 基础的目标函数3.2 二阶泰勒…

error: LNK2001: 无法解析的外部符号 “public: virtual struct QMetaObject const * __cdecl

Qt系列文章目录 文章目录 Qt系列文章目录前言一、QtCreator中qmake命令是什么&#xff1f;2.解决 前言 我在代码中加入了对应的信号和槽&#xff0c;但编译仍然报错&#xff1a; #ifndef PROJECTWIN_H #define PROJECTWIN_Hnamespace Ui { class ProjectWin; }ProjectWin类声…

Google Bard使用初体验,与ChatGPT比较到底怎么样

文章目录 Google Bard 介绍如何使用Google bardbard和ChatGPT3.5的区别 本文讲述了Google bard的入门教程和使用技巧&#xff0c;并且与竞争对手ChatGPT进行了一个全方面的比较。这是 Google 不能输的战役&#xff0c;也是全面 AI 的时刻。 Google Bard 介绍 Google Bard已经于…

【数据结构】链表(C语言)

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c语言系列专栏&#xff1a;c语言之路重点知识整合 &#x…

JUC之集合类

JUC包提供了一些并发安全的集合类&#xff0c;用于在多线程环境下进行共享数据的操作&#xff0c;以解决多线程间的竞争条件和线程安全问题。 CopyOnWriteArrayList 相当于线程安全的ArrayList public class ListTest {public static void main(String[] arge){List<Strin…

【项目-前后端交互-项目】表白墙【servlet实践】

【项目—前后端交互 案例】表白墙 代码示例: 服务器版表白墙1. 准备工作2. 约定前后端交互接口3. 实现服务器端代码创建 Message 类创建 MessageServlet 类 4. 调整前端页面代码5. 数据存入文件.6. 数据存入数据库1) 在 pom.xml 中引入 mysql 的依赖2) 创建数据库, 创建 messag…

ModuleNotFoundError: No module named ‘Multiscaledeformableattention‘

在实现DINO Detection方法时&#xff0c;我们可能会遇到以上问题。因为在DeformableAttention模块&#xff0c;为了加速&#xff0c;需要自己去编译这个模块。 如果你的环境变量中能够找到cuda路径&#xff0c;使用正确的torch版本和cuda版本的话&#xff0c;这个问题很容易解…

代码随想录算法训练营第三十九天 | 不同路径(挺简单的)

62.不同路径 文档讲解&#xff1a;代码随想录 (programmercarl.com) 视频讲解&#xff1a;动态规划中如何初始化很重要&#xff01;| LeetCode&#xff1a;62.不同路径_哔哩哔哩_bilibili 状态&#xff1a;能直接做出来。 思路 机器人从(1 , 1) 位置出发&#xff0c;到(m, n)终…

对抗训练方法:保卫人工智能的盾牌

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

【纳什博弈、ADMM】基于纳什博弈和交替方向乘子法的多微网主体能源共享研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

ch07-Pytorch的训练技巧

ch07-Pytorch的训练技巧 0.引言1.模型保存与加载1.1.序列化与反序列化1.2.PyTorch 中的模型保存与加载1.3.模型的断点续训练 2.模型 Finetune2.1.Transfer Learning & Model Finetune2.2.PyTorch中的Finetune 3.使用 GPU 训练模型3.1.CPU与GPU3.2.数据迁移至GPU3.3. 多 GPU…

mac下安装cnpm淘宝镜像

在mac安装cnpm时&#xff0c;输入npm install -g cnpm -registryhttps://registry.npm.taobao.org 报错&#xff1a; npm ERR! code EACCES npm ERR! syscall mkdir npm ERR! path /usr/local/lib/node_modules/cnpm npm ERR! errno -13 npm ERR! Error: EACCES: permission de…

单细胞 | label transfer with Seurat4(未知细胞映射到注释好的细胞图谱)

场景&#xff1a;把新的细胞比对到已经注释过的细胞集合上&#xff0c;获取映射后的细胞标签&#xff0c;UMP坐标。 准备&#xff1a; 一个分析好的单细胞图谱数据集&#xff0c;作为reference数据集。一个新的单细胞counts矩阵&#xff0c;记为 query数据集。 主要分为两个步…

在浏览器从输入URL到页面加载完成都经历了什么/一个完整的URL解析过程详细介绍

一、简述在浏览器从输入URL到页面加载完成都经历了什么 浏览器地址栏输入url地址&#xff0c;首先要在客户端上进行url解析 浏览器会首先查看自身的缓存&#xff0c;如果浏览器缓存中有对应的解析记录&#xff0c;直接返回结果 如果浏览器没有缓存&#xff0c;电脑会查看本地操…

Selenium+Unittest自动化测试框架实战(框架源码都给你)

目录 前言 项目框架 首先管理时间 !/usr/bin/env python3 -- coding:utf-8 -- 配置文件 conf.py config.ini# 读取配置文件 记录操作日志 简单理解POM模型 管理页面元素 封装Selenium基类 创建页面对象 熟悉unittest测试框架 编写测试用例 执行用例 生成测试报…

qemu-ARM篇——ARM 栈帧(一)

ARM 栈帧 本系列均已 corter-A7(armv7-a) 为例 在 ARM 中&#xff0c;通常为满减栈&#xff08;Full Descending FD&#xff09;, 也就是说&#xff0c;堆栈指针指向堆栈内存中最后一个填充的位置&#xff0c;并且随着每个新数据项被压入堆栈而递减。 栈的本质 要理解栈的本…

前端CSS学习(三)

1、盒子模型 盒子的概念1、页面中的每一个标签&#xff0c;都可看做是一 个“盒子” &#xff0c;通过盒子的视角更方便的进行布局2、浏览器在渲染 (显示)网页时&#xff0c;会将网页中的元素看做是一个个的矩形区域&#xff0c;我们也形象的称之为盒子CSS中规定每个盒子分别由…

BESV博世蔚发布2023全新款折叠e-bike —— F3,在中国自行车展会上大放异彩

BESV博世蔚身为跨界智慧出行的专家&#xff0c;今年在国内最大规模的中国国际自行车展上发布了其最新的e-bike折叠车款---VOTANI F3。拥有纯正荷兰血统的VOTANI系列车款&#xff0c;在设计外观上沿袭了欧风的极简主义和时尚设计&#xff0c;并搭配上折叠系统更易于携带和收纳。…

AnyStock JS Crack,AnyStock JS功能

AnyStock JS Crack,AnyStock JS功能 添加了新的技术指标-除了已经支持的几十个指标外&#xff0c;股票图表现在还提供了三个新的开箱即用技术指标&#xff1a; Aroon振荡器-通过从Aroon Up中减去Aroon Down&#xff0c;可以很容易地测量趋势的强度。 加权移动平均线(WMA)-通过更…