多线程

多线程

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

并发和并行

  • 并发:在同一时刻,有多个指令在单个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插件计算
上次更新 2025/2/11 17:20:39