# 多线程

  • 线程:指操作系统中能够进行运算调度的最小单位。它被包含在进程中,是进程的实际运作单位
  • 进程:是程序的基本执行实体
  • 作用:有了多线程,就可以让程序同时做多件事情,以提高效率

# 并发和并行

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

# 多线程的实现方式

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

# 多线程三种实现方式对比

优点 缺点
继承Thread 编程比较简单,可以直接使用Thread类中的方法 可拓展性较差,不能再继承其他类
实现Runnable接口 拓展性强,实现该接口的同时还可以继承其他类 编程相对复杂,不能直接使用Thread类中的方法
实现Callable接口 拓展性强,实现该接口的同时还可以继承其他类 编程相对复杂,不能直接使用Thread类中的方法

# 继承Thread类的方式进行实现

package Multithreading;

//多线程
public class Main {
    public static void main(String[] args) {
        //创建子类的对象
        MyThread myThread = new MyThread();
        MyThread myThread1 = new MyThread();
        myThread.setName("线程一");
        myThread1.setName("线程二");
        //开启多线程`start()`方法表示开启线程
        //线程1和2交替执行
        myThread.start();
        myThread1.start();
    }
}

//多线程的实现方式
//继承`Thread`类的方式进行实现
class MyThread extends Thread {
    //重写`run()`方法 当类调用`start()`方法时,`run()`方法就会执行
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "MyThread");
        }
    }
}

# 多线程的实现方式二

实现Runnable接口进行实现

package Multithreading;

public class Multithreading2 {
    public static void main(String[] args) {
        //多线程的第二种启动方式
        //3.创建自己类的对象
        MyRunnable myRunnable = new MyRunnable();
        //4.创建一个Thread对象,把对象传给Thread的构造方法
        Thread thread = new Thread(myRunnable);
        Thread thread2 = new Thread(myRunnable);
        thread.setName("线程1");
        thread2.setName("线程2");
        //5.开启多线程
        thread.start();
        thread2.start();
    }
}

//1.自己定义一个类实现Runnable接口,重写`run()`方法,
class MyRunnable implements Runnable {
    // 2.重写里面的run方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //获取当前线程的对象
            Thread thread = Thread.currentThread();
            //获取当前线程的名字
            System.out.println(thread.getName() + "-->" + i);
        }
    }
}

# 多线程的实现方式三

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

  • 前面两种接口均无返回值因此不能获取线程中的执行结果
  • 特点:可以获取多线程执行的结果
package Multithreading;

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

public class Multithreading3 {
    //    利用`Callable`接口和`Future`接口方式实现
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //3.创建多线程的对象,表示多线程应该执行的任务
        MyCallable myCallable = new MyCallable();
        //4.创建一个`Future`对象,用来管理myCallable 多线程运行的结果
        //Future是一个接口,因此需要创建其实现类的对象FutureTask
        FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
        FutureTask<Integer> futureTask2 = new FutureTask<>(myCallable);
        //5.创建Thread对象
        Thread thread = new Thread(futureTask);
        Thread thread1 = new Thread(futureTask2);
        thread.setName("线程1");
        thread1.setName("线程2");
        //6.启动多线程
        thread.start();
        thread1.start();
        //7.获取多线程运行结果
        Integer integer = futureTask.get();
        Integer integer1 = futureTask2.get();
        System.out.println("多线程1运行结果" + integer);
        System.out.println("多线程2运行结果" + integer1);
    }
}

//1.创建一个类,实现`Callable`接口
//泛型表示结果的类型
class MyCallable implements Callable<Integer> {
    //2.重写`call()`方法,该方法有返回值,这个返回值就表示多线程运行的结果
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum += i;
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
        return sum;
    }
}

# Thread类常见的成员方法

方法名称 说明
String getName() 返回此线程的名称
void setName() 设置此线程的名称
void start() 启动此线程
static Thread currentThread() 获取当前线程的对象
static void sleep(long millis) 让线程休眠指定的时间,单位为毫秒
setPriority(int newPriority) 设置线程的优先级 (最小是1,最大是10,默认为5)优先级越大抢占到cpu的概率越高
final int getPriority() 获取线程的优先级
final void setDaemon(boolean on) 设置线程为守护线程(备胎线程)
public static void yield() 出让线程/礼让线程
public static void join() 插入线程/插队线程
package Multithreading;

public class ThreadText {
    public static void main(String[] args) throws InterruptedException {
        //Thread类中的成员方法
        //使用同样的方法设置名字
        //可以用setName()和构造方法设置名字
        Thread thread = new MyThread1("线程1");
        thread.start();
        //3.获取当前线程的名字
        //细节:java虚拟机启动时,会自动启用多条线程
        //其中有一条线程就叫main线程
        String name = Thread.currentThread().getName();
        System.out.println("当前执行线程" + name);//main

        //4.线程休眠
        //那条线程执行到这个方法,那么那条线程就会在这里停留对应的时间
        //方法的参数就是停留的时间 单位是毫秒
        //当时间到了之后线程会自动醒来,继续执行下面的代码
        //使main线程休眠
        System.out.println("main线程开始休眠");
        Thread.sleep(1);
        System.out.println("main线程休眠结束");

        //5.获取线程优先级
        System.out.println(thread.getPriority());//5
        //main线程的优先级
        System.out.println(Thread.currentThread().getPriority());//5

        Thread thread1 = new MyThread1("飞机");
        Thread thread2 = new MyThread1("坦克");

        //设置优先级
        thread1.setPriority(Thread.MIN_PRIORITY);
        thread2.setPriority(Thread.MAX_PRIORITY);
        //开启线程
        thread1.start();
        thread2.start();


        //守护线程
        //当所有的非守护线程(女神)执行完毕后,守护线程(备胎)即使没执行完也会结束
        Thread thread3 = new MyThread2("女神");
        Thread thread4 = new MyThread3("备胎");
        //设置为守护线程
        thread4.setDaemon(true);
        thread3.start();
        thread4.start();

        //出让/礼让线程
        //出让当前cpu的执行权给其他线程执行
        Thread.yield();
        System.out.println("main线程继续执行");

        //插队线程
        //将女神线程插队,抢占cpu的执行权 等女神线程执行完毕再执行main线程
        thread3.join();
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程" + i);
        }
    }
}

class MyThread1 extends Thread {
    public MyThread1() {
    }

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

    @Override
    public void run() {
        //1.设置线程的名称
        //如果没有给线程设置名字,线程也是有默认名字的
        //格式:Thread-X(X:序号,从0开始)
        System.out.println(getName() + "开始休眠");
        System.out.println(getName() + "结束休眠");
        //2.返回此线程的名称
        System.out.println(getName());//默认为Thread-1

    }
}

//女神线程
class MyThread2 extends Thread {
    public MyThread2(String name) {
        super(name);
    }

    public MyThread2() {
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}

//备胎线程
class MyThread3 extends Thread {
    public MyThread3() {
    }

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

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}

# 线程的生命周期

线程的生命周期

# 线程安全问题

  • 需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票
  • 请设计一个程序模拟该电影院卖票
package Multithreading;

public class ThreadText1 {
    public static void main(String[] args) {
        //线程安全问题
        //创建售票员
        Seller seller = new Seller("售票员1");
        Seller seller1 = new Seller("售票员2");
        Seller seller3 = new Seller("售票员3");
        //开启线程
        seller.start();
        seller1.start();
        seller3.start();

    }
}

//售票员类
class Seller extends Thread {
    //票号
    //static 表示所有的类都共享ticket这个数据
    static int ticket = 0;

    public Seller() {
    }

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

    @Override
    public void run() {

        while (true) {
            if (ticket < 100) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket++;
                System.out.println(getName() + "出售第" + ticket + "张票");
            } else break;

        }
    }
}

# 卖票引发的安全问题

  1. 相同的票出现了多次

  2. 出现了超出范围的票

    根本原因: cpu的执行权有可能随时会被其他进程抢走

  • 解决方法:利用同步代码块**synchronized** 使同步代码块里面的代码是轮流执行
package Multithreading;

public class ThreadText1 {
    public static void main(String[] args) {
        //线程安全问题
        //创建售票员
        Seller seller = new Seller("售票员1");
        Seller seller1 = new Seller("售票员2");
        Seller seller3 = new Seller("售票员3");
        //开启线程
        seller.start();
        seller1.start();
        seller3.start();

    }
}

//票类-实现私有化构造方法 防止外部实例化该类
class TicketUtis {
    //票的数量
    //static 表示所有的类都共享ticket这个数据
    static int MAX_TICKET_NUM = 100;
    //当前票的编号
    static Integer ticketNum = 0;

    //私有化构造方法防止外部实例化该类
    private TicketUtis() {
    }

    //获取票号
    public static Integer getTicketNum() {
        //利用同步代码块 使同步代码块里面的代码是轮流执行
        //创建锁对像保证对象是唯一的
        //TicketUtis.class 表示当前类的字节码 只要是同一个类那么就是唯一的
        synchronized (TicketUtis.class) {
            if (ticketNum >= MAX_TICKET_NUM) {
                System.out.println("票卖完了");
                return null;
            } else {
                ticketNum++;
                return ticketNum;
            }
        }
    }
}

//售票员类
class Seller extends Thread {
    public Seller() {
    }

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

    @Override
    //设置同步代码块
    public void run() {
        Integer ticketNum = null;
        while ((ticketNum = TicketUtis.getTicketNum()) != null) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName() + "出售第" + ticketNum + "张票");
        }
    }
}

# 同步方法

  • 同步方法:就是把synchronized加在方法上
  • 作用将该方法的所有代码都锁起来,轮流执行
  • 如果方法是非静态的,则锁对象是this
  • 如果方法是静态(static)的,则锁对象是当前类的字节码对象 当前类.class
package Multithreading;

public class ThreadText1 {
    public static void main(String[] args) {
        //线程安全问题
        //创建售票员
        Seller seller = new Seller("售票员1");
        Seller seller1 = new Seller("售票员2");
        Seller seller3 = new Seller("售票员3");
        //开启线程
        seller.start();
        seller1.start();
        seller3.start();

    }
}

//售票员类
class Seller extends Thread {
    //票的数量
    //static 表示所有的类都共享ticket这个数据
    static int MAX_TICKET_NUM = 100;
    //当前票的编号
    static Integer ticketNum = 0;

    public Seller() {
    }

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

    //同步方法
    //如果方法是非静态的,则锁对象是this
    //如果方法是静态(static)的,则锁对象是当前类的字节码对象 当前类.class
    public static synchronized Integer getTicketNum() {
        //利用同步代码块 使同步代码块里面的代码是轮流执行
        //TicketUtis.class 表示当前类的字节码 只要是同一个类那么就是唯一的
        if (ticketNum >= MAX_TICKET_NUM) {
            System.out.println("票卖完了");
            return null;
        } else {
            ticketNum++;
            return ticketNum;
        }
    }

    @Override
    //设置同步代码块
    public void run() {
        Integer ticketNum = null;
        while ((ticketNum = getTicketNum()) != null) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName() + "出售第" + ticketNum + "张票");
        }
    }
}

# lock

  • lock锁是JDK5.0之后出现的一种新的锁机制
  • 可以实现同步代码手动上锁的功能
  • **lock是接口不能直接实例化,这里采用他的实现类ReentrantLock**来实例化
package Multithreading;

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

public class ThreadText1 {
    //    -----
    //定义锁对象
    static Lock lock = new ReentrantLock();

    public static Integer getTicketNum() {
        //上锁
        lock.lock();
        //利用同步代码块 使同步代码块里面的代码是轮流执行
        //TicketUtis.class 表示当前类的字节码 只要是同一个类那么就是唯一的
        try {
            if (ticketNum >= MAX_TICKET_NUM) {
                System.out.println("票卖完了");
                return null;
            } else {
                ticketNum++;
                return ticketNum;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //写在finally里面 表示无论怎样代码执行完一定会释放锁
            //解锁
            lock.unlock();
        }
        return null;
    }
}

# 死锁

  • 死锁:是指两个或者两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们将never结束。
  • 死锁产生的条件:
  • 互斥条件:指进程对资源进行排他性使用,即一个进程使用一个资源时,其他进程不可使用这些资源。
  • 请求和保持条件:指进程已经保持至少一个资源,在请求其他资源时,资源请求被允许。
  • 不剥夺条件:指进程已经获得的资源,在未使用完之前,不能被剥夺,只能自己使用。
    • 循环等待条件:指进程之间形成一种头尾相接的循环等待资源关系。
    • 解决死锁:避免使用锁嵌套的方式编写代码

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

  • 等待唤醒机制是一个十分经典额多线程协作模式
方法名称 说明
void wait() 当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法唤醒线程
void notify() 唤醒一个正在等待的线程(随机)
void notifyAll() 唤醒所有正在等待的线程
package Multithreading;

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

public class ThreadText2 {
    //等待唤醒机制
    public static void main(String[] args) {
        //定义吃货
        Thread eater = new Thread(new Eater(), "吃货1");
        Thread eater1 = new Thread(new Eater(), "吃货2");
        Thread eater2 = new Thread(new Eater(), "吃货3");
        //定义厨师
        Thread cook = new Thread(new Cook(), "厨师");
        //开启线程
        eater.start();
        eater1.start();
        eater2.start();
        cook.start();
    }
}

class Desk implements FoodStatus {
    //设置锁
    public static final Lock lock = new ReentrantLock();
    //食物数量
    public static Integer count = 0;
    //最大的食物数量
    public static Integer maxCount = 10;
    //食物状态
    public static int status = FoodStatus.EMPTY;

}


//定义吃货
class Eater implements Runnable {
    public Eater() {
    }

    //吃货吃东西方法
    @Override
    public void run() {
        while (Desk.maxCount > 0) {
            synchronized (Desk.lock) {
                if (Desk.status == FoodStatus.EMPTY) {
                    //没菜就等待
                    try {
                        Desk.lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                } else if (Desk.status == FoodStatus.FULL) {
                    //有菜则吃菜
                    Desk.maxCount--;
                    System.out.println(Thread.currentThread().getName() + "正在吃第" + Desk.count + "个菜" + "还能吃" + Desk.maxCount + "个菜");
                    Desk.count--;

                    if (Desk.count == 0) {
                        //如果菜没了 则将状态设置为空
                        Desk.status = FoodStatus.EMPTY;
                        //通知厨师做菜
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}

//定义厨师
class Cook implements Runnable {
    //厨师每次做菜的数量
    private static Integer count = 10;

    public Cook() {

    }

    //厨师做菜
    @Override
    public void run() {
        while (Desk.maxCount > 0) {
            synchronized (Desk.lock) {
                //没有菜就做菜
                if (Desk.status == FoodStatus.EMPTY) {
                    System.out.println(Thread.currentThread().getName() + "正在做" + count + "个菜");
                    Desk.count += count;
                    Desk.status = FoodStatus.FULL;
                    //通知吃货
                    Desk.lock.notifyAll();
                } else if (Desk.status == FoodStatus.FULL) {
                    //有菜则等待
                    //让当前线程跟锁绑定
                    try {
                        Desk.lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
}


# 阻塞队列(ArrayBlockingQueue)

package Multithreading;

import java.util.concurrent.ArrayBlockingQueue;

public class ThreadText4 {
    public static void main(String[] args) {
        //阻塞队列
        //定义阻塞队列
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
        //创建厨师
        Cook1 cook = new Cook1(queue);
        //创建吃货
        Eater1 eater = new Eater1(queue);
        //创建线程
        Thread thread = new Thread(cook);
        Thread thread3 = new Thread(eater);
        //开启线程
        thread.start();
        thread3.start();
    }
}

//定义厨师类
class Cook1 implements Runnable {
    private ArrayBlockingQueue<String> queue;

    //在实例化对象的时候传递阻塞队列
    public Cook1(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    //重写run方法
    @Override
    public void run() {
        //循环生产
        while (true) {
            try {
                //生产
                queue.put("一个汉堡");
                System.out.println("厨师生产了一个汉堡");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//吃货      类
class Eater1 implements Runnable {
    private ArrayBlockingQueue<String> queue;

    //在实例化对象的时候传递阻塞队列
    public Eater1(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    //重写run方法
    @Override
    public void run() {
        //循环消费
        while (true) {
            try {
                //消费
                String food = queue.take();
                System.out.println("吃货消费了" + food);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

# 线程的状态

注意

在java当中只定义了6种状态
没有运行状态,该状态交给操作系统运行

  • 新建状态(NEW):创建线程对象

  • 就绪状态(RUNNABLE):start()方法调用后进入就绪状态

  • 阻塞状态(BLOCKED):无法获得锁对象

  • 等待状态(WAITING):调用wait()方法进入等待状态

  • 计时等待状态(TIMED_WAITING):调用sleep()方法进入计时等待状态

  • 结束状态(TERMINATED):线程全部代码执行完毕

    线程的状态

# 多线程综合练习

# 送礼品

  • 需求:有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出。
  • 利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来.
package Multithreading;

public class ThreadText5 {
    public static void main(String[] args) {
        //送礼品问题
        //- 需求:有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出。
        //- 利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来.

        //定义线程对象
        Thread user1 = new Thread(new GiftUser(), "张三");
        Thread user2 = new Thread(new GiftUser(), "李四");

        //开启线程
        user1.start();
        user2.start();
    }
}

//礼品发送人
class GiftUser implements Runnable {
    //剩余礼品数
    //最开始有100个礼品
    public static int giftNum = 100;

    public GiftUser() {
    }

    @Override
    public void run() {
        //发送礼品
        //当礼品剩余10个停止发出
        //注意判断尽量写在synchronized中 写在while条件里可能使代码加入while循环但在synchronized等待的情况
        while (true) {
            synchronized (GiftUser.class) {
                //直接在synchronized中编写条件
                if (giftNum <= 10) break;
                giftNum--;
                System.out.println(Thread.currentThread().getName() + "正在发送礼品" + "剩余" + giftNum + "个");
            }
        }
    }
}

# 打印奇数数字

  • 同时开启两个线程,共同获取1-100之间的所有数字。
  • 要求:将输出所有的奇数。
package Multithreading;

public class ThreadText6 {
    public static void main(String[] args) {
        //- 同时开启两个线程,共同获取1-100之间的所有数字。
        //- 要求:将输出所有的奇数。
        //定义线程对象
        Thread thread1 = new Thread(new PrintOdd(), "线程1");
        Thread thread2 = new Thread(new PrintOdd(), "线程2");
        //开启线程
        thread1.start();
        thread2.start();
    }
}

//打印奇数数字
class PrintOdd implements Runnable {
    public static int Maxnum = 100;

    @Override
    public void run() {
        while (true) {
            synchronized (PrintOdd.class) {
                System.out.println("当前数" + " " + Maxnum);
                if (Maxnum <= 0) break;
                if (Maxnum % 2 == 1) {
                    System.out.println(Thread.currentThread().getName() + "正在打印奇数" + Maxnum);
                }
                Maxnum--;
            }
        }
    }
}

# 抢红包也用到了多线程。

  • 假设:100块,分成了3个包,现在有5个人去抢。
  • 其中,红包是共享数据。
  • 5个人是5条线程。
  • 打印结果如下:
  • XXX抢到了XXX元
  • XXX抢到了XXX元
  • XXX抢到了XXX元
  • XXX没抢到
  • XXX没抢到
package Multithreading;

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

    //抢红包问题
    //定义线程对象
    Thread thread1 = new Thread(new Reader(), "张三");
    Thread thread2 = new Thread(new Reader(), "李四");
    Thread thread3 = new Thread(new Reader(), "王五");
    Thread thread4 = new Thread(new Reader(), "赵六");
    Thread thread5 = new Thread(new Reader(), "钱七");
    //开启线程
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();
}

//抢红包
class Reader implements Runnable {
    //剩余金额
    public static BigDecimal money = BigDecimal.valueOf(100);
    //剩余红包数
    public static int count = 3;
    //每个红包最小金额(精确小数)
    public final static BigDecimal minMoney = BigDecimal.valueOf(0.01);

    //获取金额函数

    /**
     * @param money BigDecimal 剩余金额
     * @param count int 剩余红包数
     * @return BigDecimal 抢到的金额
     */
    public BigDecimal getMoney(BigDecimal money, int count) {
        if (count == 0) return BigDecimal.valueOf(-1);
        else if (count == 1) return money;
            //        生成随机数
        else {
            Random r = new Random();
            double bound = money.subtract(BigDecimal.valueOf(count - 1).multiply(minMoney)).doubleValue();
            BigDecimal getdMoney = BigDecimal.valueOf(r.nextDouble(bound));
            if (getdMoney.compareTo(minMoney) < 0) getdMoney = minMoney;
            //保留两位小数
            return getdMoney.setScale(2, RoundingMode.HALF_UP);
        }
    }

    @Override
    public void run() {
        synchronized (Reader.class) {
            BigDecimal getdMoney = getMoney(money, count);
            if (getdMoney.compareTo(BigDecimal.valueOf(-1)) == 0) {
                System.out.println(Thread.currentThread().getName() + "没抢到");
            } else {
                System.out.println(Thread.currentThread().getName() + "抢到了" + getdMoney + "元");
                money = money.subtract(getdMoney);
                count--;
            }
        }
    }
}

# 抽奖箱抽奖

  • 有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{10,5,20,50,100,200,500,800,2,80 ,300,700};
  • 创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”“抽奖箱2随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
  • 每次抽出一个奖项就打印一个(随机)
  • 抽奖箱1 又产生了一个 10 元大奖
  • 抽奖箱1 又产生了一个 100 元大奖
  • 抽奖箱1 又产生了一个 200 元大奖
  • 抽奖箱1 又产生了一个 800 元大奖
  • 抽奖箱2 又产生了一个 700 元大奖
package Multithreading;

public class ThreadText8 {
    public static void main(String[] args) {
        //抽奖池
        Thread thread1 = new Thread(new Pool(), "抽奖1");
        Thread thread2 = new Thread(new Pool(), "抽奖2");
        thread1.start();
        thread2.start();
    }
}

//抽奖池
class Pool implements Runnable {
    public static ArrayList<Integer> list = new ArrayList<>(Arrays.asList(2, 5, 10, 20, 50, 80, 100, 200, 300, 500, 700, 800));

    @Override
    public void run() {
        while (true) {
            synchronized (Pool.class) {
                if (list.isEmpty()) {
                    System.out.println("抽奖池已空");
                    break;
                } else {
                    //抽奖
                    //打乱数组
                    Collections.shuffle(list);
                    //获取第一个
                    int prize = list.getFirst();
                    System.out.println(Thread.currentThread().getName() + "抽到了" + prize + "元");
                    //将抽中的奖品移除
                    list.removeFirst();
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

# 抽奖池抽奖2 记录奖品

  • 有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为{1,5,20,58,108,208,580,880,2,80,300,708};
  • 创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”随机从抽奖池中获取奖项元素并打印在控制台上,
  • 格式如下:
  • 每次抽的过程中,不打印,抽完时一次性打印(随机)在此次抽奖过程中,抽奖箱1总共产生了6个奖项。
  • 分别为:10,20,100,500,2,300最高奖项为30元,总计额为932元在此次抽奖过程中,抽奖箱2总共产生了6个奖项。 分别为:5,50,200,800,80,700最高奖项为800元,总计额为1835元
package Multithreading;

public class ThreadText9 {
    public static void main(String[] args) {
        //抽奖池
        Thread thread1 = new Thread(new Pool(), "抽奖1");
        Thread thread2 = new Thread(new Pool(), "抽奖2");
        thread1.start();
        thread2.start();
    }
}

class Pool implements Runnable {
    public static ArrayList<Integer> list = new ArrayList<>(Arrays.asList(2, 5, 10, 20, 50, 80, 100, 200, 300, 500, 700, 800));

    @Override
    public void run() {
        ArrayList<Integer> result = new ArrayList<>();
        while (true) {
            synchronized (Pool.class) {
                if (list.isEmpty()) {
                    System.out.println("抽奖池已空");
                    break;
                } else {
                    //抽奖
                    //打乱数组
                    Collections.shuffle(list);
                    //获取第一个
                    int prize = list.getFirst();
                    //将奖品放入result集合中
                    result.add(prize);
                    //将抽中的奖品移除
                    list.removeFirst();
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        }
        //循环结束打印结果
        //当前奖项数组
        //求最大值
        Integer[] array = result.toArray(new Integer[0]);
        Arrays.sort(array);
        int max = array[array.length - 1];
        //获取总和
        int sum = result.stream().mapToInt(Integer::intValue).sum();
        System.out.println("在此次抽奖过程中" + Thread.currentThread().getName() + "产生了" + array.length + "个奖项,最高奖项为" + max + "总计金额为" + sum);
    }
}

# 抽奖池3 多线程之间的比较

  • 在上一题基础上继续完成如下需求:
  • 在此次抽奖过程中,抽奖箱1总共产生了6个奖项,分别为:10,20,100,500,2,300最高奖项为300元,总计额为932元
  • 在此次抽奖过程中,抽奖箱2总共产生了6个奖项,分别为:5,50,200,800,80,700
  • 最高奖项为800元,总计额为1835元
  • 在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元

核心逻辑:获取线程运行的结果,然后进行比较,获取最大值,然后打印出来。

package Multithreading;

public class ThreadText10 {
    public static void main(String[] args) {
        //抽奖池
        PoolReturn poolReturn = new PoolReturn();
        PoolReturn poolReturn2 = new PoolReturn();
        ////4.创建一个`Future`对象,用来管理多线程运行的结果
        FutureTask<ArrayList<Integer>> futureTask = new FutureTask<>(poolReturn);
        FutureTask<ArrayList<Integer>> futureTask2 = new FutureTask<>(poolReturn2);
        //创建线程
        Thread thread1 = new Thread(futureTask, "抽奖1");
        Thread thread2 = new Thread(futureTask2, "抽奖2");
        //开启线程
        thread1.start();
        thread2.start();
        //获取多线程运行结果
        ArrayList<Integer> integers = futureTask.get();
        ArrayList<Integer> integers2 = futureTask2.get();
        int integersMax = todoGet(integers, thread1);
        int integers2Max = todoGet(integers2, thread2);
        int max = Math.max(integersMax, integers2Max);
        System.out.println("最大奖项为" + ((integersMax == max) ? "奖池1的" : "奖池2的") + max);
    }

    //线程返回值处理
    public static int todoGet(ArrayList<Integer> list, Thread thread) {
        //当前奖项数组
        //求最大值
        if (list.isEmpty()) return 0;
        int max = Collections.max(list);
        //获取总和
        int sum = list.stream().mapToInt(Integer::intValue).sum();
        System.out.println("在此次抽奖过程中" + thread.getName() + "产生了" + list.size() + "个奖项,最高奖项为" + max + "总计金额为" + sum);
        return max;
    }
}

//抽奖池 线程返回抽奖结果
class PoolReturn implements Callable<ArrayList<Integer>> {
    public static ArrayList<Integer> list = new ArrayList<>(Arrays.asList(2, 5, 10, 20, 50, 80, 100, 200, 300, 500, 700, 800));

    @Override
    public ArrayList<Integer> call() throws Exception {
        ArrayList<Integer> result = new ArrayList<>();
        while (true) {
            synchronized (Pool.class) {
                if (list.isEmpty()) {
                    System.out.println("抽奖池已空");
                    break;
                } else {
                    //抽奖
                    //打乱数组
                    Collections.shuffle(list);
                    //获取第一个
                    int prize = list.getFirst();
                    //将奖品放入result集合中
                    result.add(prize);
                    //将抽中的奖品移除
                    list.removeFirst();
                }
            }

        }
        return result;
    }
}

# 线程池

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

Executors线程池的工具类通过调用方法返回不同类型的线程池对象

方法名称 说明
public status ExecutorService newFixedThreadPool(int nThreads) 创建一个有上线的线程池
public static ExecutorService newCachedThreadPool() 创建一个没有上线的线程池 最多int的最大值
public class ThreadPool {
    public static void main(String[] args) {
        //线程池
//        1.创建一个有上线的线程池

        ExecutorService threadPool = Executors.newFixedThreadPool(1);
        //2.提交任务
        threadPool.submit(new Pool());
        threadPool.submit(new Pool());
        threadPool.submit(new Pool());
        threadPool.submit(new Pool());
        threadPool.submit(new Pool());

        //销毁线程(一般不需要)
//        threadPool.shutdown();
    }
}

# 自定义线程池(new ThreadPoolExecutor())

new ThreadPoolExecutor()构造函数的参数

  • 参数1:核心线程的数量 不能小于0
  • 参数2:最大线程的数量 不能小于0,最大数量>=核心线程数
  • 参数3:线程空闲时间 不能等于0
  • 参数4:线程空闲时间的单位 用TimeUnit指定
  • 参数5:线程池的阻塞队列 不能为null
  • 参数6:创建线程的工厂 不能为null
  • 参数7:拒绝策略 不能为null

细节:

  1. 当线程池满时,再提交任务就会排队
  2. 当核心线程满并且队伍也满时,才会创建临时线程
  3. 当核心和临时线程池满,并且队伍也满,就会执行拒绝策略
public class ThreadPool {
    public static void main(String[] args) {
        //自定义线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                3,//核心线程的数量 不能小于0
                5,//最大线程数量 不能小于0 最大数量>=核心线程数
                60,//线程存活时间
                TimeUnit.SECONDS,//线程存活时间的单位
                new ArrayBlockingQueue<>(3),//阻塞队列
                Executors.defaultThreadFactory(),//线程工厂
                new ThreadPoolExecutor.AbortPolicy()//拒绝策略
        );
        threadPool.submit(new Pool());
        threadPool.submit(new Pool());
        threadPool.submit(new Pool());
        threadPool.submit(new Pool());
        threadPool.submit(new Pool());
    }
}

# 线程池大小

# 最大并行数

  • 线程池的最大并行数可以根据java运行环境的可用处理器数目来决定
  • 可以
public class ThreadPool {
    public static void main(String[] args) {
        //线程池多少才合适
        //获取java虚拟机的运行环境
        Runtime runtime = Runtime.getRuntime();
        //返回可用处理器数目
        //线程池的最大并行数可以根据java运行环境的可用处理器数目来决定
        int availableProcessors = runtime.availableProcessors();
        System.out.println("当前java虚拟机的处理器数量为" + availableProcessors);//32

        System.out.println("当前java虚拟机的最大内存为" + runtime.maxMemory() / 1024 / 1024 + "MB");
    }
}

# 线程池多大合适

# cpu密集型运算

线程池数量=最大并行数+1

# I/O密集型运算

线程池数量=最大并行数量期望cpu利用率总时间(cpu计算时间+等待时间)/cpu计算时间

  • 使用 thread dump插件计算
上次更新: 2/11/2025, 5:20:39 PM