当前位置: 首页 > news >正文

多线程基础总结

1. 为什么要有多线程?

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

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

  1. 什么是多线程?

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

    1. 多线程的作用?

    提高效率

    1. 多线程的应用场景?

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

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

2. 多线程的两个概念?

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

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

3. 多线程的实现方式

1. 继承 Thread 类的方法进行实现2. 实现 Runnable 接口的方式进行实现3. 利用 Callable 接口和 Future 接口方式实现

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

public class MyThread extends Thread{@Overridepublic 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{@Overridepublic 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> {@Overridepublic 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);}@Overridepublic 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{@Overridepublic 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{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(getName() + "@" + i);}}
}
public class MyThread2 extends Thread{@Overridepublic 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{@Overridepublic 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{@Overridepublic 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:土豆// 当前线程:maint.join();// 执行在 main 线程中的方法for (int i = 0; i < 10; i++) {System.out.println("main 线程" + i);}}
}

线程的生命周期

在这里插入图片描述

5. 线程安全问题

public class MyThread extends Thread{// static 表示这个类所有的对象都共享 ticketint ticket = 0;  // 0 ~ 99@Overridepublic 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 表示这个类所有的对象都共享 ticketstatic int ticket = 0;  // 0 ~ 99// 加 static 保证 obj 是唯一的。(锁对象要保证是唯一的)static Object obj = new Object();@Overridepublic 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();@Overridepublic 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();@Overridepublic 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{@Overridepublic 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{@Overridepublic 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 {// 把吃的总数 -1Desk.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;}@Overridepublic 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;}@Overridepublic 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;@Overridepublic 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;// 红包的个数 -1count--;// 本次红包的信息进行打印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;}@Overridepublic 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;}// 线程1static ArrayList<Integer> list1 = new ArrayList<>();// 线程2static ArrayList<Integer> list2 = new ArrayList<>();@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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{@Overridepublic 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{@Overridepublic 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

相关文章:

多线程基础总结

1. 为什么要有多线程&#xff1f; 线程&#xff1a;线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中&#xff0c;是进程中实际运行单位。 进程&#xff1a;进程是程序的基本执行实体。 什么是多线程&#xff1f; 有了多线程&#xff0c;我们就可以让程序同时做…...

视频理解AI模型分类与汇总

人工智能领域视频模型大体也经历了从传统手工特征&#xff0c;到卷积神经网络、双流网络&#xff08;2014年-2017年&#xff09;、3D卷积网络、transformer的发展脉络。为了时序信息&#xff0c;有的模型也结合用LSTM。 视频的技术大多借鉴图像处理技术&#xff0c;只是视频比…...

【Linux】多线程 --- 线程同步与互斥+生产消费模型

人生总是那么痛苦吗&#xff1f;还是只有小时候是这样&#xff1f; —总是如此 文章目录 一、线程互斥1.多线程共享资源访问的不安全问题2.提出解决方案&#xff1a;加锁&#xff08;局部和静态锁的两种初始化/销毁方案&#xff09;2.1 对于锁的初步理解和实现2.2 局部和全局锁…...

17.模型的定义

学习要点&#xff1a; 1.默认设置 2.模型定义 本节课我们来开始学习数据库的模型部分的定义和默认值的设置。 一&#xff0e;默认设置 1. 框架可以使用 Eloquent ORM 进行数据库交互&#xff0c;也就是关系对象模型&#xff1b; 2. 在数据库入门阶段&#xff0c;我们已经创建了…...

golang 记录交叉编译sqlite的报错信息 go build -ldflags

go build -ldflags ‘-s -w --extldflags “-static -fpic”’ -o go-web main.go [gos20230512]# CGO_ENABLED1 CCaarch64-linux-gnu-gcc CXXaarch64-linux-gnu-g GOOSlinux GOARCHarm64 go build -ldflags -s -w --extldflags "-static -fpic" -o go-web m…...

ChatGPT AI使用成本

LLM “经济学”&#xff1a;ChatGPT 与开源模型&#xff0c;二者之间有哪些优劣权衡&#xff1f;谁的部署成本更低&#xff1f; 太长不看版&#xff1a;对于日均请求在 1000 次左右的低频使用场景&#xff0c;ChatGPT 的实现成本低于部署在 AWS 上的开源大模型。但面对每天数以…...

腾讯云与中电金信发布联合核心方案

5月11日&#xff0c;以“聚力革新&#xff0c;行稳致远”为主题的 “腾讯金融云国产化战略峰会”在北京举办&#xff0c;来自金融业、科技侧、研究机构的专家学者同聚一堂&#xff0c;共同探讨银行核心下移方法论以及国产化转型实践等话题。会议期间&#xff0c;中电金信副总经…...

老胡的周刊(第090期)

老胡的信息周刊[1]&#xff0c;记录这周我看到的有价值的信息&#xff0c;主要针对计算机领域&#xff0c;内容主题极大程度被我个人喜好主导。这个项目核心目的在于记录让自己有印象的信息做一个留存以及共享。 &#x1f3af; 项目 privateGPT[2] 为保证数据私密性&#xff0c…...

2023-数仓常见问题以及解决方案

01 数据仓库现状 小 A 公司创建时间比较短&#xff0c;才刚过完两周岁生日没多久&#xff1b;业务增长速度快&#xff0c;数据迅速增加&#xff0c;同时取数需求激增与数据应用场景对数据质量、响应速度、数据时效性与稳定要求越来越高&#xff1b;但技术能力滞后业务增长&…...

没关系,前端还死不了

前言 网络上的任何事情都可以在《乌合之众》书中找到答案。大众言论没有理性&#xff0c;全是极端&#xff0c;要么封神&#xff0c;要么踩死。不少人喷前端&#xff0c;说前端已死&#xff1f;前端内卷&#xff1f;前端一个月800包吃住&#xff1f; 对此我想说&#xff0c;“…...

OpenSSL-基于IP或域名生成自签名证书脚本

个人名片&#xff1a; 对人间的热爱与歌颂&#xff0c;可抵岁月冗长&#x1f31e; Github&#x1f468;&#x1f3fb;‍&#x1f4bb;&#xff1a;念舒_C.ying CSDN主页✏️&#xff1a;念舒_C.ying 个人博客&#x1f30f; &#xff1a;念舒_C.ying 一、安装 需要安装并配置Op…...

如何在C#中创建和使用自定义异常

C#是一种强类型语言&#xff0c;可以捕获和处理各种异常&#xff0c;从而帮助我们发现程序中出现的错误。在程序开发过程中&#xff0c;如果需要找到特定的错误情况并处理&#xff0c;这时就需要创建自定义异常。下面介绍一下如何在C#中创建和使用自定义异常。 1、什么是异常&…...

通过systemctl管理服务

文章目录 通过systemctl管理服务通过systemctl管理单一服务(service unit)使用案例服务启动/关闭/查看的练习关于systemctl命令启动/停止服务后面的后缀名是否加&#xff1f; 通过systemctl查看系统上所有的服务使用案例 通过systemctl管理不同的操作环境(target unit)使用案例…...

面经|小红书经营分析师

感觉面试官还挺严肃的&#xff0c;并且猎头说因为工作经验不够是外包岗位。 但是没想到最后败在了SQL上&#xff0c;很久没刷题了 平时工作中还是需要想下给公司整体带来的收益结果是什么&#xff0c;实际工作中不一定会用到&#xff0c;但是要有这个思路&#xff0c;面试的时候…...

abpvnext后台工作者使用quartz扩展的一些思路和使用细节记录--(未完待续)

需求背景描述&#xff1a; 我有一个温湿度数据采集的物联网系统&#xff0c;每个租户都需要定时执行若干种任务&#xff0c; 不同的租户&#xff0c; 他定时执行的间隔不一样 &#xff0c;比如 A租户&#xff0c;数据保存间隔60秒&#xff0c;数据是否超限的轮询间隔是是600…...

提升应届生职场竞争力:有效策略和关键推动因素

应届生进入职场是一个关键的阶段&#xff0c;他们需要通过有效的方法和策略来提高自己的竞争力&#xff0c;以适应职场的挑战并取得成功。以下是一些可以帮助应届生提升竞争力的方法和策略&#xff0c;以及对其职场发展起到关键推动和支撑作用的方面。 学习和继续教育&#xff…...

PBDB Data Service:List of fossil collections(化石采集记录列表)

List of fossil collections&#xff08;化石采集记录列表&#xff09; 描述用法参数以下参数可用于按各种条件查询集合。以下参数可用于筛选所选内容以下参数还可用于根据分类筛选结果列表以下参数可用于生成数据存档您可以使用以下参数选择要检索的额外信息&#xff0c;以及要…...

centos安装SNB服务

Samba 是一种开源软件&#xff0c;它提供了一种让 Linux 和 Unix 系统与 Windows 操作系统相互通信的标准协议。Samba 允许 Linux 和 Unix 系统作为文件服务器和打印服务器&#xff0c;提供 Windows 客户端所需的服务。 具体来说&#xff0c;Samba 通过实现 SMB/CIFS 协议来实现…...

课程《JavaWeb基础框架程序设计》考试题下篇——数据库与表单操作用题(人事管理平台的添加员工档案信息的操作题)

文章目录 &#x1f4cb;前言&#x1f3af;第三题&#xff08;40分&#xff09;&#x1f3af;报错以及解决方法&#x1f4dd;最后 &#x1f4cb;前言 这篇文章是大学课程《JavaWeb基础框架程序设计》考试题目的内容&#xff0c;包括了原题和答案。题目只包括了三道编程题&#…...

Linux-初学者系列——篇幅4_系统运行相关命令

系统运行相关命令-目录 一、关机重启注销命令1、重启或者关机命令-shutdown语法格式&#xff1a;常用参数&#xff1a;01 指定多久关闭/重启系统02 指定时间关闭/重启系统03 实现立即关闭/重启系统04 取消关闭/重启系统计划 2、重启或者关机命令-halt/poweroff/reboot/systemct…...

无缝集成:利用Requests库轻松实现数据抓取与处理

目录 引言安装基本用法发送HTTP请求处理HTTP响应高级功能总结 引言 Requests是Python中一个常用的第三方库&#xff0c;用于向Web服务器发起HTTP请求并获取响应。该库的使用简单&#xff0c;功能强大&#xff0c;被广泛应用于网络爬虫、API访问、Web应用开发等领域。 本文将介…...

几种内部排序算法的cpp代码实现与分析

零、测试函数 typedef void (*SortFunc) (int*&, int);inline void swap(int &a, int &b) {int tmp a;a b;b tmp; }inline void printArr(int* a, int n) {for (int k 0; k < n; k) {std::cout << a[k] << ;}std::cout << std::endl; }…...

第3天学习Docker-Docker部署常见应用(MySQL、Tomcat、Nginx、Redis、Centos)

前提须知&#xff1a; &#xff08;1&#xff09;搜索镜像命令 格式&#xff1a;docker search 镜像名 &#xff08;2&#xff09;设置Docker镜像加速器 详见文章&#xff1a;Docker设置ustc的镜像源&#xff08;镜像加速器&#xff09; 1、部署MySQL 拉取镜像&#xff08;这…...

给大家介绍四款最受欢迎的抓包神器

身为互联网人&#xff0c;无论在平时开发还是在测试过程中&#xff0c;我们都不可避免的会涉及到网络安全性&#xff0c;如何监测网络请求&#xff0c;从而最大程度的保证数据的安全&#xff0c;需要我们了解并掌握抓包的技巧。那么何谓抓包呢&#xff1f;抓包就是将网络传输发…...

解决Reids过期方案 游标遍历清除Redis过期的key

游标遍历清除Redis过期的key 为什么要清除Redis过期的Key ​ Redis的过期清理是一种懒惰的清理方案&#xff0c;他不会过期后立刻清除&#xff0c;而是在Key被访问的时候进行删除&#xff0c;Redis这么做的目的就是为了提高性能降低资源开销。 ​ 具体来说&#xff0c;一个K…...

K8s基础10——数据卷、PV和PVC、StorageClass动态补给、StatefulSet控制器

文章目录 一、数据卷类型1.1 临时数据卷&#xff08;节点挂载&#xff09;1.2 节点数据卷&#xff08;节点挂载&#xff09;1.3 网络数据卷NFS1.3.1 效果测试 1.4 持久数据卷&#xff08;PVC/PV&#xff09;1.4.1 效果测试1.4.2 测试结论 二、PV、PVC生命周期2.1 各阶段工作原理…...

oracle系统查询~3

查看实例的基本信息 SQL> col host_name for a25 col instance_name for a15 col version for a15 col status for a10 set linesize 600 col host_name for a20 select instance_number,instance_name,host_name,version,startup_time,status,archiver f…...

Mybatis源码(九)— chche

Mybatis中共有三级缓存&#xff0c;其中一级缓存默认开启&#xff0c;作用范围是在sqlSession对象&#xff08;同一个会话&#xff09;&#xff0c;二级缓存需要手动配置开启&#xff0c;作用范围是在sqlSessionFactory对象下的同一个namespace范围&#xff08;所以二级缓存是可…...

回溯法--N皇后问题

N皇后问题 一、问题描述二、示例2.1 四皇后的2个可行解2.2 过程图示 三、问题分析3.1涉及到的概念递归回溯 3.2 分析 四、 代码实现4.1 实现思路宏观&#xff1a;微观&#xff1a; 4.2 递归函数NS图4.3 代码 一、问题描述 1、按照国际象棋的规则&#xff0c;皇后可以攻击与之处…...

ajax请求

ajax的优点 可以无需刷新页面而与服务器进行通信允许你根据用户事件来更新部分页面内容 ajax的缺点 没有浏览历史&#xff0c;不能回退存在跨域问题SEO不友好 get请求 <button>点击发送请求</button><div id"result"></div><script>…...