【JavaSE】基础知识复习(三)

目录

1.异常

Java异常体系

Throwable

Error和Exception

编译时异常和运行时异常

受检异常和非受检异常

常见的错误和异常

Error

运行时异常

编译时异常

异常的处理

Java异常处理(两种方式)

try-catch-finally捕获异常

确保资源关闭

throws抛出异常

方法重写中对throws的要求

两种异常处理方式的选择

手动抛出异常对象:throw

使用格式

注意点

自定义异常

注意点

小结

2.多线程

概念

程序、进程与线程

查看进程和线程

线程调度

多线程程序的优点

 并发和并行

多线程的实现方式(三种)

(1)继承Thread类

(2)实现Runnable接口

(3)利用Callable接口和Future接口

多线程中的常用成员方法

线程名称

获取当前线程对象

线程休眠

线程优先级

线程调度

守护线程

礼让线程

插队线程

线程的生命周期

同步代码块

多线程处理同一数据:多窗口售票

问题思考

解决思路

Synchronized锁

修改源代码(两种方式)

① 继承Thread类

② 实现Runnable接口

同步方法

线程销毁

Lock锁

死锁(一种错误)

等待唤醒机制(两种)

生产者/消费者实现

理想情况

消费者等待(wait¬ify)

生产者等待

完整的生产者消费者机制

常见方法

代码实现

消费者代码实现

生产者代码实现

编写测试类

阻塞队列实现

阻塞与队列

阻塞队列的继承结构

代码案例

多线程的四步套路

多线程的六大状态

为什么Java的线程没有运行状态?

多线程练习题

第一题

第二题

第三题

第四题

用double实现

用BigDecimal实现

第五题

第六题

第七题

第八题

线程池

常见方法

具体实现

自定义线程池

ThreadPoolExecutor实现类

理解七个参数

线程池处理逻辑(重要)

任务拒绝策略

代码实现


1.异常

 

Java异常体系

Throwable

Error和Exception

编译时异常和运行时异常

受检异常和非受检异常

常见的错误和异常

Error

运行时异常
import org.junit.Test;
import java.util.Scanner;
public class TestRuntimeException {
    @Test
    public void test01(){
        //NullPointerException
        int[][] arr = new int[3][];
        System.out.println(arr[0].length);
    }
    @Test
    public void test02(){
        //ClassCastException
        Object obj = 15;
        String str = (String) obj;
    }
    @Test
    public void test03(){
        //ArrayIndexOutOfBoundsException
        int[] arr = new int[5];
        for (int i = 1; i <= 5; i++) {
            System.out.println(arr[i]);
        }
    }
    @Test
    public void test04(){
        //InputMismatchException
        Scanner input = new Scanner(System.in);
        System.out.print("请输入一个整数:");//输入非整数
        int num = input.nextInt();
        input.close();
    }
    @Test
    public void test05(){
        int a = 1;
        int b = 0;
        //ArithmeticException
        System.out.println(a/b);
    }
}
编译时异常
import org.junit.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class TestCheckedException {
    @Test
    public void test06() {
        Thread.sleep(1000);//休眠 1 秒 InterruptedException
    }
    @Test
    public void test07(){
        Class c = Class.forName("java.lang.String");//ClassNotFoundEx
        ception
    }
    @Test
    public void test08() {
        Connection conn = DriverManager.getConnection("...."); //SQL
        Exception
    }
    @Test
    public void test09() {
        FileInputStream fis = new FileInputStream("尚硅谷 Java 秘籍.txt
                "); //FileNotFoundException
    }
    @Test
    public void test10() {
        File file = new File("尚硅谷 Java 秘籍.txt");
        FileInputStream fis = new FileInputStream(file);//FileNotFound
        Exception
        int b = fis.read();//IOException
        while(b != -1){
            System.out.print((char)b);
            b = fis.read();//IOException
        }
        fis.close();//IOException
    }
}

异常的处理

Java异常处理(两种方式)

try-catch-finally捕获异常

确保资源关闭
import java.util.InputMismatchException;
import java.util.Scanner;
public class TestFinally {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        try {
            System.out.print("请输入第一个整数:");
            int a = input.nextInt();
            System.out.print("请输入第二个整数:");
            int b = input.nextInt();
            int result = a/b;
            System.out.println(a + "/" + b +"=" + result);
        } catch (InputMismatchException e) {
            System.out.println("数字格式不正确,请输入两个整数");
        }catch (ArithmeticException e){
            System.out.println("第二个整数不能为 0");
        } finally {
            System.out.println("程序结束,释放资源");
            input.close();
        }
    }

    @Test
    public void test1(){
        FileInputStream fis = null;
        try{
            File file = new File("hello1.txt");
            fis = new FileInputStream(file);//FileNotFoundException
            int b = fis.read();//IOException
            while(b != -1){
                System.out.print((char)b);
                b = fis.read();//IOException
            }
        }catch(IOException e){
            e.printStackTrace();
        }finally{
            try {
                if(fis != null)
                    fis.close();//IOException
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

throws抛出异常

import java.util.InputMismatchException;
import java.util.Scanner;
public class TestThrowsRuntimeException {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        try {
            System.out.print("请输入第一个整数:");
            int a = input.nextInt();
            System.out.print("请输入第二个整数:");
            int b = input.nextInt();
            int result = divide(a,b);
            System.out.println(a + "/" + b +"=" + result);
        } catch (ArithmeticException | InputMismatchException e) {
            e.printStackTrace();
        } finally {
            input.close();
        }
    }
    public static int divide(int a, int b)throws ArithmeticException{
        return a/b;
    }
}
方法重写中对throws的要求

两种异常处理方式的选择

手动抛出异常对象:throw

使用格式

注意点

自定义异常

注意点

举例 1:
class MyException extends Exception {
    static final long serialVersionUID = 23423423435L;
    private int idnumber;
    public MyException(String message, int id) {
        super(message);
        this.idnumber = id;
    }
    public int getId() {
        return idnumber;
    }
}
public class MyExpTest {
    public void regist(int num) throws MyException {
        if (num < 0)
            throw new MyException("人数为负值,不合理", 3);
        else
            System.out.println("登记人数" + num);
    }
    public void manager() {
        try {
            regist(100);
        } catch (MyException e) {
            System.out.print("登记失败,出错种类" + e.getId());
        }
        System.out.print("本次登记操作结束");
    }
    public static void main(String args[]) {
        MyExpTest t = new MyExpTest();
        t.manager();
    }
}

小结


2.多线程

概念

程序、进程与线程

其实不用背那么拗口的概念,一句话,有了多线程,可以让程序在同一时间做多件事情

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

查看进程和线程
  • 每个应用程序的运行都是一个进程

  • 一个应用程序的多次运行,就是多个进程

  • 一个进程中包含多个线程

线程调度

多线程程序的优点

 并发和并行
  • 正常运行的程序(软件)就是一个独立的进程

  • 线程是属于进程,一个进程中包含多个线程

  • 进程中的线程其实并发和并行同时存在

什么是并发?

进程中的线程由CPU负责调度执行,但是CPU同时处理线程的数量是优先的,为了保证全部线程都能执行到,CPU采用轮询机制为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。(简单记:并发就是多条线程交替执行)

这么说的的我自己都乱了,举个例子:

假设有三个任务 A、B 和 C:

  • 在并发的情况下:

    • 任务 A 开始执行,然后被暂停
    • 任务 B 开始执行,再暂停
    • 任务 C 开始执行
    • 然后再回到任务 A 或 B 等等...

    这种情况下,虽然每个任务只有在其轮到的时候才会执行,但它们是交替进行的。

  • 在并行的情况下:

    • 如果有 3 个 CPU 核心,任务 A、B 和 C 可以同时在不同的核心上执行。

什么是并行?

 多个指令(也就是多个线程,多个任务)同时被CPU调度执行

  • 并发与并行有可能同时在发生

多线程的实现方式(三种)

(1)继承Thread类

(2)实现Runnable接口

(3)利用Callable接口和Future接口

前两种方法重写run()都是void无返回值的,无法获得run()运行的结果,而 ...

多线程中的常用成员方法

线程名称

可以通过setName设置名称

可以通过构造器设置名称

默认名称是Thread-X(X是需要,从0开始的)

可以通过getName获取名称

当JVM虚拟机启动之后,会自动的启动多条线程

其中有一条线程就叫做main线程

他的作用就是去调用main方法,并执行里面的代码

在以前,我们所写的代码,都是运行在main线程当中

获取当前线程对象

线程休眠

线程优先级

Java中,线程优先级默认是5,最小是1,最大是10

  • 优先级越大,抢占cpu的概率越高
  • 记住,只是概率越高,而不是百分百能抢到cpu的执行权,多运行几次试试看

线程调度

在计算机当中,线程调度分为 抢占式调度非抢占式调度

  • 抢占式调度(随机性):多个线程抢夺cpu的执行权,cpu在同一时刻执行哪一个线程是不确定的,执行多长时间也是不确定的
  • 非抢占式调度(有序性):多个线程轮流的执行,你一次我一次,执行的时间也是差不多的

在Java中,采用的是抢占式调度

守护线程

非守护线程执行完毕后,守护线程没有存在的必要,会陆续结束

如何理解陆续结束?

        比如女神线程打印100个数,守护线程也打印100个数,当女神线程打印完后,守护线程没有存在的必要了就会自动进入死亡状态,但是在进入之前的一瞬间还是能执行部分线程程序的,比如打印了30个数就结束了

礼让线程

为了让多个线程抢占cpu执行权,概率尽量均匀一些

注意不是完全均匀!!

插队线程

线程的生命周期

就绪状态:有抢cpu的资格但是还没抢到,所以没有执行权

其实应该还有wait和notify还有阻塞与锁,这里老师并没有讲,不知道为啥

同步代码块

将操作共享数据的代码块锁起来,让所有线程轮流执行这段代码块

多线程处理同一数据:多窗口售票
public class MyThread extends Thread {
    // 被多线程操作的共享数据
    static int ticket = 0;// 0 ~ 99
    @Override
    public void run() {
         while (true) {
             if (ticket < 10) {
                 try {
                     Thread.sleep(100);
                 } catch (InterruptedException e) {
                     throw new RuntimeException(e);
                 }
                 ticket++;
                 System.out.println(getName() + "售出第:" + ticket + "张门票。");
             }else {
                 break;
             }
         }
    }
}
public class MyTest {
    public static void main(String[] args) {
        /*
            需求:
                某电影院目前正在上映国产大片,共有100张票,而有三个售票窗口,模拟程序。
         */
        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();
    }
}

代码逻辑上似乎没问题,每次某个窗口卖出一张票时,就会调用sleep()睡眠100ms,避免卖票太快,但是运行结果发现

问题思考

多线程处理同一数据引发的问题:

  1. 相同的数据出现了多次
  2. 出现了超出范围的数据
解决思路

当有一个线程抢到执行权,就锁住这块代码,就算其他线程也抢到了执行权,也要等着,等执行完,其他线程才能轮流进入

将线程的随机性变成有序性

Synchronized锁

锁对象,一定要是唯一的,确定锁的唯一

修改源代码(两种方式)

修改上文源代码

① 继承Thread类
public class MyTest {
    public static void main(String[] args) {
        /*
            需求:
                某电影院目前正在上映国产大片,共有100张票,而有三个售票窗口,模拟程序。
         */
        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();
    }
}
public class MyThread extends Thread {
    // 被多线程操作的共享数据
    static int ticket = 0;// 0 ~ 99

    // 锁对象,一定要是唯一的
    static Object obj = new Object();

    @Override
    public void run() {
         while (true) {
             // 同步代码块
             synchronized (obj) {
                 if (ticket < 100) {
                     try {
                         Thread.sleep(10);
                     } catch (InterruptedException e) {
                         throw new RuntimeException(e);
                     }
                     ticket++;
                     System.out.println(getName() + "售出第:" + ticket + "张门票。");
                 }else {
                     break;
                 }
             }
         }
    }
}

② 实现Runnable接口
public class MyTest {
    public static void main(String[] args) {
        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();
    }
}
public class MyRunnable implements Runnable{
    // 这种实现方式反正也就创建一次对象,不需要static修饰
    int ticket = 0;

    @Override
    public void run() {
        // 1.循环
        while (true) {
            // 2.同步代码块(同步方法)
            synchronized (MyRunnable.class) {
                // 3.判断共享数据是否到了末尾,如果到了末尾
                if (ticket == 100) {
                    break;
                }else {
                    // 4.如果没到末尾
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    ticket++;
                    System.out.println(Thread.currentThread().getName() + "售出" + ticket + "张票。");
                }
            }
        }
    }
}

同步方法

给上文代码抽取成方法

public class MyRunnable implements Runnable{
    // 这种实现方式反正也就创建一次对象,不需要static修饰
    int ticket = 0;

    @Override
    public void run() {
        // 1.循环
        while (true) {
            // 2.同步代码块(同步方法)
            if (method()) break;
        }
    }

    // 是非静态方法,锁对象是this
    private synchronized boolean method() {
        // 3.判断共享数据是否到了末尾,如果到了末尾
        if (ticket == 100) {
            return true;
        }else {
            // 4.如果没到末尾
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            ticket++;
            System.out.println(Thread.currentThread().getName() + "售出" + ticket + "张票。");
        }
        return false;
    }
}
public class MyTest {
    public static void main(String[] args) {
        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锁

Synchronized锁的上锁和释放锁都是自动的,Lock锁提供手动上锁手动释放锁

下面这个例子的代码逻辑错了,不过lock的使用没问题可以看

多线程&JUC-16-lock锁_哔哩哔哩_bilibili

public class MyThread extends Thread{
    static int ticket = 0;

//    static Object obj = new Object();

    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();
//            synchronized (obj) {
            try {
                // 同步代码块
                if (ticket < 100) {
                        Thread.sleep(30);
                    ticket++;
                    System.out.println(getName() + "售出第:" + ticket + "张门票。");
                }else {
                    break;
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
//            }
        }
    }
}
package com.autism;

/**
 * @Author oi
 * @Date 2024/8/4 5:01
 * @Description:
 */
public class MyTest {
    public static void main(String[] args) {
        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();
    }
}

死锁(一种错误)

两个线程分别拿着自己的锁,并且等着对方释放锁,从而卡住

锁的嵌套容易导致死锁

等待唤醒机制(两种)

生产者/消费者实现

Java的线程调度是抢占式调度,具有随机性,生产者消费者模式将打破这种机制,变成轮流执行,你一次我一次

理想情况
  • 厨师抢到了CPU的执行权,发现桌上空的,厨师将饭端上桌,释放线程
  • 顾客抢到了CPU执行权,顾客吃饭

厨师端一碗,顾客吃一碗,厨师再端一碗,顾客再吃一碗

消费者等待(wait&notify)

但是Java的线程调度具有随机性,程序不可能这么听话,不总会按照我们想着的这么听话

  • 顾客先抢到了CPU的执行权,发现桌上空的,于是进入等待状态(wait)
  • 此时厨师就可以抢到CPU的执行权,发现桌上空的,端上了一碗饭
  • 可惜此时顾客依旧处于wait,厨师还需要一次唤醒(notify),提醒顾客吃饭

生产者等待
  • 厨师先抢到了CPU的执行权,发现桌上空的,端上一碗饭,唤醒(notify)
  • 厨师02紧跟着抢到了CPU的执行权,发现桌上有饭,只能等着(wait)
完整的生产者消费者机制

常见方法

代码实现
消费者代码实现

代码实现逻辑是有问题的,看看方法使用就好

public class Foodie extends Thread{
    @Override
    public void run() {
        while (true) {
            //同步代码块
            synchronized (Desk.lock) {
                if (Desk.count == 0) {
                    break;
                }else {
                    if (Desk.foodFlag == 0) {
                        // 如果没有,则等待
                        try {
                            Desk.lock.wait(); // 让当前线程跟锁进行绑定
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    } else {
                        // 如果有,则吃掉一碗
                        Desk.count--;
                        System.out.println("顾客吃了一碗面条,还剩下" + Desk.count + "碗。");
                        // 吃完之后,唤醒厨师继续做
                        Desk.lock.notifyAll();
                        // 修改桌子的状态
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}
public class Desk {
    /**
     * 作用:控制生产者和消费者的执行
     *
     */

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

    // 总个数 (模拟判断条件)
    public static int count = 10;

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

生产者代码实现
public class Cook 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();
                    }
                }
            }
        }
    }
}
public class Desk {
    /**
     * 作用:控制生产者和消费者的执行
     *
     */

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

    // 总个数 (模拟判断条件)
    public static int count = 10;

    // 锁对象
    public static Object lock = new Object();
}
编写测试类
public class MyTest {
    public static void main(String[] args) {
        /**
         *
         *  需求:完成生产者和消费者(等待唤醒机制)的代码
         *        实现线程轮流交替执行的效果
         *
         */

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

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

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

阻塞队列实现
阻塞与队列
  • 理解阻塞?

  • 理解队列

数据在队列中,像是在排队一样,先进先出,后进后出,是可以设置最大值的

阻塞队列的继承结构

代码案例

  • 测试类

在测试类中再创建阻塞队列的对象

注意此时输出语句是在锁的外面的,所以输出可能是乱的,但是程序本身没有错误

多线程的四步套路

建议按照这个步骤写多线程,实用率高

多线程的六大状态

Java中并没有运行这个状态,是黑马的阿伟老师自己加的

NEW: 新建状态,线程还没有启动
RUNNABLE: 可以运行状态,线程调用了start()方法后处于这个状态
BLOCKED: 锁阻塞状态,没有获取到锁处于这个状态
WAITING: 无限等待状态,线程执行时被调用了wait方法处于这个状态
TIMED_WAITING: 计时等待状态,线程执行时被调用了sleep(毫秒)或者wait(毫秒)方法处于这个状态
TERMINATED: 终止状态, 线程执行完毕或者遇到异常时,处于这个状态。

为什么Java的线程没有运行状态?

当线程抢到CPU执行权的时候,JVM便将此线程交给操作系统,自己不管了


多线程练习题

第一题

public class TicketWindow extends Thread{
    // 1000张电影票
    static int ticketCount = 1000;

    // lock锁
    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                // 同步代码块
                if (ticketCount == 0) {
                    break;
                }else {
                    Thread.sleep(3000);
                    ticketCount--;
                    System.out.println(getName() + "卖出了1张票,还剩:" + ticketCount + "张票。");
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
        }
    }
}
public class MyTest {
    public static void main(String[] args) {
        TicketWindow tw1 = new TicketWindow();
        TicketWindow tw2 = new TicketWindow();
        TicketWindow tw3 = new TicketWindow();

        tw1.setName("①号售票窗口");
        tw2.setName("②号售票窗口");
        tw3.setName("③号售票窗口");

        tw1.start();
        tw2.start();
        tw3.start();
    }
}
第二题

public class Gift extends Thread{
    static int giftCount = 100;
    static Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
                // 同步代码块
                if (giftCount < 10) {
                    break;
                }else {
                    giftCount--;
                    System.out.println(getName() + "送出了1份礼物,还剩下:" + giftCount + "份。");
                }
            }
        }
    }
}
public class MyTest {
    public static void main(String[] args) {
        Gift g1 = new Gift();
        Gift g2 = new Gift();

        g1.setName("①号送礼人");
        g2.setName("②号送礼人");

        g1.start();
        g2.start();
    }
}
第三题

public class Number extends Thread{
    static int number = 1;
    static Object obj = new Object();
    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
                if (number == 100) {
                    break;
                }else {
                    if (number % 2 == 1) {
                        System.out.println(getName() + "输出奇数:" + number);
                    }
                    number++;
                }
            }
        }
    }
}
public class MyTest {
    public static void main(String[] args) {
        Number n1 = new Number();
        Number n2 = new Number();

        n1.setName("线程1");
        n2.setName("线程2");

        n1.start();
        n2.start();
    }
}
第四题

用double实现

这种是有精度损失的,主要是便于新手看代码逻辑,BigDecimal的加减乘除都是用方法实现的不方便看

开发中,有关金额的处理绝对不可以使用double,绝对不可以,绝对不行

public class RedPacket extends Thread{
    // 总金额100元
    static double money = 100;
    // 分成三个红包
    static int packetCount = 3;

    // 最小的中奖金额:0.01元
    static final double MIN_MONEY = 0.01;

    static Object obj = new Object();
    @Override
    public void run() {
        // 循环(一个人只能抢一次,so没必要循环)
        synchronized (obj) {
            // 同步代码块
            if (packetCount == 0) {
                // 判断,共享数据是否到了末尾(已经到了末尾)
                System.out.println(getName() + "没有抢到红包!");
            }else {
                // 判断,共享数据是否到了末尾(没有到末尾)
                // 定义一个变量,表示中奖的金额
                double prize = 0;
                if (packetCount == 1) {
                    // 此时只剩下最后一个红包
                    // 最值范围:【0.01, (100 - 第一个 - 第二个)】
                    // 无需随机,剩余的钱都是中间金额
                    prize = money;
                }else {
                    // 表示第一次,第二次(随机)
                    Random r = new Random();
                    // 100元 3个红包
                    // 第一个红包的最值范围:【0.01,99.98】
                    // 99.98 = 100 - (3-1) * 0.01
                    double bounds = money - (packetCount - 1) * MIN_MONEY;
                    prize = r.nextDouble(bounds);
                    if (prize < MIN_MONEY) {
                        // 如果小于0.01,则算做0.01
                        prize = MIN_MONEY;
                    }
                }
                // 从money当中,去掉当前中奖的金额
                money = money - prize;
                // 红包的个数 - 1
                packetCount--;
                System.out.println(getName() + "抢到了" + prize + "元。");
            }
        }
    }
}
public class MyTest {
    public static void main(String[] args) {
        RedPacket rp1 = new RedPacket();
        RedPacket rp2 = new RedPacket();
        RedPacket rp3 = new RedPacket();
        RedPacket rp4 = new RedPacket();
        RedPacket rp5 = new RedPacket();

        rp1.setName("用户1");
        rp2.setName("用户2");
        rp3.setName("用户3");
        rp4.setName("用户4");
        rp5.setName("用户5");

        rp1.start();
        rp2.start();
        rp3.start();
        rp4.start();
        rp5.start();
    }
}

用BigDecimal实现

使用BigDecimal实现可以解决精度问题,不过阿伟老师的如下给出的BigDecimal其实依旧没有解决精度问题开发中禁止使用将double直接转为BigDecimal

应该先转为字符串,再去处理,底层使用数组存储每一位,进行位运算,即可避免精度损失

常用API-08-BigDecima基本使用和原理解析_哔哩哔哩_bilibili

public class RedPacket extends Thread{
    // 总金额100元
    static BigDecimal money = BigDecimal.valueOf(100.0);
    // 分成三个红包
    static int packetCount = 3;

    // 最小的中奖金额:0.01元
    static final BigDecimal MIN_MONEY = BigDecimal.valueOf(0.01);

    static Object obj = new Object();
    @Override
    public void run() {
        // 循环(一个人只能抢一次,so没必要循环)
        synchronized (obj) {
            // 同步代码块
            if (packetCount == 0) {
                // 判断,共享数据是否到了末尾(已经到了末尾)
                System.out.println(getName() + "没有抢到红包!");
            }else {
                // 判断,共享数据是否到了末尾(没有到末尾)
                // 定义一个变量,表示中奖的金额
                BigDecimal prize;
                if (packetCount == 1) {
                    // 此时只剩下最后一个红包
                    // 最值范围:【0.01, (100 - 第一个 - 第二个)】
                    // 无需随机,剩余的钱都是中间金额
                    prize = money;
                }else {
                    // 表示第一次,第二次(随机)
                    Random r = new Random();
                    // 100元 3个红包
                    // 第一个红包的最值范围:【0.01,99.98】
                    // 99.98 = 100 - (3-1) * 0.01
//                    double bounds = money - (packetCount - 1) * MIN_MONEY;
                    double bounds = money.subtract(BigDecimal.valueOf(packetCount - 1).multiply(MIN_MONEY)).doubleValue();
//                    prize = r.nextDouble(bounds);
                    prize = BigDecimal.valueOf(r.nextDouble(bounds));
                }
                //设置抽中红包,小数点保留两位,四舍五入
                prize = prize.setScale(2, RoundingMode.HALF_UP);
                // 从money当中,去掉当前中奖的金额
//                money = money - prize;
                money = money.subtract(prize);
                // 红包的个数 - 1
                packetCount--;
                System.out.println(getName() + "抢到了" + prize + "元。");
            }
        }
    }
}
public class MyTest {
    public static void main(String[] args) {
        RedPacket rp1 = new RedPacket();
        RedPacket rp2 = new RedPacket();
        RedPacket rp3 = new RedPacket();
        RedPacket rp4 = new RedPacket();
        RedPacket rp5 = new RedPacket();

        rp1.setName("用户1");
        rp2.setName("用户2");
        rp3.setName("用户3");
        rp4.setName("用户4");
        rp5.setName("用户5");

        rp1.start();
        rp2.start();
        rp3.start();
        rp4.start();
        rp5.start();
    }
}

第五题

第六题

第七题

第八题

线程池

线程不释放,线程复用

发现复用了线程1

常见方法

两个Java自带的工具类

具体实现

public class MyThreadPool {
    public static void main(String[] args) {
        // 1.获取线程池对象
        ExecutorService pool1 = Executors.newCachedThreadPool();
//        ExecutorService pool2 = Executors.newFixedThreadPool(3);

        // 2.提交任务
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());

        // 3.销毁线程池 (一般不会销毁)
//        pool1.shutdown();
    }
}
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}

自定义线程池

Java自带的工具类Excutors不够灵活

ThreadPoolExecutor实现类

查看Executors源码,其中一个实现类ThreadPoolExecutor的构造器有7个参数

理解七个参数

线程池处理逻辑(重要)

核心线程:3

临时线程:3

阻塞队列长度:3

此时提交了10个任务

会发生什么事情呢?

  1. 首先核心线程1~3立即处理任务1~3
  2. 其次创建阻塞队列让任务4~6等待,等着核心线程1~3来处理
  3. 然后创建临时线程4~6立即处理任务7~9
  4. 3(核心线程) + 3(临时线程)+ 3(队列长度) < 10,则第10个任务被拒绝服务,除法任务拒绝策略
任务拒绝策略

代码实现

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值