多线程——

目录

一、为什么要有多线程?

1、线程与进程

2、多线程的应用场景

3、小结​编辑

二、多线程中的两个概念(并发和并行)

1、并发

2、并行

3、小结

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

1、继承Thread类的方式进行实现

2、实现Runnable接口的方式进行实现

3、利用Callable接口和Future接口方式的实现

4、多线程三种实现方式对比

四、常见的成员方法

1、get/setName方法 -- 线程名字

2、currentThread方法 -- 获取当前线程对象

3、sleep方法 -- 线程休眠

4、set/getPriority方法 -- 线程优先级

5、setDaemon方法 -- 守护线程

6、yield方法 -- 礼让线程

7、join方法 -- 插入线程

8、线程的生命周期

五、线程安全的问题

1、练习:设计一个程序模拟电影院卖票

2、买票引发的安全问题

2.1、重复票的由来:(线程在执行代码的过程中,CPU的执行权随时有可能被抢走)

2.2、出现了超出范围的票:(和上面的原因相同)

3、安全问题的解决办法 -- 同步代码块

4、同步代码块中的两个小细节

4.1、细节1:synchronized要写在循环的里面​编辑

4.2、细节2:synchronized中的锁对象一定是唯一的

5、同步方法

6、StringBuilder和StringBuffer的区别

7、Lock锁(手动加锁、释放锁)​编辑

7.1、Lock使用不规范造成的两个安全问题

六、死锁

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

1、消费者等待

2、生产者等待​编辑

3、常见方法(wait/notify/notifyAll)

4、消费者与生产者代码实现

4.1、Cook.java

4.2、Desk.java

4.3、Foodie.java

4.4、ThreadDemo.java

5、阻塞队列方式(另一种等待唤醒机制)​编辑

5.1、阻塞队列的继承结构​编辑

5.2、阻塞队列实现等待唤醒机制

7、多线程的6中状态

八、综合练习

1、多线程练习1(卖电影票)​编辑

2、多线程练习2(送礼品)

3、多线程练习3(打印奇数数字)​编辑

4、多线程练习4(抢红包)

5、多线程练习5(抽奖箱抽奖)

6、多线程练习6(多线程统计并求最大值)

7、多线程练习7(多线程之间的比较)

8、多线程练习8(多线程阶段大作业)

九、线程池

1、吃饭买碗的故事

1.1、问题​编辑

1.2、解决方案

2、以前写多线程的弊端​编辑

3、线程池的核心原理

4、线程池的代码实现

4.1、Executors工具类​编辑

4.2、线程复用示例

4.3、创建一个有上限的线程池

5、自定义线程池(ThreadPoolExecutor)

5.1、任务拒绝策略​编辑

5.2、代码实现​编辑

5.3、小结​编辑

6、最大并行数

6.1、什么是最大并行数?​编辑

6.2、向Java虚拟机返回可用处理器的数目

7、线程池多大才合适?

​编辑

十、多线程的额外扩展内容准备面试时可以再突击学习,资料可见《多线程(额外扩展).md》


一、为什么要有多线程?

1、线程与进程

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

举例:在任务管理器中,一个软件运行之后,它就是一个进程

线程:(简单理解,线程就说应用软件中互相独立,可以同时运行的功能)

 单线程程序:所有的都在一个线程中执行,耗时长

2、多线程的应用场景

3、小结

二、多线程中的两个概念(并发和并行)

1、并发

2、并行

 以2核4线程为例:(如果计算机中只要4条线程,那么它是不用切换的,但如果线程越来越多,那么这个红线就会在多个线程之间随机的进行切换)

3、小结

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

1、继承Thread类的方式进行实现

 自己定义一个类继承Thread并重写run方法

创建子类的对象,并启动线程

 

2、实现Runnable接口的方式进行实现

自己定义一个类实现Runnable接口,并重新里面的run方法

public class MyRun implements Runnable {
    @Override
    public void run() {
        //书写线程要执行的代码
        for (int i = 0; i < 100; i++) {
            //获取当前线程的对象
            /*Thread t = Thread.currentThread();
            System.out.println(t.getName()+"HelloWorld");
            */
            System.out.println(Thread.currentThread().getName()+"HelloWorld");

        }
    }
}

package com.yaqi.a02threadcase2;

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("线程1");
        t1.setName("线程2");

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

 

 结果

3、利用Callable接口和Future接口方式的实现

package com.yaqi.a03threadcase3;

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

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


    }
}

package com.yaqi.a03threadcase3;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer> {

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

4、多线程三种实现方式对比

四、常见的成员方法

1、get/setName方法 -- 线程名字

默认名字的由来:

 序号自增

/*
            String getName()                    返回此线程的名称
            void setName(String name)           设置线程的名字(构造方法也可以设置名字)
            细节:
                1、如果我们没有给线程设置名字,线程也是有默认的名字的
                        格式:Thread-X(X序号,从0开始的)
                2、如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置

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



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

 MyThread

package com.yaqi.a04threadmethod1;

public class MyThread extends Thread{

    public MyThread() {
    }

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

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName() + "@" + i);
        }
    }
}

2、currentThread方法 -- 获取当前线程对象

static Thread currentThread()       获取当前线程的对象
细节:
    当JVM虚拟机启动之后,会自动的启动多条线程
    其中有一条线程就叫做main线程
    他的作用就是去调用main方法,并执行里面的代码
    在以前,我们写的所有的代码,其实都是运行在main线程当中
//哪条线程执行到这个方法,此时获取的就是哪条线程的对象
        Thread t = Thread.currentThread();
        String name = t.getName();
        System.out.println(name);//main

3、sleep方法 -- 线程休眠

static void sleep(long time)        让线程休眠指定的时间,单位为毫秒
细节:
    1、哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
    2、方法的参数:就表示睡眠的时间,单位毫秒
        1 秒= 1000毫秒
    3、当时间到了之后,线程会自动的醒来,继续执行下面的其他代码
System.out.println("11111111111");
        Thread.sleep(5000);
        System.out.println("22222222222");

4、set/getPriority方法 -- 线程优先级

没有设置,优先级则默认为5,优先级越高,抢到CPU的概率就越高

  • 最小1
  • 最大10
  • 默认5

5、setDaemon方法 -- 守护线程

两个线程执行的代码不同:守护线程是陆续结束的,所以守护线程也叫做备胎线程

应用场景

6、yield方法 -- 礼让线程

但是只是尽可能的均匀,不是绝对的

7、join方法 -- 插入线程

插入线程:将土豆插入到main线程之前,只有当土豆线程执行完毕,才会轮到main线程

8、线程的生命周期

五、线程安全的问题

1、练习:设计一个程序模拟电影院卖票

出现了超出票范围或者重复票的情况: 

2、买票引发的安全问题

相同的票出现多次

出现了超出范围的票

2.1、重复票的由来:(线程在执行代码的过程中,CPU的执行权随时有可能被抢走)

2.2、出现了超出范围的票:(和上面的原因相同)

3、安全问题的解决办法 -- 同步代码块

示例代码

结果

4、同步代码块中的两个小细节

4.1、细节1:synchronized要写在循环的里面

4.2、细节2:synchronized中的锁对象一定是唯一的

 

5、同步方法

 示例代码

将同步代码块改成同步方法: 

6、StringBuilder和StringBuffer的区别

两个类的方法都是相同的

但是StringBuffer是线程安全的,它里面所有的方法都是线程同步的

StringBulider:代码单线程的不需要考虑多线程当中数据安全的情况
StringBuffer:多线程环境下需要考虑数据安全则选择StringBuffer

7、Lock锁(手动加锁、释放锁)

7.1、Lock使用不规范造成的两个安全问题

Ⅰ、重复票以及超出范围票

我们在使用Thread类实现多线程时,创建自己的类,一定要注意锁对象需要唯一,即在相关变量前加上static关键字

Ⅱ、程序无法正常终止

这是由于当满足条件时,循环直接被终止,导致lock锁没有被释放

Ⅲ、正确代码(标准写法)

即将容易产生异常的代码块放入try…catch中

 

六、死锁

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

 注意事项:千万不要让两个锁嵌套起来!

 

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

生产者消费者模式是一个十分经典的多线程协作的模式

1、消费者等待

2、生产者等待

3、常见方法(wait/notify/notifyAll)

4、消费者与生产者代码实现

4.1、Cook.java



public class Cook extends Thread{

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

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

4.2、Desk.java

package com.yaqi.a13waitandnotify;

public class Desk {

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

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

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

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


}

4.3、Foodie.java


public class Foodie extends Thread{

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

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

4.4、ThreadDemo.java

package com.yaqi.a13waitandnotify;


public class ThreadDemo {
    public static void main(String[] args) {
       /*
       *
       *    需求:完成生产者和消费者(等待唤醒机制)的代码
       *         实现线程轮流交替执行的效果
       *
       * */


        //创建线程的对象
        Cook c = new Cook();
        Foodie f = new Foodie();

        //给线程设置名字
        c.setName("厨师");
        f.setName("吃货");

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

    }
}

5、阻塞队列方式(另一种等待唤醒机制)

5.1、阻塞队列的继承结构

5.2、阻塞队列实现等待唤醒机制

Cook.java

package com.yaqi.a14waitandnotify;

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

 put方法的源码中实现了Lock锁

Foodie.java:

take方法的底层也是有锁的


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 {
                    String food = queue.take();
                    System.out.println(food);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        }
    }
}

 ThreadDemo.java:

package com.yaqi.a14waitandnotify;


import java.util.concurrent.ArrayBlockingQueue;

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、多线程的6中状态

 

八、综合练习

1、多线程练习1(卖电影票)

2、多线程练习2(送礼品)

3、多线程练习3(打印奇数数字)

4、多线程练习4(抢红包)

package com.yaqi.test4case1;

import java.util.Random;

public class MyThread extends Thread{

    //共享数据
    //100块,分成了3个包
    static double money = 100;
    static int count = 3;

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

    @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();
                    //100 元   3个包
                    //第一个红包:99.98
                    //100 - (3-1) * 0.01
                    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 + "元");
            }
        }
    }
}

测试类:

package com.yaqi.test4case1;

public class Test {
    public static void main(String[] args) {
        /*
            微信中的抢红包也用到了多线程。
            假设:100块,分成了3个包,现在有5个人去抢。
            其中,红包是共享数据。
            5个人是5条线程。
            打印结果如下:
            	XXX抢到了XXX元
            	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("小QQ");
        t3.setName("小哈哈");
        t4.setName("小诗诗");
        t5.setName("小丹丹");

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


    }
}

 

精确运算:(BigDecimal)

package com.yaqi.test4case2;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Random;

public class MyThread extends Thread{

    //总金额
    static BigDecimal money = BigDecimal.valueOf(100.0);
    //个数
    static int count = 3;
    //最小抽奖金额
    static final BigDecimal MIN = BigDecimal.valueOf(0.01);

    @Override
    public void run() {
        synchronized (MyThread.class){
            if(count == 0){
                System.out.println(getName() + "没有抢到红包!");
            }else{
                //中奖金额
                BigDecimal prize;
                if(count == 1){
                    prize = money;
                }else{
                    //获取抽奖范围
                    double bounds = money.subtract(BigDecimal.valueOf(count-1).multiply(MIN)).doubleValue();
                    Random r = new Random();
                    //抽奖金额
                    prize = BigDecimal.valueOf(r.nextDouble(bounds));
                }
                //设置抽中红包,小数点保留两位,四舍五入
                prize = prize.setScale(2,RoundingMode.HALF_UP);
                //在总金额中去掉对应的钱
                money = money.subtract(prize);
                //红包少了一个
                count--;
                //输出红包信息
                System.out.println(getName() + "抽中了" + prize + "元");
            }
        }
    }
}

 

5、多线程练习5(抽奖箱抽奖)

package com.yaqi.test5;

import java.util.ArrayList;
import java.util.Collections;

public class MyThread extends Thread {

    ArrayList<Integer> list;

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

    @Override
    public void run() {
        //1.循环
        //2.同步代码块
        //3.判断
        //4.判断

        while (true) {
            synchronized (MyThread.class) {
                if (list.size() == 0) {
                    break;
                } else {
                    //继续抽奖
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    System.out.println(getName() + "又产生了一个" + prize + "元大奖");
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

测试类

import java.util.ArrayList;
import java.util.Collections;

public class Test {
    public static void main(String[] args) {
        /*
            有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为 {10,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,10,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();

    }
}

 

6、多线程练习6(多线程统计并求最大值)

示例代码一:(在练习5的基础上进行修改) 

package com.yaqi.test6case1;

import java.util.ArrayList;
import java.util.Collections;

public class MyThread extends Thread {

    ArrayList<Integer> list;

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

    //线程一
    static ArrayList<Integer> list1 = new ArrayList<>();
    //线程二
    static ArrayList<Integer> list2 = new ArrayList<>();

    @Override
    public void run() {
        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 prize = list.remove(0);
                    if("抽奖箱1".equals(getName())){
                        list1.add(prize);
                    }else {
                        list2.add(prize);
                    }
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

示例代码二:升级版--线程栈(示例一可以用,但不好)

改进后,这里只需要一个ArrayList就搞定了

 

package com.yaqi.test6case2;

import java.util.ArrayList;
import java.util.Collections;

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<>();//1 //2
        while (true) {
            synchronized (MyThread.class) {
                if (list.size() == 0) {
                    System.out.println(getName() + boxList);
                    break;
                } else {
                    //继续抽奖
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    boxList.add(prize);
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

测试类

package com.yaqi.test6case2;

import java.util.ArrayList;
import java.util.Collections;

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,10,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();

    }
}

 

示例二内存图讲解:

每个线程都有自己独立的空间

 

7、多线程练习7(多线程之间的比较)

示例代码:(难点在于如何获取两个线程中的最大值★)

调用多线程的第三种方式Callable来实现(可以返回结果)

MyCallable.java:

package com.yaqi.test7;

import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.Callable;

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<>();//1 //2
        while (true) {
            synchronized (MyCallable.class) {
                if (list.size() == 0) {
                    System.out.println(Thread.currentThread().getName() + boxList);
                    break;
                } else {
                    //继续抽奖
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    boxList.add(prize);
                }
            }
            Thread.sleep(10);
        }
        //把集合中的最大值返回
        if(boxList.size() == 0){
            return null;
        }else{
            return Collections.max(boxList);
        }
    }
}

 

package com.yaqi.test7;

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

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元
        if(max1 == null){
            System.out.println("在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为"+max2+"元");
        }else if(max2 == null){
            System.out.println("在此次抽奖过程中,抽奖箱1中产生了最大奖项,该奖项金额为"+max1+"元");
        }else if(max1 > max2){
            System.out.println("在此次抽奖过程中,抽奖箱1中产生了最大奖项,该奖项金额为"+max1+"元");
        }else if(max1 < max2){
            System.out.println("在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为"+max2+"元");
        }else{
            System.out.println("两者的最大奖项是一样的");
        }


    }
}

8、多线程练习8(多线程阶段大作业)

九、线程池

1、吃饭买碗的故事

1.1、问题

1.2、解决方案

买个碗柜,买了碗之后不摔,存入碗柜中

2、以前写多线程的弊端

3、线程池的核心原理

当有新的任务出现,且线程池线程不足时,会新建线程以满足需求,其中最大线程的数量可以自行设置

4、线程池的代码实现

4.1、Executors工具类

示例代码:

MyRunnable.java:

package com.yaqi.a01threadpool1;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}

测试类

package com.yaqi.a01threadpool1;


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

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


        //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());
        pool1.submit(new MyRunnable());


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


    }
}

 

4.2、线程复用示例

package com.yaqi.a02threadpool2;


import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

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

    /*
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
        (核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

        参数一:核心线程数量              不能小于0
        参数二:最大线程数                不能小于0,最大数量 >= 核心线程数量
        参数三:空闲线程最大存活时间       不能小于0
        参数四:时间单位                  用TimeUnit指定
        参数五:任务队列                  不能为null
        参数六:创建线程工厂              不能为null
        参数七:任务的拒绝策略             不能为null
    */

        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,  //核心线程数量,能小于0
                6,  //最大线程数,不能小于0,最大数量 >= 核心线程数量
                60,//空闲线程最大存活时间
                TimeUnit.SECONDS,//时间单位
                new ArrayBlockingQueue<>(3),//任务队列
                Executors.defaultThreadFactory(),//创建线程工厂
                new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
        );
    }
}

4.3、创建一个有上限的线程池

5、自定义线程池(ThreadPoolExecutor)

5.1、任务拒绝策略

以下面示例为例,它会将任务4抛弃,将任务10加入 

5.2、代码实现

package com.yaqi.a02threadpool2;


import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

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

    /*
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
        (核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

        参数一:核心线程数量              不能小于0
        参数二:最大线程数                不能小于0,最大数量 >= 核心线程数量
        参数三:空闲线程最大存活时间       不能小于0
        参数四:时间单位                  用TimeUnit指定
        参数五:任务队列                  不能为null
        参数六:创建线程工厂              不能为null
        参数七:任务的拒绝策略             不能为null
    */

        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,  //核心线程数量,能小于0
                6,  //最大线程数,不能小于0,最大数量 >= 核心线程数量
                60,//空闲线程最大存活时间
                TimeUnit.SECONDS,//时间单位
                new ArrayBlockingQueue<>(3),//任务队列
                Executors.defaultThreadFactory(),//创建线程工厂
                new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
        );
    }
}

 

5.3、小结

6、最大并行数

6.1、什么是最大并行数?

 

6.2、向Java虚拟机返回可用处理器的数目

7、线程池多大才合适?

可以通过thread dump来计算CPU的计算时间和等待时间

十、多线程的额外扩展内容准备面试时可以再突击学习,资料可见《多线程(额外扩展).md》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值