Java_多线程

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

作用:

        提高效率

应用场景:

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

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

并发和并行

       并发:在同一时刻,有多个指令在单个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工具类获取,然后计算

  • 39
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值