Java_多线程

news2025/2/24 14:18:15

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

作用:

        提高效率

应用场景:

        只要想让多个事情同时运行就需要用到多线程

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

并发和并行

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

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

多线程的实现方式:

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

实现步骤:

        1.自己定义一个类继承Thread

        2.重写run方法

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

代码演示:
        MyThread类(继承Thread):
public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "aaa");
        }
    }
}
        测试类ThreadDemo1:
public class ThreadDemo1 {
    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();

    }
}
        运行结果:

(只截取了一部分)两个进程并发执行

        

②实现Runnable接口的方式进行实现

 实现步骤:

        1.定义一个类implements Runnable接口

        2.重写run方法

        3.创建这个类的对象

        4.创建线程对象,启动线程

代码演示:
        MyRun类(实现Runnable):
public class MyRun implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "aaa");
        }
    }
}
        测试类ThreadDemo2:
public class ThreadDemo2 {
    public static void main(String[] args) {
        /*
        1.定义一个类implements Runnable接口
        2.重写run方法
        3.创建这个类的对象
        4.创建线程对象,启动线程
         */

        //创建这个类的对象
        MyRun mr = new MyRun();

        //创建线程对象
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        //起名字
        t1.setName("线程1");
        t2.setName("线程2");

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


    }
}
        运行结果:

(只截取了一部分)两个进程并发执行

        

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

代码演示:

         MyCallable类(实现Callable):
public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum = sum + i;
        }
        return sum;
    }
}
        测试类ThreadDemo3:
public class ThreadDemo3 {
    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类的对象
        Thread t1 = new Thread(ft);

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

        //获取结果
        Integer result = ft.get();
        System.out.println(result);

    }
}
        运行结果:

        

多线程三种实现方式对比

常见成员方法:

        其中前四种方法比较简单,在此简单介绍几点

        1.线程的默认名字是Thread-序号,序号从0开始,随着进程创建按顺序逐个+1

        2.第三个第四个方法都是哪个线程实现这两个成员方法所在的方法,则是对这个线程操作

        3.让线程休眠后续代码也会运行

优先级:

        线程的优先级从1~10分为10挡,1为优先级最低,10为最高。

        优先级越高,在线程并发中抢到CPU的概率就更高(但不是绝对的)。

        线程的默认优先级都为5。

代码演示:
MyThread类:
public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}
测试类ThreadDemo4:
public class ThreadDemo4 {
    public static void main(String[] args) {
        //创建进程
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        //设置优先级
        t1.setPriority(10);
        t2.setPriority(1);

        //设置名字
        t1.setName("线程1");
        t2.setName("线程2");

        t1.start();
        t2.start();
    }
}
运行结果:

守护线程:

        特点:

                当其他非守护线程执行完毕之后,守护线程也会陆续结束,不管是否执行完代码。

代码演示:
MyThread1:
public class MyThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}
MyThread2:
public class MyThread2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i <= 10; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}
测试类ThreadDemo5:
public class ThreadDemo5 {
    public static void main(String[] args) {
        //创建进程
        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        //设置名字
        t1.setName("沸羊羊");
        t2.setName("美羊羊");

        t1.setDaemon(true);

        t1.start();
        t2.start();
    }
}
运行结果:

出让线程:

        执行Thread.yield命令后就是把CPU的执行权交出,然后各个线程重新抢夺。

插队线程:

        执行方法 ‘线程对象.join()’ 后 表示把这个线程,插入到当前线程之前,先执行完这个线程,再执行当前方法的线程。

线程的生命周期

线程的生命周期是:新建状态,就绪状态,运行状态,阻塞状态,死亡状态

虚拟机中线程的六种状态

新建状态、就绪状态、阻塞状态、等待状态、计时等待、结束状态 (没有运行状态)

多线程代码编写核心逻辑

        1.循环

        2.同步代码块

        3.判断共享数据是否到了末尾(到了末尾)

        4.判断共享数据是否到了末尾(没到末尾,执行核心逻辑)

同步代码块:

格式:

        synchronized (锁对象) {

                同步代码块

        }

特点:

        当一个线程抢到CPU的执行权进入到同步代码块中执行代码了,那么其他的线程是不能再进来同步代码块的,只有当成功进入的线程执行完毕出去之后,它的锁才打开,其他的线程才能进来,而且也只能进去一个线程。

        并且,如果锁对象不唯一,那么相当于有好几把锁,就可能出现不同的线程看的是不同的锁,还是会同时进去执行代码。一般我们用当前类的字节码文件作为锁对象(类名.class).

        先结合一个小练习进行代码演示

练习:

        某电影院目前正在上映国产大片,共有1000张票,而它有3个卖票窗口,请设计一个程序模拟该电影院买票。

代码演示:
MyThread3类:
public class MyThread3 extends Thread {
    //表示这个类所有的对象,都共享ticket数据
    static int ticket = 0;

    //锁对象,必须是唯一的
    @Override
    public void run() {
        while (true) {
            //同步代码块
            synchronized (MyThread3.class) {
                if(ticket < 1000) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    ticket++;
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                } else {
                    break;
                }
            }
        }
    }
}
测试类ThreadDemo6:
public class ThreadDemo6 {
    public static void main(String[] args) {
        //某电影院目前正在上映国产大片,共有1000张票,而它有3个卖票窗口,请设计一个程序模拟该电影院买票。

        //创建三个线程对象
        MyThread3 t1 = new MyThread3();
        MyThread3 t2 = new MyThread3();
        MyThread3 t3 = new MyThread3();

        //设置名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

    }
}
运行结果:

同步方法:

        同步方法就是把synchronized关键字加到方法上

格式:

        修饰符 synchronized 返回值类型 方法名(方法参数) {...}

特点:

        1.同步方法是锁住方法里面所有的代码

        2.锁对象不能自己指定(非静态方法中是this,静态方法中是当前类的字节码文件对象)

        继续结合上述小练习进行代码演示

练习:

        某电影院目前正在上映国产大片,共有1000张票,而它有3个卖票窗口,请设计一个程序模拟该电影院买票。

代码演示:
MyRunnable类:
public class MyRunnable implements Runnable {

    int ticket = 0;

    @Override
    public void run() {
        while(true) {
            if (method()) break;
        }
    }

    //同步方法
    private synchronized boolean method() {
        if(ticket == 1000) {
            return true;
        } else {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            ticket++;
            System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
        }
        return false;
    }
}
测试类ThreadDemo7:
public class ThreadDemo7 {
    public static void main(String[] args) {
        //某电影院目前正在上映国产大片,共有1000张票,而它有3个卖票窗口,请设计一个程序模拟该电影院买票。

        MyRunnable mr = new MyRunnable();

        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        Thread t3 = new Thread(mr);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

    }
}
运行结果:

lock锁:

        继续结合上述小练习进行代码演示

练习:

        某电影院目前正在上映国产大片,共有1000张票,而它有3个卖票窗口,请设计一个程序模拟该电影院买票。

代码演示:
MyThread类:
public class MyThread extends Thread {
    static int ticket = 0;

    //创建锁对象
    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true) {
            lock.lock();
            try {
                if(ticket == 1000) {
                    break;
                } else {
                    Thread.sleep(10);
                    ticket++;
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
        }
    }
}
测试类ThreadDemo8:
public class ThreadDemo8 {
    public static void main(String[] args) {
        //某电影院目前正在上映国产大片,共有1000张票,而它有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();

    }
}
运行结果:

死锁:

概念:

        死锁是多线程中的一种错误

        举个例子:

        两个人在一起吃饭,桌上只有一双筷子,有三个条件

                ①每次需要拿起筷子才能吃饭

                ②一次只能拿一只筷子

                ③拿到一双筷子后可以吃一口

        在一个人拿到一只筷子,另一个人也拿到一只筷子时,这时候就发生了死锁,程序无法结束

注意:

        关于死锁需要注意,在设计程序时尽量避免发生锁的嵌套

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

1.基本写法:

        假如现在有一个厨师和一个食客,食客吃10份食物就饱了,当桌子上有食物时,食客就会吃一份,吃完通知厨师再做一份,如果没有食物,就等待。厨师在桌子上没有食物时,就再做一份,如果有食物则等待。

代码演示:
桌子Desk类:
public class Desk {
    //定义变量表示桌子上是否有食物 0:没有 1:有
    public static int foodFlag = 0;
    //定义变量存储食客还能吃几碗 食客最多能吃10碗 初始值为10
    public static int count = 10;
    //锁对象
    public static Object lock = new Object();
}
食客Foodie类:
public class Foodie extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {
                //判断还能吃吗
                if(Desk.count == 0) {
                    //不能吃了
                    break;
                } else {
                    //还能吃
                    //判断桌子上有没有食物
                    if(Desk.foodFlag == 0) {
                        //如果没有食物
                        //等待(调用锁对象的wait方法让食客线程阻塞)
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    } else {
                        //如果有食物
                        Desk.count--;
                        if(Desk.count == 0) {
                            System.out.println("食客吃完了一份食物,吃饱了");
                        } else {
                            System.out.println("食客吃完了一份食物,还能吃" + Desk.count + "份");
                        }
                        //将桌子上置为没有食物
                        Desk.foodFlag = 0;
                        //通知厨师
                        Desk.lock.notifyAll();//唤醒这个锁对象内的所有线程
                    }
                }
            }
        }
    }
}
厨师Cooker类:
public class Cooker extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {
                //判断食客是否吃饱了
                if(Desk.count == 0) {
                    //吃饱了
                    break;
                } else {
                    //没吃饱
                    //判断桌子上是否有食物
                    if(Desk.foodFlag == 1) {
                        //有食物
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    } else {
                        //没有食物
                        System.out.println("厨师制作好了食物放到了桌子上");
                        //将桌子置为有食物
                        Desk.foodFlag = 1;
                        //唤醒食客
                        Desk.lock.notifyAll();//唤醒这个锁对象内的所有线程
                    }
                }
            }
        }
    }
}
测试类Test:
public class Test {
    public static void main(String[] args) {
        //创建线程对象
        Foodie foodieThread = new Foodie();
        Cooker cookerThread = new Cooker();

        //启动线程
        foodieThread.start();
        cookerThread.start();
    }
}
运行结果:

2.利用阻塞队列:

        假如现在有一个厨师和一个食客,食客吃10份食物就饱了,厨师可以不断的做好食物并放到窗口上,窗口上最多可以放3碗。当窗口上有食物时,食客就会吃,如果没有食物,就等待。

代码演示:
食客Foodie类:
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) {
                throw new RuntimeException(e);
            }

        }
    }
}
厨师Cooker类:
public class Cooker extends Thread {
    ArrayBlockingQueue<String> queue;

    public Cooker(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                queue.put("面条");
                System.out.println("厨师放了一碗面条");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
测试类Test:
public class Test {
    public static void main(String[] args) {
        /*
        假如现在有一个厨师和一个食客,
        厨师可以不断的做好食物并放到窗口上,窗口上最多可以放3碗。
        当窗口上有食物时,食客就会吃,如果没有食物,就等待。
         */

        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);

        Foodie foodieThread = new Foodie(queue);
        Cooker cookerThread = new Cooker(queue);

        foodieThread.start();
        cookerThread.start();

    }
}
运行结果:

(因为输出语句在锁的外面,所以输出结果不一定和真是数据传输一致)

练习题

练习一:

        一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒,

        要求:用多线程模拟卖票过程并打印剩余电影票的数量

代码演示:
MyThread类:
public class MyThread extends Thread {
    //定义还剩多少票
    static int ticket = 1000;

    @Override
    public void run() {
        while(true) {
            synchronized (MyThread.class) {
                //判断是否还有票
                if(ticket == 0) {
                    //没票了
                    break;
                } else {
                    //有票
                    //每次领取时间3000毫秒
                    try {
                        sleep(3000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    ticket--;
                    if(ticket != 0) {
                        System.out.println(getName() + "卖了一张票,还剩" + ticket + "张票");
                    } else {
                        System.out.println(getName() + "卖了一张票,票卖完了");
                    }
                }
            }
        }
    }
}
测试类Test:
public class Test {
    public static void main(String[] args) {
        /*
        一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒,
        要求:用多线程模拟卖票过程并打印剩余电影票的数量
         */

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

        //设置名字
        t1.setName("窗口1");
        t2.setName("窗口2");

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

    }
}
运行结果:

 练习二:

        有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再发出

        利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来

代码演示:
MyThread类:
public class MyThread extends Thread {
    //剩余礼物数量
    static int gift = 100;

    @Override
    public void run() {
        while (true) {
            synchronized (MyThread.class) {
                //判断礼物数量
                if(gift == 9) {
                    //礼物数量小于10
                    break;
                } else {
                    //礼物数量不小于10
                    gift--;
                    System.out.println(getName() + "分发了一份礼物,还剩" + gift + "份");
                }
            }
        }
    }
}
测试类Test:
public class Test {
    public static void main(String[] args) {
        /*
        有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再发出
        利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来
         */

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

        //设置名字
        t1.setName("分发员1");
        t2.setName("分发员2");

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

    }
}
运行结果:

 练习三:

        同时开启了两个线程,共同获取1~100之间的所有数字

        要求:输出所有的奇数

代码演示:
MyThread类:
public class MyThread extends Thread {
    static int num = 1;

    @Override
    public void run() {
        while (true) {
            synchronized (MyThread.class) {
                if(num > 100) {
                    break;
                } else {
                    if(num % 2 == 1) {
                        System.out.println(getName() + ":" + num);
                    }
                    num++;
                }
            }
        }
    }
}
测试类Test:
public class Test {
    public static void main(String[] args) {
        /*
        同时开启了两个线程,共同获取1~100之间的所有数字
        要求:输出所有的奇数
         */

        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

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

    }
}
运行结果:

 练习四:

        抢红包也用到了多线程

        假设:100块,分成了3个包,现在有五个人去抢

        其中,红包是共享数据 5个人是5条线程

        打印结果如下:

                XXX抢到了XXX元

                XXX抢到了XXX元

                XXX抢到了XXX元

                XXX没抢到

                XXX没抢到

代码演示:
MyThread类:
public class MyThread extends Thread {
    //红包个数
    static int count = 3;
    //红包中剩余金额
    static BigDecimal money = BigDecimal.valueOf(100);
    //红包金额最小值
    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 {
                    //还剩多个红包
                    Random r = new Random();
                    double bounds = money.subtract(MIN.multiply(BigDecimal.valueOf(count - 1))).doubleValue();
                    prize = BigDecimal.valueOf(r.nextDouble(bounds));
                    if (prize.compareTo(MIN) == -1) {
                        //prize小于MIN
                        prize = MIN;
                    }
                }
                //设置保留两位小数,四舍五入
                prize = prize.setScale(2, RoundingMode.HALF_UP);
                System.out.println(getName() + "抢到了" + prize + "元");
                //从红包金额中减去抢到的金额
                money = money.subtract(prize);
                //红包个数减一
                count--;
            }
        }
    }
}
测试类Test:
public class Test {
    public static void main(String[] args) {
        /*
        抢红包也用到了多线程
        假设:100块,分成了3个包,现在有五个人去抢
        其中,红包是共享数据
        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("小B");
        t3.setName("小C");
        t4.setName("小D");
        t5.setName("小E");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();


    }
}
运行结果:

​​​​​​​

 练习五:

        有一个抽奖池,该抽奖池中存放了奖励的金额,

        该抽奖池中的奖项为: {10,5,20,50,100,200,500,800,2,80,300,700}

        创建两个抽奖箱(线程) 随机从抽奖池中获取奖项元素并打印在控制台上

        格式如下:

                抽奖箱1产生了一个10元大奖

                抽奖箱2产生了一个100元大奖

                抽奖箱2产生了一个800元大奖

                抽奖箱1产生了一个200元大奖

                ...

代码演示:
MyThread类:
public class MyThread extends Thread {

    ArrayList<Integer> list;

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

    @Override
    public void run() {
        while((true)) {
            synchronized (MyThread.class) {
                if(list.isEmpty()) {
                    //集合为空
                    break;
                } else {
                    //集合不为空
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    System.out.println(getName() + "产生了一个" + prize + "元大奖");
                }
            }
            //锁外睡10毫秒,避免结果只出现一个线程
            try {
                sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
测试类Test:
public class Test {
    public static void main(String[] args) {
        /*
        有一个抽奖池,该抽奖池中存放了奖励的金额,
        该抽奖池中的奖项为:
        {10,5,20,50,100,200,500,800,2,80,300,700}
        创建两个抽奖箱(线程)
        随机从抽奖池中获取奖项元素并打印在控制台上
        格式如下:
            抽奖箱1产生了一个10元大奖
            抽奖箱2产生了一个100元大奖
            抽奖箱2产生了一个800元大奖
            抽奖箱1产生了一个200元大奖
            ...
        */

        //创建抽奖池
        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();
    }
}
运行结果:

​​​​​​​​​​​​​​​​​​​​​​​​​​​​

 练习五Pro1:

        在上一题(练习五)的基础上继续完成如下需求:

         每次抽的过程中,不打印,抽完时一次性打印(随机)

        格式如下:

                在此次抽奖过程中,抽奖箱1总共产生了6个奖项

                分别为:10,20,100,500,2,300最高奖项为300元,总计额为932元

                在此次抽奖过程中,抽奖箱2总共产生了6个奖项

                分别为:5,50,200,800,80,700最高奖项为800元,总计额为1835元

代码演示:
MyThread类:
public class MyThread extends Thread {

    ArrayList<Integer> list;

    int max = 0;
    int sum = 0;

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

    public String boxToString(ArrayList<Integer> box) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < box.size(); i++) {
            if (i != box.size() - 1) {
                sb.append(box.get(i) + ",");
            } else {
                sb.append(box.get(i));
            }
        }
        return sb.toString();
    }

    @Override
    public void run() {
        ArrayList<Integer> box = new ArrayList<>();
        while ((true)) {
            synchronized (com.han.thread.test5.MyThread.class) {
                if (list.isEmpty()) {
                    //集合为空
                    System.out.println("在此次抽奖过程中," + getName() + "总共产生了" + box.size() + "个奖项\n" +
                            "分别为:" + boxToString(box) + "最高奖项为" + max + "元,总计额为" + sum + "元");
                    break;
                } else {
                    //集合不为空
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    box.add(prize);
                    sum = sum + prize;
                    if (prize > max) {
                        max = prize;
                    }
                    //System.out.println(getName() + "产生了一个" + prize + "元大奖");
                }
            }
            //锁外睡10毫秒,避免结果只出现一个线程
            try {
                sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }


    }
}
测试类Test:
public class Test {
    public static void main(String[] args) {
        /*
        在上一题(练习五)的基础上继续完成如下需求:
            每次抽的过程中,不打印,抽完时一次性打印(随机)格式如下:
                在此次抽奖过程中,抽奖箱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();

    }
}
运行结果:

​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

 练习五Pro2:

        在上一题基础上继续完成如下需求:

         每次抽的过程中,不打印,抽完时一次性打印(随机)

        格式如下:

                在此次抽奖过程中,抽奖箱1总共产生了6个奖项

                分别为:10,20,100,500,2,300最高奖项为300元,总计额为932元

                在此次抽奖过程中,抽奖箱2总共产生了6个奖项

                分别为:5,50,200,800,80,700最高奖项为800元,总计额为1835元

                在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为800元

代码演示:
MyCallable类:
public class MyCallable implements Callable<Integer> {

    ArrayList<Integer> list;

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

    public String boxToString(ArrayList<Integer> box) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < box.size(); i++) {
            if (i != box.size() - 1) {
                sb.append(box.get(i) + ",");
            } else {
                sb.append(box.get(i));
            }
        }
        return sb.toString();
    }

    @Override
    public Integer call() throws Exception {
        int max = 0;
        int sum = 0;
        ArrayList<Integer> box = new ArrayList<>();
        while ((true)) {
            synchronized (com.han.thread.test5.MyThread.class) {
                if (list.isEmpty()) {
                    //集合为空
                    System.out.println("在此次抽奖过程中," + Thread.currentThread().getName() + "总共产生了" + box.size() + "个奖项\n" +
                            "分别为:" + boxToString(box) + "最高奖项为" + max + "元,总计额为" + sum + "元");
                    break;
                } else {
                    //集合不为空
                    Collections.shuffle(list);
                    int prize = list.remove(0);
                    box.add(prize);
                    sum = sum + prize;
                    if (prize > max) {
                        max = prize;
                    }
                    //System.out.println(getName() + "产生了一个" + prize + "元大奖");
                }
            }
            //锁外睡10毫秒,避免结果只出现一个线程
            Thread.sleep(10);
        }
        if(box.isEmpty()) {
            return null;
        } else {
            return Collections.max(box);
        }
    }



}
测试类Test:
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /*
         在上一题基础上继续完成如下需求:
            每次抽的过程中,不打印,抽完时一次性打印(随机)格式如下:
                在此次抽奖过程中,抽奖箱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();

        //输出结果
        int max1 = ft1.get();
        int max2 = ft2.get();
        if (max1 > max2) {
            System.out.println("在此次抽奖过程中,抽奖箱1中产生了最大奖项,该奖项金额为" + max1 + "元");
        } else {
            System.out.println("在此次抽奖过程中,抽奖箱2中产生了最大奖项,该奖项金额为" + max2 + "元");
        }

    }
}
运行结果:

​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

线程池

核心原理:

        ①创建一个池子,池子中是空的

        ②提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可

        ③但是如果提交任务时,池子中没有空闲的线程,也无法创建新的线程,任务就会排队等待

创建方法:

代码演示:

MyRunnable类:
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 1000; i++) {
            System.out.println(Thread.currentThread().getName() + "-" + i);
        }
    }
}
测试类ThreadPoolDemo1:
public class ThreadPoolDemo1 {
    public static void main(String[] args) throws InterruptedException {

        //创建一个没有上限的线程池(上限二十一亿多,但是创建不了这么多机器就承受不住)
        ExecutorService pool1 = Executors.newCachedThreadPool();
        System.out.println("无上限线程池");
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());

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


        //创建一个有上线的线程池
        ExecutorService pool2 = Executors.newFixedThreadPool(3);
        System.out.println("有上限线程池");
        pool2.submit(new MyRunnable());
        pool2.submit(new MyRunnable());
        pool2.submit(new MyRunnable());
        pool2.submit(new MyRunnable());
        pool2.submit(new MyRunnable());
        //销毁线程池
        pool2.shutdown();

    }
}

自定义线程池

核心原理:

        ①创建一个池子,池子中是空的

        ②有任务提交时,线程池会创建线程去执行任务,执行完毕归还线程

不断的提交任务,会有以下三个临界点

        ①当核心线程满时,再提交任务就会排队

        ②当核心线程满,队伍满时,会创建临时线程

        ③当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略

任务拒绝策略:

创建代码演示:

public class ThreadPoolDemo2 {
    public static void main(String[] args) {
        /*
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        核心线程数量,最大线程数量,空闲线程最大存活时间,时间单位,任务队列,创建线程工厂,任务的拒绝策略)

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

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                3,
                6,
                60,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
                );

    }
}

线程池多大合适

前置概念:

最大并行数:

        如果电脑是四核八线程,那么最大并行数就是8

        我的电脑是八核十六线程,所以最大并行数是16

1.CPU密集型运算:

        计算比较多,读取本地文件或者数据库的操作比较少

线程池大小:

        这种情况线程池大小最好为最大并行数+1=17,多出来那一个用来候补,,在遇到问题时顶上去

2.I/O密集型运算:

        读取本地文件或者数据库的操作比较多

线程池大小:

        有下面公式求出

​​​​​​​

        这里期望CPU利用率我们设为100%,假如要从本地文件中读取两个数据并进行相加,获取数据由硬盘完成,不计算在CPU计算时间中,假设读取数据用1秒,CPU计算用1秒,那么总时间就为2秒,线程池大小就等于16(最大并行数)* 100% * 2/1 = 32

        CPU计算时间可以通过thread dump工具类获取,然后计算

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

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

相关文章

一文详解逻辑越权漏洞

1. 逻辑越权 1.1. 漏洞原理 逻辑越权漏洞就是当用户跳过自己的权限限制&#xff0c;去操作同等级用户或者上级用户。正常的情况下&#xff0c;当一个用户去访问某个资源的时候&#xff0c;首先需要去登录验证自己的权限&#xff0c;其次是对数据的查询&#xff0c;最后返回数…

2024年学浪视频怎么下载到手机相册

随着2024年的到来&#xff0c;学浪平台继续为广大学习者提供优质的在线教育资源。然而&#xff0c;如何将这些宝贵的视频内容下载到手机相册&#xff0c;方便随时离线观看呢&#xff1f;无论您是想在旅途中学习&#xff0c;还是希望在没有网络的情况下复习课程&#xff0c;本文…

Linux之单机项目部署

1、虚拟机&#xff08;VMware&#xff09;创建Linux系统 1.1、创建虚拟机 1.2、配置虚拟机IOS映射文件 1.3、虚拟机内部相关配置 等待加载即可&#xff0c;加载完后会弹出图形化界面&#xff0c;如图&#xff1a; 注意&#xff1a;一般我们做为管理员使用ROOT账号来操作&#x…

Java之SpringSecurity使用心得

文章目录 一、内存身份认证二、jdbc身份认证三、自定义登录页 一、内存身份认证 添加pom依赖 <!-- Spring Security提供的安全管理依赖启动器 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-s…

串口服务器在工业控制领域的应用:深度解析与前沿实践

在工业控制领域&#xff0c;随着技术的不断发展&#xff0c;传统的串口通信方式已经难以满足现代工业系统对高效、稳定、安全通信的需求。此时&#xff0c;串口服务器作为一种先进的通信技术解决方案&#xff0c;正在逐步改变工业控制领域的通信格局。本文将深度解析串口服务器…

第十届水利、土木工程国际学术会议暨工程安全与防灾论坛 (ICHCE ESDP 2024)

文章目录 一、会议详情二、重要信息三、会议简介四、组织单位五、出席嘉宾六、大会议程七、咨询 一、会议详情 二、重要信息 会议官网&#xff1a;www.ichce.org 大会时间&#xff1a;2024年8月9-11日 最后一轮截稿时间&#xff1a;2024年6月30日 报名截止时间&#xff1a;2…

得物小程序逆向+qt可视化(不含sku)

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;wx a15018601872 本文章未…

linux-配置服务器之间 ssh免密登录

前言 在管理多台Linux服务器时,为了方便操作和自动化任务,实现服务器之间的SSH免密登录是非常有必要的。SSH免密登录可以避免每次远程连接时输入密码,大大提高效率。本文将详细介绍SSH免密登录的原理和实现步骤。 一、原理解释 SSH免密登录的实现依赖于SSH密钥对,主要是利用…

为什么手机冬天续航短 – 锂电池的温度特性曲线

原文出自微信公众号【小小的电子之路】 相信大家都有这样的经历&#xff1a;手机的续航能力在寒冷的冬天会有一定程度的降低&#xff0c;有些手机甚至充不进去电。在这种情况下&#xff0c;有些人可能会在手机上贴一个暖宝宝。其实这个问题不止出现在手机上&#xff0c;大家如果…

Mybatis Cache(一)MybatisCache+Redis

前面提到了&#xff0c;使用mybatis cache&#xff0c;一般是结合redis使用。 一、demo 1、数据表 create table demo.t_address (id int auto_incrementprimary key,address_name varchar(200) null,address_code varchar(20) null,address_type int n…

Java进阶学习笔记4——Static应用知识:代码块

代码块&#xff1a; 代码块是类的五大成员之一&#xff08;成员变量、构造器、方法、代码块、内部类&#xff09;。 Java类生命周期&#xff1a;加载、验证、准备、初始化、卸载。 代码块分为两种&#xff1a; 静态代码块&#xff1a; 格式&#xff1a;static {} 特点&…

Linux--网络通信(一)概述

网络通信概述 网络通信本质上是一种进程间通信&#xff0c;是位于网络中不同主机上的进程之间的通信&#xff0c;属于 IPC 的一种&#xff0c; 通常称为 socket IPC。所以网络通信是为了解决在网络环境中&#xff0c;不同主机上的应用程序之间的通信问题。 大概可以分为三个层…

物联网应用开发--STM32与机智云通信(ESP8266 Wi-Fi+手机APP+LED+蜂鸣器+SHT20温湿度传感器)

实现目标 1、熟悉机智云平台&#xff0c;会下载APP 2、熟悉新云平台创建产品&#xff0c;项目虚拟调试 3、掌握云平台生成MCU代码&#xff0c;并移植。机智云透传固件的下载 4、具体目标&#xff1a;&#xff08;1&#xff09;注册机智云平台&#xff1b;&#xff08;2&…

转行3年涨薪300%,我总结了一套产品经理快速入门指南!

想转行的产品小白&#xff0c;初期一定会遇到这个问题——我要如何 0 基础转行产品经理&#xff1f; 要想 0 基础快速转行产品经理&#xff0c;我通过个人实践总结了 5 个关键点&#xff0c;可以参考。 一、熟悉产品经理的工作全流程 转行的产品小白&#xff0c;首先要建立产…

mninst数据集图片下载

//不需要在官网下载&#xff0c;直接通过python代码&#xff0c;利用pytorch下载即可 from icecream import ic from torchvision import datasets from tqdm import tqdm import ostrain_data datasets.MNIST(root"./data/", trainTrue, downloadTrue) test_data …

VMware虚拟机桥接无线网卡上网(WIFI)

一、打开VM点击【编辑】-【虚拟网络编辑器】 二、点击【桥接模式】- 点击【自动设置】- 选择自己的无线网适配器 - 【确定】 三、开机之后会弹出提示连接网络&#xff0c;就能看见网络已经连上了

FastCopy

目录 背景: 简介&#xff1a; 原理: 下载地址: 工具的使用: 背景: 简介&#xff1a; FastCopy是一款速度非常快的拷贝软件&#xff0c;软件版本为5.7.1 Fastcopy是日本的最快的文件拷贝工具&#xff0c;磁盘间相互拷贝文件是司空见惯的事情&#xff0c;通常情况…

零一万物Yi-1.5开源,34B/9B/6B多尺寸,34B超Qwen1.5-72B

前言 近年来&#xff0c;大型语言模型&#xff08;LLM&#xff09;在各个领域展现出惊人的能力&#xff0c;为人们的生活和工作带来了巨大的改变。然而&#xff0c;大多数开源 LLM 的性能仍然无法与闭源模型相媲美&#xff0c;这限制了 LLM 在科研和商业领域的进一步应用。为了…

C# 利用Xejen框架源码,我们来开发一个基于Dapper技术的数据库通用的帮助访问类,通过Dapper的增删改查,可以访问Sqlite数据库

Dapper 是一个轻量级的对象关系映射&#xff08;ORM&#xff09;工具&#xff0c;适用于 .NET 平台。它由 Stack Overflow 团队开发&#xff0c;旨在提供简单、高效的数据访问功能。与其他重量级 ORM&#xff08;如 Entity Framework&#xff09;相比&#xff0c;Dapper 更加轻…

【算法例题】n元钱买n只鸡

题目描述&#xff1a;公鸡5元1只&#xff0c;母鸡3元1只&#xff0c;小鸡1元3只&#xff0c;问&#xff1a;n元钱买n只鸡&#xff0c;怎么买&#xff1f; 解题思路&#xff1a;这题要用枚举算法&#xff0c;枚举鸡的数量&#xff0c;代码如下&#xff1a; ​#include <bit…